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
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 #use warnings; FIXME - Bug 2505
32 use List::MoreUtils qw/any/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36 # debugging; so please don't remove this
38 use Koha::AuthorisedValues;
39 use Koha::DateUtils qw/dt_from_string/;
42 use Koha::Biblioitems;
45 use Koha::SearchEngine;
46 use Koha::SearchEngine::Search;
49 use vars qw(@ISA @EXPORT);
54 @ISA = qw( Exporter );
72 GetItemsByBiblioitemnumber
76 GetItemnumbersForBiblio
77 get_hostitemnumbers_of
78 GetItemnumberFromBarcode
79 GetBarcodeFromItemnumber
94 PrepareItemrecordDisplay
101 C4::Items - item management functions
105 This module contains an API for manipulating item
106 records in Koha, and is used by cataloguing, circulation,
107 acquisitions, and serials management.
109 # FIXME This POD is not up-to-date
110 A Koha item record is stored in two places: the
111 items table and embedded in a MARC tag in the XML
112 version of the associated bib record in C<biblioitems.marcxml>.
113 This is done to allow the item information to be readily
114 indexed (e.g., by Zebra), but means that each item
115 modification transaction must keep the items table
116 and the MARC XML in sync at all times.
118 Consequently, all code that creates, modifies, or deletes
119 item records B<must> use an appropriate function from
120 C<C4::Items>. If no existing function is suitable, it is
121 better to add one to C<C4::Items> than to use add
122 one-off SQL statements to add or modify items.
124 The items table will be considered authoritative. In other
125 words, if there is ever a discrepancy between the items
126 table and the MARC XML, the items table should be considered
129 =head1 HISTORICAL NOTE
131 Most of the functions in C<C4::Items> were originally in
132 the C<C4::Biblio> module.
134 =head1 CORE EXPORTED FUNCTIONS
136 The following functions are meant for use by users
143 $item = GetItem($itemnumber,$barcode,$serial);
145 Return item information, for a given itemnumber or barcode.
146 The return value is a hashref mapping item column
147 names to values. If C<$serial> is true, include serial publication data.
152 my ($itemnumber,$barcode, $serial) = @_;
153 my $dbh = C4::Context->dbh;
157 $item = Koha::Items->find( $itemnumber );
159 $item = Koha::Items->find( { barcode => $barcode } );
162 return unless ( $item );
164 my $data = $item->unblessed();
165 $data->{itype} = $item->effective_itemtype(); # set the correct itype
168 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
169 $ssth->execute( $data->{'itemnumber'} );
170 ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
178 CartToShelf($itemnumber);
180 Set the current shelving location of the item record
181 to its stored permanent shelving location. This is
182 primarily used to indicate when an item whose current
183 location is a special processing ('PROC') or shelving cart
184 ('CART') location is back in the stacks.
189 my ( $itemnumber ) = @_;
191 unless ( $itemnumber ) {
192 croak "FAILED CartToShelf() - no itemnumber supplied";
195 my $item = GetItem($itemnumber);
196 if ( $item->{location} eq 'CART' ) {
197 $item->{location} = $item->{permanent_location};
198 ModItem($item, undef, $itemnumber);
204 ShelfToCart($itemnumber);
206 Set the current shelving location of the item
207 to shelving cart ('CART').
212 my ( $itemnumber ) = @_;
214 unless ( $itemnumber ) {
215 croak "FAILED ShelfToCart() - no itemnumber supplied";
218 my $item = GetItem($itemnumber);
219 $item->{'location'} = 'CART';
220 ModItem($item, undef, $itemnumber);
223 =head2 AddItemFromMarc
225 my ($biblionumber, $biblioitemnumber, $itemnumber)
226 = AddItemFromMarc($source_item_marc, $biblionumber);
228 Given a MARC::Record object containing an embedded item
229 record and a biblionumber, create a new item record.
233 sub AddItemFromMarc {
234 my ( $source_item_marc, $biblionumber ) = @_;
235 my $dbh = C4::Context->dbh;
237 # parse item hash from MARC
238 my $frameworkcode = GetFrameworkCode( $biblionumber );
239 my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
241 my $localitemmarc=MARC::Record->new;
242 $localitemmarc->append_fields($source_item_marc->field($itemtag));
243 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
244 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
245 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
250 my ($biblionumber, $biblioitemnumber, $itemnumber)
251 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
253 Given a hash containing item column names as keys,
254 create a new Koha item record.
256 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
257 do not need to be supplied for general use; they exist
258 simply to allow them to be picked up from AddItemFromMarc.
260 The final optional parameter, C<$unlinked_item_subfields>, contains
261 an arrayref containing subfields present in the original MARC
262 representation of the item (e.g., from the item editor) that are
263 not mapped to C<items> columns directly but should instead
264 be stored in C<items.more_subfields_xml> and included in
265 the biblio items tag for display and indexing.
271 my $biblionumber = shift;
273 my $dbh = @_ ? shift : C4::Context->dbh;
274 my $frameworkcode = @_ ? shift : GetFrameworkCode($biblionumber);
275 my $unlinked_item_subfields;
277 $unlinked_item_subfields = shift;
280 # needs old biblionumber and biblioitemnumber
281 $item->{'biblionumber'} = $biblionumber;
282 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
283 $sth->execute( $item->{'biblionumber'} );
284 ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
286 _set_defaults_for_add($item);
287 _set_derived_columns_for_add($item);
288 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
290 # FIXME - checks here
291 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
292 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
293 $itype_sth->execute( $item->{'biblionumber'} );
294 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
297 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
300 $item->{'itemnumber'} = $itemnumber;
302 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
304 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
305 if C4::Context->preference("CataloguingLog");
307 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
310 =head2 AddItemBatchFromMarc
312 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
313 $biblionumber, $biblioitemnumber, $frameworkcode);
315 Efficiently create item records from a MARC biblio record with
316 embedded item fields. This routine is suitable for batch jobs.
318 This API assumes that the bib record has already been
319 saved to the C<biblio> and C<biblioitems> tables. It does
320 not expect that C<biblio_metadata.metadata> is populated, but it
321 will do so via a call to ModBibiloMarc.
323 The goal of this API is to have a similar effect to using AddBiblio
324 and AddItems in succession, but without inefficient repeated
325 parsing of the MARC XML bib record.
327 This function returns an arrayref of new itemsnumbers and an arrayref of item
328 errors encountered during the processing. Each entry in the errors
329 list is a hashref containing the following keys:
335 Sequence number of original item tag in the MARC record.
339 Item barcode, provide to assist in the construction of
340 useful error messages.
344 Code representing the error condition. Can be 'duplicate_barcode',
345 'invalid_homebranch', or 'invalid_holdingbranch'.
347 =item error_information
349 Additional information appropriate to the error condition.
355 sub AddItemBatchFromMarc {
356 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
358 my @itemnumbers = ();
360 my $dbh = C4::Context->dbh;
362 # We modify the record, so lets work on a clone so we don't change the
364 $record = $record->clone();
365 # loop through the item tags and start creating items
366 my @bad_item_fields = ();
367 my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
368 my $item_sequence_num = 0;
369 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
370 $item_sequence_num++;
371 # we take the item field and stick it into a new
372 # MARC record -- this is required so far because (FIXME)
373 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
374 # and there is no TransformMarcFieldToKoha
375 my $temp_item_marc = MARC::Record->new();
376 $temp_item_marc->append_fields($item_field);
378 # add biblionumber and biblioitemnumber
379 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
380 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
381 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
382 $item->{'biblionumber'} = $biblionumber;
383 $item->{'biblioitemnumber'} = $biblioitemnumber;
385 # check for duplicate barcode
386 my %item_errors = CheckItemPreSave($item);
388 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
389 push @bad_item_fields, $item_field;
393 _set_defaults_for_add($item);
394 _set_derived_columns_for_add($item);
395 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
396 warn $error if $error;
397 push @itemnumbers, $itemnumber; # FIXME not checking error
398 $item->{'itemnumber'} = $itemnumber;
400 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
402 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
403 $item_field->replace_with($new_item_marc->field($itemtag));
406 # remove any MARC item fields for rejected items
407 foreach my $item_field (@bad_item_fields) {
408 $record->delete_field($item_field);
411 # update the MARC biblio
412 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
414 return (\@itemnumbers, \@errors);
417 =head2 ModItemFromMarc
419 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
421 This function updates an item record based on a supplied
422 C<MARC::Record> object containing an embedded item field.
423 This API is meant for the use of C<additem.pl>; for
424 other purposes, C<ModItem> should be used.
426 This function uses the hash %default_values_for_mod_from_marc,
427 which contains default values for item fields to
428 apply when modifying an item. This is needed because
429 if an item field's value is cleared, TransformMarcToKoha
430 does not include the column in the
431 hash that's passed to ModItem, which without
432 use of this hash makes it impossible to clear
433 an item field's value. See bug 2466.
435 Note that only columns that can be directly
436 changed from the cataloging and serials
437 item editors are included in this hash.
443 sub _build_default_values_for_mod_marc {
444 my ($frameworkcode) = @_;
446 my $cache = Koha::Caches->get_instance();
447 my $cache_key = "default_value_for_mod_marc-$frameworkcode";
448 my $cached = $cache->get_from_cache($cache_key);
449 return $cached if $cached;
451 my $default_values = {
453 booksellerid => undef,
455 'items.cn_source' => undef,
456 coded_location_qualifier => undef,
460 holdingbranch => undef,
462 itemcallnumber => undef,
465 itemnotes_nonpublic => undef,
468 permanent_location => undef,
472 # paidfor => undef, # commented, see bug 12817
474 replacementprice => undef,
475 replacementpricedate => undef,
478 stocknumber => undef,
482 my %default_values_for_mod_from_marc;
483 while ( my ( $field, $default_value ) = each %$default_values ) {
484 my $kohafield = $field;
485 $kohafield =~ s|^([^\.]+)$|items.$1|;
486 $default_values_for_mod_from_marc{$field} =
488 if C4::Koha::IsKohaFieldLinked(
489 { kohafield => $kohafield, frameworkcode => $frameworkcode } );
492 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
493 return \%default_values_for_mod_from_marc;
496 sub ModItemFromMarc {
497 my $item_marc = shift;
498 my $biblionumber = shift;
499 my $itemnumber = shift;
501 my $dbh = C4::Context->dbh;
502 my $frameworkcode = GetFrameworkCode($biblionumber);
503 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
505 my $localitemmarc = MARC::Record->new;
506 $localitemmarc->append_fields( $item_marc->field($itemtag) );
507 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
508 my $default_values = _build_default_values_for_mod_marc($frameworkcode);
509 foreach my $item_field ( keys %$default_values ) {
510 $item->{$item_field} = $default_values->{$item_field}
511 unless exists $item->{$item_field};
513 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
515 ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
521 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
523 Change one or more columns in an item record and update
524 the MARC representation of the item.
526 The first argument is a hashref mapping from item column
527 names to the new values. The second and third arguments
528 are the biblionumber and itemnumber, respectively.
530 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
531 an arrayref containing subfields present in the original MARC
532 representation of the item (e.g., from the item editor) that are
533 not mapped to C<items> columns directly but should instead
534 be stored in C<items.more_subfields_xml> and included in
535 the biblio items tag for display and indexing.
537 If one of the changed columns is used to calculate
538 the derived value of a column such as C<items.cn_sort>,
539 this routine will perform the necessary calculation
546 my $biblionumber = shift;
547 my $itemnumber = shift;
549 # if $biblionumber is undefined, get it from the current item
550 unless (defined $biblionumber) {
551 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
554 my $dbh = @_ ? shift : C4::Context->dbh;
555 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
557 my $unlinked_item_subfields;
559 $unlinked_item_subfields = shift;
560 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
563 $item->{'itemnumber'} = $itemnumber or return;
565 my @fields = qw( itemlost withdrawn );
567 # Only call GetItem if we need to set an "on" date field
568 if ( $item->{itemlost} || $item->{withdrawn} ) {
569 my $pre_mod_item = GetItem( $item->{'itemnumber'} );
570 for my $field (@fields) {
571 if ( defined( $item->{$field} )
572 and not $pre_mod_item->{$field}
573 and $item->{$field} )
575 $item->{ $field . '_on' } =
576 DateTime::Format::MySQL->format_datetime( dt_from_string() );
581 # If the field is defined but empty, we are removing and,
582 # and thus need to clear out the 'on' field as well
583 for my $field (@fields) {
584 if ( defined( $item->{$field} ) && !$item->{$field} ) {
585 $item->{ $field . '_on' } = undef;
590 _set_derived_columns_for_mod($item);
591 _do_column_fixes_for_mod($item);
594 # attempt to change itemnumber
595 # attempt to change biblionumber (if we want
596 # an API to relink an item to a different bib,
597 # it should be a separate function)
600 _koha_modify_item($item);
602 # request that bib be reindexed so that searching on current
603 # item status is possible
604 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
606 logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
609 =head2 ModItemTransfer
611 ModItemTransfer($itenumber, $frombranch, $tobranch);
613 Marks an item as being transferred from one branch
618 sub ModItemTransfer {
619 my ( $itemnumber, $frombranch, $tobranch ) = @_;
621 my $dbh = C4::Context->dbh;
623 # Remove the 'shelving cart' location status if it is being used.
624 CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
626 #new entry in branchtransfers....
627 my $sth = $dbh->prepare(
628 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
629 VALUES (?, ?, NOW(), ?)");
630 $sth->execute($itemnumber, $frombranch, $tobranch);
632 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
633 ModDateLastSeen($itemnumber);
637 =head2 ModDateLastSeen
639 ModDateLastSeen($itemnum);
641 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
642 C<$itemnum> is the item number
646 sub ModDateLastSeen {
647 my ($itemnumber) = @_;
649 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
650 ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
655 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
657 Exported function (core API) for deleting an item record in Koha.
664 my $itemnumber = $params->{itemnumber};
665 my $biblionumber = $params->{biblionumber};
667 unless ($biblionumber) {
668 $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
671 # If there is no biblionumber for the given itemnumber, there is nothing to delete
672 return 0 unless $biblionumber;
674 # FIXME check the item has no current issues
675 my $deleted = _koha_delete_item( $itemnumber );
677 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
679 #search item field code
680 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
684 =head2 CheckItemPreSave
686 my $item_ref = TransformMarcToKoha($marc, 'items');
688 my %errors = CheckItemPreSave($item_ref);
689 if (exists $errors{'duplicate_barcode'}) {
690 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
691 } elsif (exists $errors{'invalid_homebranch'}) {
692 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
693 } elsif (exists $errors{'invalid_holdingbranch'}) {
694 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
699 Given a hashref containing item fields, determine if it can be
700 inserted or updated in the database. Specifically, checks for
701 database integrity issues, and returns a hash containing any
702 of the following keys, if applicable.
706 =item duplicate_barcode
708 Barcode, if it duplicates one already found in the database.
710 =item invalid_homebranch
712 Home branch, if not defined in branches table.
714 =item invalid_holdingbranch
716 Holding branch, if not defined in branches table.
720 This function does NOT implement any policy-related checks,
721 e.g., whether current operator is allowed to save an
722 item that has a given branch code.
726 sub CheckItemPreSave {
727 my $item_ref = shift;
731 # check for duplicate barcode
732 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
733 my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
734 if ($existing_itemnumber) {
735 if (!exists $item_ref->{'itemnumber'} # new item
736 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
737 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
742 # check for valid home branch
743 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
744 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
745 unless (defined $home_library) {
746 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
750 # check for valid holding branch
751 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
752 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
753 unless (defined $holding_library) {
754 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
762 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
764 The following functions provide various ways of
765 getting an item record, a set of item records, or
766 lists of authorized values for certain item fields.
768 Some of the functions in this group are candidates
769 for refactoring -- for example, some of the code
770 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
771 has copy-and-paste work.
775 =head2 GetItemsForInventory
777 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
778 minlocation => $minlocation,
779 maxlocation => $maxlocation,
780 location => $location,
781 itemtype => $itemtype,
782 ignoreissued => $ignoreissued,
783 datelastseen => $datelastseen,
784 branchcode => $branchcode,
788 statushash => $statushash,
791 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
793 The sub returns a reference to a list of hashes, each containing
794 itemnumber, author, title, barcode, item callnumber, and date last
795 seen. It is ordered by callnumber then title.
797 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
798 the datelastseen can be used to specify that you want to see items not seen since a past date only.
799 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
800 $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.
802 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
806 sub GetItemsForInventory {
807 my ( $parameters ) = @_;
808 my $minlocation = $parameters->{'minlocation'} // '';
809 my $maxlocation = $parameters->{'maxlocation'} // '';
810 my $location = $parameters->{'location'} // '';
811 my $itemtype = $parameters->{'itemtype'} // '';
812 my $ignoreissued = $parameters->{'ignoreissued'} // '';
813 my $datelastseen = $parameters->{'datelastseen'} // '';
814 my $branchcode = $parameters->{'branchcode'} // '';
815 my $branch = $parameters->{'branch'} // '';
816 my $offset = $parameters->{'offset'} // '';
817 my $size = $parameters->{'size'} // '';
818 my $statushash = $parameters->{'statushash'} // '';
820 my $dbh = C4::Context->dbh;
821 my ( @bind_params, @where_strings );
823 my $select_columns = q{
824 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
826 my $select_count = q{SELECT COUNT(*)};
829 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
830 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
833 for my $authvfield (keys %$statushash){
834 if ( scalar @{$statushash->{$authvfield}} > 0 ){
835 my $joinedvals = join ',', @{$statushash->{$authvfield}};
836 push @where_strings, "$authvfield in (" . $joinedvals . ")";
842 push @where_strings, 'itemcallnumber >= ?';
843 push @bind_params, $minlocation;
847 push @where_strings, 'itemcallnumber <= ?';
848 push @bind_params, $maxlocation;
852 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
853 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
854 push @bind_params, $datelastseen;
858 push @where_strings, 'items.location = ?';
859 push @bind_params, $location;
863 if($branch eq "homebranch"){
864 push @where_strings, 'items.homebranch = ?';
866 push @where_strings, 'items.holdingbranch = ?';
868 push @bind_params, $branchcode;
872 push @where_strings, 'biblioitems.itemtype = ?';
873 push @bind_params, $itemtype;
876 if ( $ignoreissued) {
877 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
878 push @where_strings, 'issues.date_due IS NULL';
881 if ( @where_strings ) {
883 $query .= join ' AND ', @where_strings;
885 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
886 my $count_query = $select_count . $query;
887 $query .= " LIMIT $offset, $size" if ($offset and $size);
888 $query = $select_columns . $query;
889 my $sth = $dbh->prepare($query);
890 $sth->execute( @bind_params );
893 my $tmpresults = $sth->fetchall_arrayref({});
894 $sth = $dbh->prepare( $count_query );
895 $sth->execute( @bind_params );
896 my ($iTotalRecords) = $sth->fetchrow_array();
898 my @avs = Koha::AuthorisedValues->search(
899 { 'marc_subfield_structures.kohafield' => { '>' => '' },
900 'me.authorised_value' => { '>' => '' },
902 { join => { category => 'marc_subfield_structures' },
903 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
904 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
905 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
909 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
911 foreach my $row (@$tmpresults) {
914 foreach (keys %$row) {
915 if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
916 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
922 return (\@results, $iTotalRecords);
925 =head2 GetItemsByBiblioitemnumber
927 GetItemsByBiblioitemnumber($biblioitemnumber);
929 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
930 Called by C<C4::XISBN>
934 sub GetItemsByBiblioitemnumber {
935 my ( $bibitem ) = @_;
936 my $dbh = C4::Context->dbh;
937 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
938 # Get all items attached to a biblioitem
941 $sth->execute($bibitem) || die $sth->errstr;
942 while ( my $data = $sth->fetchrow_hashref ) {
943 # Foreach item, get circulation information
944 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
946 AND issues.borrowernumber = borrowers.borrowernumber"
948 $sth2->execute( $data->{'itemnumber'} );
949 if ( my $data2 = $sth2->fetchrow_hashref ) {
950 # if item is out, set the due date and who it is out too
951 $data->{'date_due'} = $data2->{'date_due'};
952 $data->{'cardnumber'} = $data2->{'cardnumber'};
953 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
956 # set date_due to blank, so in the template we check itemlost, and withdrawn
957 $data->{'date_due'} = '';
959 # Find the last 3 people who borrowed this item.
960 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
961 AND old_issues.borrowernumber = borrowers.borrowernumber
962 ORDER BY returndate desc,timestamp desc LIMIT 3";
963 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
964 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
966 while ( my $data2 = $sth2->fetchrow_hashref ) {
967 $data->{"timestamp$i2"} = $data2->{'timestamp'};
968 $data->{"card$i2"} = $data2->{'cardnumber'};
969 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
972 push(@results,$data);
979 @results = GetItemsInfo($biblionumber);
981 Returns information about items with the given biblionumber.
983 C<GetItemsInfo> returns a list of references-to-hash. Each element
984 contains a number of keys. Most of them are attributes from the
985 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
986 Koha database. Other keys include:
990 =item C<$data-E<gt>{branchname}>
992 The name (not the code) of the branch to which the book belongs.
994 =item C<$data-E<gt>{datelastseen}>
996 This is simply C<items.datelastseen>, except that while the date is
997 stored in YYYY-MM-DD format in the database, here it is converted to
998 DD/MM/YYYY format. A NULL date is returned as C<//>.
1000 =item C<$data-E<gt>{datedue}>
1002 =item C<$data-E<gt>{class}>
1004 This is the concatenation of C<biblioitems.classification>, the book's
1005 Dewey code, and C<biblioitems.subclass>.
1007 =item C<$data-E<gt>{ocount}>
1009 I think this is the number of copies of the book available.
1011 =item C<$data-E<gt>{order}>
1013 If this is set, it is set to C<One Order>.
1020 my ( $biblionumber ) = @_;
1021 my $dbh = C4::Context->dbh;
1022 require C4::Languages;
1023 my $language = C4::Languages::getlanguage();
1029 biblioitems.itemtype,
1032 biblioitems.publicationyear,
1033 biblioitems.publishercode,
1034 biblioitems.volumedate,
1035 biblioitems.volumedesc,
1038 items.notforloan as itemnotforloan,
1039 issues.borrowernumber,
1040 issues.date_due as datedue,
1041 issues.onsite_checkout,
1042 borrowers.cardnumber,
1044 borrowers.firstname,
1045 borrowers.branchcode as bcode,
1047 serial.publisheddate,
1048 itemtypes.description,
1049 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1050 itemtypes.notforloan as notforloan_per_itemtype,
1054 holding.opac_info as holding_branch_opac_info,
1055 home.opac_info as home_branch_opac_info
1059 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1060 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1061 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1062 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1063 LEFT JOIN issues USING (itemnumber)
1064 LEFT JOIN borrowers USING (borrowernumber)
1065 LEFT JOIN serialitems USING (itemnumber)
1066 LEFT JOIN serial USING (serialid)
1067 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1068 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1070 LEFT JOIN localization ON itemtypes.itemtype = localization.code
1071 AND localization.entity = 'itemtypes'
1072 AND localization.lang = ?
1075 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1076 my $sth = $dbh->prepare($query);
1077 $sth->execute($language, $biblionumber);
1082 my $userenv = C4::Context->userenv;
1083 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1084 while ( my $data = $sth->fetchrow_hashref ) {
1085 if ( $data->{borrowernumber} && $want_not_same_branch) {
1086 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1089 $serial ||= $data->{'serial'};
1092 # get notforloan complete status if applicable
1093 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1094 $data->{notforloanvalue} = $descriptions->{lib} // '';
1095 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
1097 # get restricted status and description if applicable
1098 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1099 $data->{restricted} = $descriptions->{lib} // '';
1100 $data->{restrictedopac} = $descriptions->{opac_description} // '';
1102 # my stack procedures
1103 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1104 $data->{stack} = $descriptions->{lib} // '';
1106 # Find the last 3 people who borrowed this item.
1107 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1108 WHERE itemnumber = ?
1109 AND old_issues.borrowernumber = borrowers.borrowernumber
1110 ORDER BY returndate DESC
1112 $sth2->execute($data->{'itemnumber'});
1114 while (my $data2 = $sth2->fetchrow_hashref()) {
1115 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1116 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1117 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1121 $results[$i] = $data;
1126 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1130 =head2 GetItemsLocationInfo
1132 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1134 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1136 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1140 =item C<$data-E<gt>{homebranch}>
1142 Branch Name of the item's homebranch
1144 =item C<$data-E<gt>{holdingbranch}>
1146 Branch Name of the item's holdingbranch
1148 =item C<$data-E<gt>{location}>
1150 Item's shelving location code
1152 =item C<$data-E<gt>{location_intranet}>
1154 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1156 =item C<$data-E<gt>{location_opac}>
1158 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1161 =item C<$data-E<gt>{itemcallnumber}>
1163 Item's itemcallnumber
1165 =item C<$data-E<gt>{cn_sort}>
1167 Item's call number normalized for sorting
1173 sub GetItemsLocationInfo {
1174 my $biblionumber = shift;
1177 my $dbh = C4::Context->dbh;
1178 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1179 location, itemcallnumber, cn_sort
1180 FROM items, branches as a, branches as b
1181 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1182 AND biblionumber = ?
1183 ORDER BY cn_sort ASC";
1184 my $sth = $dbh->prepare($query);
1185 $sth->execute($biblionumber);
1187 while ( my $data = $sth->fetchrow_hashref ) {
1188 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1189 $av = $av->count ? $av->next : undef;
1190 $data->{location_intranet} = $av ? $av->lib : '';
1191 $data->{location_opac} = $av ? $av->opac_description : '';
1192 push @results, $data;
1197 =head2 GetHostItemsInfo
1199 $hostiteminfo = GetHostItemsInfo($hostfield);
1200 Returns the iteminfo for items linked to records via a host field
1204 sub GetHostItemsInfo {
1206 my @returnitemsInfo;
1208 if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1209 C4::Context->preference('marcflavour') eq 'NORMARC'){
1210 foreach my $hostfield ( $record->field('773') ) {
1211 my $hostbiblionumber = $hostfield->subfield("0");
1212 my $linkeditemnumber = $hostfield->subfield("9");
1213 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1214 foreach my $hostitemInfo (@hostitemInfos){
1215 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1216 push (@returnitemsInfo,$hostitemInfo);
1221 } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1222 foreach my $hostfield ( $record->field('461') ) {
1223 my $hostbiblionumber = $hostfield->subfield("0");
1224 my $linkeditemnumber = $hostfield->subfield("9");
1225 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1226 foreach my $hostitemInfo (@hostitemInfos){
1227 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1228 push (@returnitemsInfo,$hostitemInfo);
1234 return @returnitemsInfo;
1238 =head2 GetLastAcquisitions
1240 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1241 'itemtypes' => ('BK','BD')}, 10);
1245 sub GetLastAcquisitions {
1246 my ($data,$max) = @_;
1248 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1250 my $number_of_branches = @{$data->{branches}};
1251 my $number_of_itemtypes = @{$data->{itemtypes}};
1254 my @where = ('WHERE 1 ');
1255 $number_of_branches and push @where
1256 , 'AND holdingbranch IN ('
1257 , join(',', ('?') x $number_of_branches )
1261 $number_of_itemtypes and push @where
1262 , "AND $itemtype IN ("
1263 , join(',', ('?') x $number_of_itemtypes )
1267 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1268 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1269 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1271 GROUP BY biblio.biblionumber
1272 ORDER BY dateaccessioned DESC LIMIT $max";
1274 my $dbh = C4::Context->dbh;
1275 my $sth = $dbh->prepare($query);
1277 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1280 while( my $row = $sth->fetchrow_hashref){
1281 push @results, {date => $row->{dateaccessioned}
1282 , biblionumber => $row->{biblionumber}
1283 , title => $row->{title}};
1289 =head2 GetItemnumbersForBiblio
1291 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1293 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1297 sub GetItemnumbersForBiblio {
1298 my $biblionumber = shift;
1300 my $dbh = C4::Context->dbh;
1301 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1302 $sth->execute($biblionumber);
1303 while (my $result = $sth->fetchrow_hashref) {
1304 push @items, $result->{'itemnumber'};
1309 =head2 get_hostitemnumbers_of
1311 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1313 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1315 Return a reference on a hash where key is a biblionumber and values are
1316 references on array of itemnumbers.
1321 sub get_hostitemnumbers_of {
1322 my ($biblionumber) = @_;
1323 my $marcrecord = GetMarcBiblio($biblionumber);
1325 return unless $marcrecord;
1327 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1329 my $marcflavor = C4::Context->preference('marcflavour');
1330 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1335 elsif ( $marcflavor eq 'UNIMARC' ) {
1341 foreach my $hostfield ( $marcrecord->field($tag) ) {
1342 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1343 my $linkeditemnumber = $hostfield->subfield($item_s);
1344 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1345 push @returnhostitemnumbers, $linkeditemnumber
1349 return @returnhostitemnumbers;
1353 =head2 GetItemnumberFromBarcode
1355 $result = GetItemnumberFromBarcode($barcode);
1359 sub GetItemnumberFromBarcode {
1361 my $dbh = C4::Context->dbh;
1364 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1365 $rq->execute($barcode);
1366 my ($result) = $rq->fetchrow;
1370 =head2 GetBarcodeFromItemnumber
1372 $result = GetBarcodeFromItemnumber($itemnumber);
1376 sub GetBarcodeFromItemnumber {
1377 my ($itemnumber) = @_;
1378 my $dbh = C4::Context->dbh;
1381 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1382 $rq->execute($itemnumber);
1383 my ($result) = $rq->fetchrow;
1387 =head2 GetHiddenItemnumbers
1389 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1391 Given a list of items it checks which should be hidden from the OPAC given
1392 the current configuration. Returns a list of itemnumbers corresponding to
1393 those that should be hidden.
1397 sub GetHiddenItemnumbers {
1401 my $yaml = C4::Context->preference('OpacHiddenItems');
1402 return () if (! $yaml =~ /\S/ );
1403 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1406 $hidingrules = YAML::Load($yaml);
1409 warn "Unable to parse OpacHiddenItems syspref : $@";
1412 my $dbh = C4::Context->dbh;
1415 foreach my $item (@items) {
1417 # We check each rule
1418 foreach my $field (keys %$hidingrules) {
1420 if (exists $item->{$field}) {
1421 $val = $item->{$field};
1424 my $query = "SELECT $field from items where itemnumber = ?";
1425 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1427 $val = '' unless defined $val;
1429 # If the results matches the values in the yaml file
1430 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1432 # We add the itemnumber to the list
1433 push @resultitems, $item->{'itemnumber'};
1435 # If at least one rule matched for an item, no need to test the others
1440 return @resultitems;
1443 =head1 LIMITED USE FUNCTIONS
1445 The following functions, while part of the public API,
1446 are not exported. This is generally because they are
1447 meant to be used by only one script for a specific
1448 purpose, and should not be used in any other context
1449 without careful thought.
1455 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1457 Returns MARC::Record of the item passed in parameter.
1458 This function is meant for use only in C<cataloguing/additem.pl>,
1459 where it is needed to support that script's MARC-like
1465 my ( $biblionumber, $itemnumber ) = @_;
1467 # GetMarcItem has been revised so that it does the following:
1468 # 1. Gets the item information from the items table.
1469 # 2. Converts it to a MARC field for storage in the bib record.
1471 # The previous behavior was:
1472 # 1. Get the bib record.
1473 # 2. Return the MARC tag corresponding to the item record.
1475 # The difference is that one treats the items row as authoritative,
1476 # while the other treats the MARC representation as authoritative
1477 # under certain circumstances.
1479 my $itemrecord = GetItem($itemnumber);
1481 # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1482 # Also, don't emit a subfield if the underlying field is blank.
1485 return Item2Marc($itemrecord,$biblionumber);
1489 my ($itemrecord,$biblionumber)=@_;
1492 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1493 } keys %{ $itemrecord }
1495 my $itemmarc = TransformKohaToMarc($mungeditem);
1496 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1498 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1499 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1500 foreach my $field ($itemmarc->field($itemtag)){
1501 $field->add_subfields(@$unlinked_item_subfields);
1507 =head1 PRIVATE FUNCTIONS AND VARIABLES
1509 The following functions are not meant to be called
1510 directly, but are documented in order to explain
1511 the inner workings of C<C4::Items>.
1515 =head2 %derived_columns
1517 This hash keeps track of item columns that
1518 are strictly derived from other columns in
1519 the item record and are not meant to be set
1522 Each key in the hash should be the name of a
1523 column (as named by TransformMarcToKoha). Each
1524 value should be hashref whose keys are the
1525 columns on which the derived column depends. The
1526 hashref should also contain a 'BUILDER' key
1527 that is a reference to a sub that calculates
1532 my %derived_columns = (
1533 'items.cn_sort' => {
1534 'itemcallnumber' => 1,
1535 'items.cn_source' => 1,
1536 'BUILDER' => \&_calc_items_cn_sort,
1540 =head2 _set_derived_columns_for_add
1542 _set_derived_column_for_add($item);
1544 Given an item hash representing a new item to be added,
1545 calculate any derived columns. Currently the only
1546 such column is C<items.cn_sort>.
1550 sub _set_derived_columns_for_add {
1553 foreach my $column (keys %derived_columns) {
1554 my $builder = $derived_columns{$column}->{'BUILDER'};
1555 my $source_values = {};
1556 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1557 next if $source_column eq 'BUILDER';
1558 $source_values->{$source_column} = $item->{$source_column};
1560 $builder->($item, $source_values);
1564 =head2 _set_derived_columns_for_mod
1566 _set_derived_column_for_mod($item);
1568 Given an item hash representing a new item to be modified.
1569 calculate any derived columns. Currently the only
1570 such column is C<items.cn_sort>.
1572 This routine differs from C<_set_derived_columns_for_add>
1573 in that it needs to handle partial item records. In other
1574 words, the caller of C<ModItem> may have supplied only one
1575 or two columns to be changed, so this function needs to
1576 determine whether any of the columns to be changed affect
1577 any of the derived columns. Also, if a derived column
1578 depends on more than one column, but the caller is not
1579 changing all of then, this routine retrieves the unchanged
1580 values from the database in order to ensure a correct
1585 sub _set_derived_columns_for_mod {
1588 foreach my $column (keys %derived_columns) {
1589 my $builder = $derived_columns{$column}->{'BUILDER'};
1590 my $source_values = {};
1591 my %missing_sources = ();
1592 my $must_recalc = 0;
1593 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1594 next if $source_column eq 'BUILDER';
1595 if (exists $item->{$source_column}) {
1597 $source_values->{$source_column} = $item->{$source_column};
1599 $missing_sources{$source_column} = 1;
1603 foreach my $source_column (keys %missing_sources) {
1604 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1606 $builder->($item, $source_values);
1611 =head2 _do_column_fixes_for_mod
1613 _do_column_fixes_for_mod($item);
1615 Given an item hashref containing one or more
1616 columns to modify, fix up certain values.
1617 Specifically, set to 0 any passed value
1618 of C<notforloan>, C<damaged>, C<itemlost>, or
1619 C<withdrawn> that is either undefined or
1620 contains the empty string.
1624 sub _do_column_fixes_for_mod {
1627 if (exists $item->{'notforloan'} and
1628 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1629 $item->{'notforloan'} = 0;
1631 if (exists $item->{'damaged'} and
1632 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1633 $item->{'damaged'} = 0;
1635 if (exists $item->{'itemlost'} and
1636 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1637 $item->{'itemlost'} = 0;
1639 if (exists $item->{'withdrawn'} and
1640 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1641 $item->{'withdrawn'} = 0;
1643 if (exists $item->{location}
1644 and $item->{location} ne 'CART'
1645 and $item->{location} ne 'PROC'
1646 and not $item->{permanent_location}
1648 $item->{'permanent_location'} = $item->{'location'};
1650 if (exists $item->{'timestamp'}) {
1651 delete $item->{'timestamp'};
1655 =head2 _get_single_item_column
1657 _get_single_item_column($column, $itemnumber);
1659 Retrieves the value of a single column from an C<items>
1660 row specified by C<$itemnumber>.
1664 sub _get_single_item_column {
1666 my $itemnumber = shift;
1668 my $dbh = C4::Context->dbh;
1669 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1670 $sth->execute($itemnumber);
1671 my ($value) = $sth->fetchrow();
1675 =head2 _calc_items_cn_sort
1677 _calc_items_cn_sort($item, $source_values);
1679 Helper routine to calculate C<items.cn_sort>.
1683 sub _calc_items_cn_sort {
1685 my $source_values = shift;
1687 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1690 =head2 _set_defaults_for_add
1692 _set_defaults_for_add($item_hash);
1694 Given an item hash representing an item to be added, set
1695 correct default values for columns whose default value
1696 is not handled by the DBMS. This includes the following
1703 C<items.dateaccessioned>
1725 sub _set_defaults_for_add {
1727 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1728 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1731 =head2 _koha_new_item
1733 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1735 Perform the actual insert into the C<items> table.
1739 sub _koha_new_item {
1740 my ( $item, $barcode ) = @_;
1741 my $dbh=C4::Context->dbh;
1743 $item->{permanent_location} //= $item->{location};
1744 _mod_item_dates( $item );
1746 "INSERT INTO items SET
1748 biblioitemnumber = ?,
1750 dateaccessioned = ?,
1754 replacementprice = ?,
1755 replacementpricedate = ?,
1756 datelastborrowed = ?,
1764 coded_location_qualifier = ?,
1767 itemnotes_nonpublic = ?,
1771 permanent_location = ?,
1783 more_subfields_xml = ?,
1788 my $sth = $dbh->prepare($query);
1789 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1791 $item->{'biblionumber'},
1792 $item->{'biblioitemnumber'},
1794 $item->{'dateaccessioned'},
1795 $item->{'booksellerid'},
1796 $item->{'homebranch'},
1798 $item->{'replacementprice'},
1799 $item->{'replacementpricedate'} || $today,
1800 $item->{datelastborrowed},
1801 $item->{datelastseen} || $today,
1803 $item->{'notforloan'},
1805 $item->{'itemlost'},
1806 $item->{'withdrawn'},
1807 $item->{'itemcallnumber'},
1808 $item->{'coded_location_qualifier'},
1809 $item->{'restricted'},
1810 $item->{'itemnotes'},
1811 $item->{'itemnotes_nonpublic'},
1812 $item->{'holdingbranch'},
1814 $item->{'location'},
1815 $item->{'permanent_location'},
1818 $item->{'renewals'},
1819 $item->{'reserves'},
1820 $item->{'items.cn_source'},
1821 $item->{'items.cn_sort'},
1824 $item->{'materials'},
1826 $item->{'enumchron'},
1827 $item->{'more_subfields_xml'},
1828 $item->{'copynumber'},
1829 $item->{'stocknumber'},
1830 $item->{'new_status'},
1834 if ( defined $sth->errstr ) {
1835 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1838 $itemnumber = $dbh->{'mysql_insertid'};
1841 return ( $itemnumber, $error );
1844 =head2 MoveItemFromBiblio
1846 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1848 Moves an item from a biblio to another
1850 Returns undef if the move failed or the biblionumber of the destination record otherwise
1854 sub MoveItemFromBiblio {
1855 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1856 my $dbh = C4::Context->dbh;
1857 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1858 SELECT biblioitemnumber
1860 WHERE biblionumber = ?
1861 |, undef, $tobiblio );
1862 my $return = $dbh->do(q|
1864 SET biblioitemnumber = ?,
1866 WHERE itemnumber = ?
1867 AND biblionumber = ?
1868 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1870 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1871 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1872 # Checking if the item we want to move is in an order
1873 require C4::Acquisition;
1874 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1876 # Replacing the biblionumber within the order if necessary
1877 $order->{'biblionumber'} = $tobiblio;
1878 C4::Acquisition::ModOrder($order);
1881 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1882 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1885 SET biblionumber = ?
1886 WHERE itemnumber = ?
1887 |, undef, $tobiblio, $itemnumber );
1894 =head2 ItemSafeToDelete
1896 ItemSafeToDelete( $biblionumber, $itemnumber);
1898 Exported function (core API) for checking whether an item record is safe to delete.
1900 returns 1 if the item is safe to delete,
1902 "book_on_loan" if the item is checked out,
1904 "not_same_branch" if the item is blocked by independent branches,
1906 "book_reserved" if the there are holds aganst the item, or
1908 "linked_analytics" if the item has linked analytic records.
1912 sub ItemSafeToDelete {
1913 my ( $biblionumber, $itemnumber ) = @_;
1915 my $dbh = C4::Context->dbh;
1919 my $countanalytics = GetAnalyticsCount($itemnumber);
1921 # check that there is no issue on this item before deletion.
1922 my $sth = $dbh->prepare(
1924 SELECT COUNT(*) FROM issues
1925 WHERE itemnumber = ?
1928 $sth->execute($itemnumber);
1929 my ($onloan) = $sth->fetchrow;
1931 my $item = GetItem($itemnumber);
1934 $status = "book_on_loan";
1936 elsif ( defined C4::Context->userenv
1937 and !C4::Context->IsSuperLibrarian()
1938 and C4::Context->preference("IndependentBranches")
1939 and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
1941 $status = "not_same_branch";
1944 # check it doesn't have a waiting reserve
1945 $sth = $dbh->prepare(
1947 SELECT COUNT(*) FROM reserves
1948 WHERE (found = 'W' OR found = 'T')
1952 $sth->execute($itemnumber);
1953 my ($reserve) = $sth->fetchrow;
1955 $status = "book_reserved";
1957 elsif ( $countanalytics > 0 ) {
1958 $status = "linked_analytics";
1969 DelItemCheck( $biblionumber, $itemnumber);
1971 Exported function (core API) for deleting an item record in Koha if there no current issue.
1973 DelItemCheck wraps ItemSafeToDelete around DelItem.
1978 my ( $biblionumber, $itemnumber ) = @_;
1979 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1981 if ( $status == 1 ) {
1984 biblionumber => $biblionumber,
1985 itemnumber => $itemnumber
1992 =head2 _koha_modify_item
1994 my ($itemnumber,$error) =_koha_modify_item( $item );
1996 Perform the actual update of the C<items> row. Note that this
1997 routine accepts a hashref specifying the columns to update.
2001 sub _koha_modify_item {
2003 my $dbh=C4::Context->dbh;
2006 my $query = "UPDATE items SET ";
2008 _mod_item_dates( $item );
2009 for my $key ( keys %$item ) {
2010 next if ( $key eq 'itemnumber' );
2012 push @bind, $item->{$key};
2015 $query .= " WHERE itemnumber=?";
2016 push @bind, $item->{'itemnumber'};
2017 my $sth = $dbh->prepare($query);
2018 $sth->execute(@bind);
2020 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2023 return ($item->{'itemnumber'},$error);
2026 sub _mod_item_dates { # date formatting for date fields in item hash
2028 return if !$item || ref($item) ne 'HASH';
2031 { $_ =~ /^onloan$|^date|date$|datetime$/ }
2033 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
2034 # NOTE: We do not (yet) have items fields ending with datetime
2035 # Fields with _on$ have been handled already
2037 foreach my $key ( @keys ) {
2038 next if !defined $item->{$key}; # skip undefs
2039 my $dt = eval { dt_from_string( $item->{$key} ) };
2040 # eval: dt_from_string will die on us if we pass illegal dates
2043 if( defined $dt && ref($dt) eq 'DateTime' ) {
2044 if( $key =~ /datetime/ ) {
2045 $newstr = DateTime::Format::MySQL->format_datetime($dt);
2047 $newstr = DateTime::Format::MySQL->format_date($dt);
2050 $item->{$key} = $newstr; # might be undef to clear garbage
2054 =head2 _koha_delete_item
2056 _koha_delete_item( $itemnum );
2058 Internal function to delete an item record from the koha tables
2062 sub _koha_delete_item {
2063 my ( $itemnum ) = @_;
2065 my $dbh = C4::Context->dbh;
2066 # save the deleted item to deleteditems table
2067 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2068 $sth->execute($itemnum);
2069 my $data = $sth->fetchrow_hashref();
2071 # There is no item to delete
2072 return 0 unless $data;
2074 my $query = "INSERT INTO deleteditems SET ";
2076 foreach my $key ( keys %$data ) {
2077 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2078 $query .= "$key = ?,";
2079 push( @bind, $data->{$key} );
2082 $sth = $dbh->prepare($query);
2083 $sth->execute(@bind);
2085 # delete from items table
2086 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2087 my $deleted = $sth->execute($itemnum);
2088 return ( $deleted == 1 ) ? 1 : 0;
2091 =head2 _marc_from_item_hash
2093 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2095 Given an item hash representing a complete item record,
2096 create a C<MARC::Record> object containing an embedded
2097 tag representing that item.
2099 The third, optional parameter C<$unlinked_item_subfields> is
2100 an arrayref of subfields (not mapped to C<items> fields per the
2101 framework) to be added to the MARC representation
2106 sub _marc_from_item_hash {
2108 my $frameworkcode = shift;
2109 my $unlinked_item_subfields;
2111 $unlinked_item_subfields = shift;
2114 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2115 # Also, don't emit a subfield if the underlying field is blank.
2116 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2117 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2118 : () } keys %{ $item } };
2120 my $item_marc = MARC::Record->new();
2121 foreach my $item_field ( keys %{$mungeditem} ) {
2122 my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2123 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2124 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2125 foreach my $value (@values){
2126 if ( my $field = $item_marc->field($tag) ) {
2127 $field->add_subfields( $subfield => $value );
2129 my $add_subfields = [];
2130 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2131 $add_subfields = $unlinked_item_subfields;
2133 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2141 =head2 _repack_item_errors
2143 Add an error message hash generated by C<CheckItemPreSave>
2144 to a list of errors.
2148 sub _repack_item_errors {
2149 my $item_sequence_num = shift;
2150 my $item_ref = shift;
2151 my $error_ref = shift;
2153 my @repacked_errors = ();
2155 foreach my $error_code (sort keys %{ $error_ref }) {
2156 my $repacked_error = {};
2157 $repacked_error->{'item_sequence'} = $item_sequence_num;
2158 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2159 $repacked_error->{'error_code'} = $error_code;
2160 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2161 push @repacked_errors, $repacked_error;
2164 return @repacked_errors;
2167 =head2 _get_unlinked_item_subfields
2169 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2173 sub _get_unlinked_item_subfields {
2174 my $original_item_marc = shift;
2175 my $frameworkcode = shift;
2177 my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2179 # assume that this record has only one field, and that that
2180 # field contains only the item information
2182 my @fields = $original_item_marc->fields();
2183 if ($#fields > -1) {
2184 my $field = $fields[0];
2185 my $tag = $field->tag();
2186 foreach my $subfield ($field->subfields()) {
2187 if (defined $subfield->[1] and
2188 $subfield->[1] ne '' and
2189 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2190 push @$subfields, $subfield->[0] => $subfield->[1];
2197 =head2 _get_unlinked_subfields_xml
2199 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2203 sub _get_unlinked_subfields_xml {
2204 my $unlinked_item_subfields = shift;
2207 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2208 my $marc = MARC::Record->new();
2209 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2210 # used in the framework
2211 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2212 $marc->encoding("UTF-8");
2213 $xml = $marc->as_xml("USMARC");
2219 =head2 _parse_unlinked_item_subfields_from_xml
2221 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2225 sub _parse_unlinked_item_subfields_from_xml {
2227 require C4::Charset;
2228 return unless defined $xml and $xml ne "";
2229 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2230 my $unlinked_subfields = [];
2231 my @fields = $marc->fields();
2232 if ($#fields > -1) {
2233 foreach my $subfield ($fields[0]->subfields()) {
2234 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2237 return $unlinked_subfields;
2240 =head2 GetAnalyticsCount
2242 $count= &GetAnalyticsCount($itemnumber)
2244 counts Usage of itemnumber in Analytical bibliorecords.
2248 sub GetAnalyticsCount {
2249 my ($itemnumber) = @_;
2251 ### ZOOM search here
2253 $query= "hi=".$itemnumber;
2254 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2255 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2259 =head2 SearchItemsByField
2261 my $items = SearchItemsByField($field, $value);
2263 SearchItemsByField will search for items on a specific given field.
2264 For instance you can search all items with a specific stocknumber like this:
2266 my $items = SearchItemsByField('stocknumber', $stocknumber);
2270 sub SearchItemsByField {
2271 my ($field, $value) = @_;
2278 my ($results) = SearchItems($filters);
2282 sub _SearchItems_build_where_fragment {
2285 my $dbh = C4::Context->dbh;
2288 if (exists($filter->{conjunction})) {
2289 my (@where_strs, @where_args);
2290 foreach my $f (@{ $filter->{filters} }) {
2291 my $fragment = _SearchItems_build_where_fragment($f);
2293 push @where_strs, $fragment->{str};
2294 push @where_args, @{ $fragment->{args} };
2299 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2302 args => \@where_args,
2306 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2307 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2308 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2309 my @operators = qw(= != > < >= <= like);
2310 my $field = $filter->{field};
2311 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2312 my $op = $filter->{operator};
2313 my $query = $filter->{query};
2315 if (!$op or (0 == grep /^$op$/, @operators)) {
2316 $op = '='; # default operator
2320 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2322 my $marcsubfield = $2;
2323 my ($kohafield) = $dbh->selectrow_array(q|
2324 SELECT kohafield FROM marc_subfield_structure
2325 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2326 |, undef, $marcfield, $marcsubfield);
2329 $column = $kohafield;
2331 # MARC field is not linked to a DB field so we need to use
2332 # ExtractValue on marcxml from biblio_metadata or
2333 # items.more_subfields_xml, depending on the MARC field.
2336 my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2337 if ($marcfield eq $itemfield) {
2338 $sqlfield = 'more_subfields_xml';
2339 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2341 $sqlfield = 'metadata'; # From biblio_metadata
2342 if ($marcfield < 10) {
2343 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2345 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2348 $column = "ExtractValue($sqlfield, '$xpath')";
2354 if (ref $query eq 'ARRAY') {
2357 } elsif ($op eq '!=') {
2361 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2366 str => "$column $op ?",
2373 return $where_fragment;
2378 my ($items, $total) = SearchItems($filter, $params);
2380 Perform a search among items
2382 $filter is a reference to a hash which can be a filter, or a combination of filters.
2384 A filter has the following keys:
2388 =item * field: the name of a SQL column in table items
2390 =item * query: the value to search in this column
2392 =item * operator: comparison operator. Can be one of = != > < >= <= like
2396 A combination of filters hash the following keys:
2400 =item * conjunction: 'AND' or 'OR'
2402 =item * filters: array ref of filters
2406 $params is a reference to a hash that can contain the following parameters:
2410 =item * rows: Number of items to return. 0 returns everything (default: 0)
2412 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2415 =item * sortby: A SQL column name in items table to sort on
2417 =item * sortorder: 'ASC' or 'DESC'
2424 my ($filter, $params) = @_;
2428 return unless ref $filter eq 'HASH';
2429 return unless ref $params eq 'HASH';
2431 # Default parameters
2432 $params->{rows} ||= 0;
2433 $params->{page} ||= 1;
2434 $params->{sortby} ||= 'itemnumber';
2435 $params->{sortorder} ||= 'ASC';
2437 my ($where_str, @where_args);
2438 my $where_fragment = _SearchItems_build_where_fragment($filter);
2439 if ($where_fragment) {
2440 $where_str = $where_fragment->{str};
2441 @where_args = @{ $where_fragment->{args} };
2444 my $dbh = C4::Context->dbh;
2446 SELECT SQL_CALC_FOUND_ROWS items.*
2448 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2449 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2450 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2453 if (defined $where_str and $where_str ne '') {
2454 $query .= qq{ AND $where_str };
2457 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2458 push @where_args, C4::Context->preference('marcflavour');
2460 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2461 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2462 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2463 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2464 ? $params->{sortby} : 'itemnumber';
2465 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2466 $query .= qq{ ORDER BY $sortby $sortorder };
2468 my $rows = $params->{rows};
2471 my $offset = $rows * ($params->{page}-1);
2472 $query .= qq { LIMIT ?, ? };
2473 push @limit_args, $offset, $rows;
2476 my $sth = $dbh->prepare($query);
2477 my $rv = $sth->execute(@where_args, @limit_args);
2479 return unless ($rv);
2480 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2482 return ($sth->fetchall_arrayref({}), $total_rows);
2486 =head1 OTHER FUNCTIONS
2490 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2492 Find the given $subfield in the given $tag in the given
2493 MARC::Record $record. If the subfield is found, returns
2494 the (indicators, value) pair; otherwise, (undef, undef) is
2498 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2499 I suggest we export it from this module.
2504 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2507 if ( $tagfield < 10 ) {
2508 if ( $record->field($tagfield) ) {
2509 push @result, $record->field($tagfield)->data();
2514 foreach my $field ( $record->field($tagfield) ) {
2515 my @subfields = $field->subfields();
2516 foreach my $subfield (@subfields) {
2517 if ( @$subfield[0] eq $insubfield ) {
2518 push @result, @$subfield[1];
2519 $indicator = $field->indicator(1) . $field->indicator(2);
2524 return ( $indicator, @result );
2528 =head2 PrepareItemrecordDisplay
2530 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2532 Returns a hash with all the fields for Display a given item data in a template
2534 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2538 sub PrepareItemrecordDisplay {
2540 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2542 my $dbh = C4::Context->dbh;
2543 $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2544 my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2546 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2547 # a shared data structure. No plugin (including custom ones) should change
2548 # its contents. See also GetMarcStructure.
2549 my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2551 # return nothing if we don't have found an existing framework.
2552 return q{} unless $tagslib;
2555 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2559 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2561 SELECT authorised_value,lib FROM authorised_values
2564 LEFT JOIN authorised_values_branches ON ( id = av_id )
2569 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2570 $query .= qq{ ORDER BY lib};
2571 my $authorised_values_sth = $dbh->prepare( $query );
2572 foreach my $tag ( sort keys %{$tagslib} ) {
2575 # loop through each subfield
2577 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2578 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2579 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2580 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2582 $subfield_data{tag} = $tag;
2583 $subfield_data{subfield} = $subfield;
2584 $subfield_data{countsubfield} = $cntsubf++;
2585 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2586 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2588 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2589 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2590 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2591 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2592 $subfield_data{hidden} = "display:none"
2593 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2594 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2595 my ( $x, $defaultvalue );
2597 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2599 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2600 if ( !defined $defaultvalue ) {
2601 $defaultvalue = q||;
2603 $defaultvalue =~ s/"/"/g;
2606 # search for itemcallnumber if applicable
2607 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2608 && C4::Context->preference('itemcallnumber') ) {
2609 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2610 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2611 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2612 $defaultvalue = $field->subfield($CNsubfield);
2615 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2617 && $defaultvalues->{'callnumber'} ) {
2618 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2619 # if the item record exists, only use default value if the item has no callnumber
2620 $defaultvalue = $defaultvalues->{callnumber};
2621 } elsif ( !$itemrecord and $defaultvalues ) {
2622 # if the item record *doesn't* exists, always use the default value
2623 $defaultvalue = $defaultvalues->{callnumber};
2626 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2628 && $defaultvalues->{'branchcode'} ) {
2629 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2630 $defaultvalue = $defaultvalues->{branchcode};
2633 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2635 && $defaultvalues->{'location'} ) {
2637 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2638 # if the item record exists, only use default value if the item has no locationr
2639 $defaultvalue = $defaultvalues->{location};
2640 } elsif ( !$itemrecord and $defaultvalues ) {
2641 # if the item record *doesn't* exists, always use the default value
2642 $defaultvalue = $defaultvalues->{location};
2645 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2646 my @authorised_values;
2649 # builds list, depending on authorised value...
2651 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2652 if ( ( C4::Context->preference("IndependentBranches") )
2653 && !C4::Context->IsSuperLibrarian() ) {
2654 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2655 $sth->execute( C4::Context->userenv->{branch} );
2656 push @authorised_values, ""
2657 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2658 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2659 push @authorised_values, $branchcode;
2660 $authorised_lib{$branchcode} = $branchname;
2663 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2665 push @authorised_values, ""
2666 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2667 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2668 push @authorised_values, $branchcode;
2669 $authorised_lib{$branchcode} = $branchname;
2673 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2674 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2675 $defaultvalue = $defaultvalues->{branchcode};
2679 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2680 my $itemtypes = Koha::ItemTypes->search_with_localization;
2681 push @authorised_values, ""
2682 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2683 while ( my $itemtype = $itemtypes->next ) {
2684 push @authorised_values, $itemtype->itemtype;
2685 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2687 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2688 $defaultvalue = $defaultvalues->{'itemtype'};
2692 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2693 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2695 my $class_sources = GetClassSources();
2696 my $default_source = C4::Context->preference("DefaultClassificationSource");
2698 foreach my $class_source (sort keys %$class_sources) {
2699 next unless $class_sources->{$class_source}->{'used'} or
2700 ($class_source eq $default_source);
2701 push @authorised_values, $class_source;
2702 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2705 $defaultvalue = $default_source;
2707 #---- "true" authorised value
2709 $authorised_values_sth->execute(
2710 $tagslib->{$tag}->{$subfield}->{authorised_value},
2711 $branch_limit ? $branch_limit : ()
2713 push @authorised_values, ""
2714 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2715 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2716 push @authorised_values, $value;
2717 $authorised_lib{$value} = $lib;
2720 $subfield_data{marc_value} = {
2722 values => \@authorised_values,
2723 default => "$defaultvalue",
2724 labels => \%authorised_lib,
2726 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2728 require Koha::FrameworkPlugin;
2729 my $plugin = Koha::FrameworkPlugin->new({
2730 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2733 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2734 $plugin->build( $pars );
2735 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2736 $defaultvalue = $field->subfield($subfield);
2738 if( !$plugin->errstr ) {
2739 #TODO Move html to template; see report 12176/13397
2740 my $tab= $plugin->noclick? '-1': '';
2741 my $class= $plugin->noclick? ' disabled': '';
2742 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2743 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2745 warn $plugin->errstr;
2746 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />); # supply default input form
2749 elsif ( $tag eq '' ) { # it's an hidden field
2750 $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2752 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2753 $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2755 elsif ( length($defaultvalue) > 100
2756 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2757 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2758 or (C4::Context->preference("marcflavour") eq "MARC21" and
2759 500 <= $tag && $tag < 600 )
2761 # oversize field (textarea)
2762 $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
2764 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2766 push( @loop_data, \%subfield_data );
2771 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2772 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2775 'itemtagfield' => $itemtagfield,
2776 'itemtagsubfield' => $itemtagsubfield,
2777 'itemnumber' => $itemnumber,
2778 'iteminformation' => \@loop_data
2782 sub ToggleNewStatus {
2783 my ( $params ) = @_;
2784 my @rules = @{ $params->{rules} };
2785 my $report_only = $params->{report_only};
2787 my $dbh = C4::Context->dbh;
2789 my @item_columns = map { "items.$_" } Koha::Items->columns;
2790 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2792 for my $rule ( @rules ) {
2793 my $age = $rule->{age};
2794 my $conditions = $rule->{conditions};
2795 my $substitutions = $rule->{substitutions};
2799 SELECT items.biblionumber, items.itemnumber
2801 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2804 for my $condition ( @$conditions ) {
2806 grep {/^$condition->{field}$/} @item_columns
2807 or grep {/^$condition->{field}$/} @biblioitem_columns
2809 if ( $condition->{value} =~ /\|/ ) {
2810 my @values = split /\|/, $condition->{value};
2811 $query .= qq| AND $condition->{field} IN (|
2812 . join( ',', ('?') x scalar @values )
2814 push @params, @values;
2816 $query .= qq| AND $condition->{field} = ?|;
2817 push @params, $condition->{value};
2821 if ( defined $age ) {
2822 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2825 my $sth = $dbh->prepare($query);
2826 $sth->execute( @params );
2827 while ( my $values = $sth->fetchrow_hashref ) {
2828 my $biblionumber = $values->{biblionumber};
2829 my $itemnumber = $values->{itemnumber};
2830 my $item = C4::Items::GetItem( $itemnumber );
2831 for my $substitution ( @$substitutions ) {
2832 next unless $substitution->{field};
2833 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
2834 unless $report_only;
2835 push @{ $report->{$itemnumber} }, $substitution;