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>.
23 use vars qw(@ISA @EXPORT);
42 get_hostitemnumbers_of
50 PrepareItemrecordDisplay
63 use List::MoreUtils qw(any);
65 use DateTime::Format::MySQL;
66 use Data::Dumper; # used as part of logging item record changes, not just for
67 # debugging; so please don't remove this
69 use Koha::AuthorisedValues;
70 use Koha::DateUtils qw(dt_from_string);
73 use Koha::Biblioitems;
76 use Koha::SearchEngine;
77 use Koha::SearchEngine::Search;
82 C4::Items - item management functions
86 This module contains an API for manipulating item
87 records in Koha, and is used by cataloguing, circulation,
88 acquisitions, and serials management.
90 # FIXME This POD is not up-to-date
91 A Koha item record is stored in two places: the
92 items table and embedded in a MARC tag in the XML
93 version of the associated bib record in C<biblioitems.marcxml>.
94 This is done to allow the item information to be readily
95 indexed (e.g., by Zebra), but means that each item
96 modification transaction must keep the items table
97 and the MARC XML in sync at all times.
99 The items table will be considered authoritative. In other
100 words, if there is ever a discrepancy between the items
101 table and the MARC XML, the items table should be considered
104 =head1 HISTORICAL NOTE
106 Most of the functions in C<C4::Items> were originally in
107 the C<C4::Biblio> module.
109 =head1 CORE EXPORTED FUNCTIONS
111 The following functions are meant for use by users
118 CartToShelf($itemnumber);
120 Set the current shelving location of the item record
121 to its stored permanent shelving location. This is
122 primarily used to indicate when an item whose current
123 location is a special processing ('PROC') or shelving cart
124 ('CART') location is back in the stacks.
129 my ( $itemnumber ) = @_;
131 unless ( $itemnumber ) {
132 croak "FAILED CartToShelf() - no itemnumber supplied";
135 my $item = Koha::Items->find($itemnumber);
136 if ( $item->location eq 'CART' ) {
137 ModItem({ location => $item->permanent_location}, undef, $itemnumber);
141 =head2 AddItemFromMarc
143 my ($biblionumber, $biblioitemnumber, $itemnumber)
144 = AddItemFromMarc($source_item_marc, $biblionumber);
146 Given a MARC::Record object containing an embedded item
147 record and a biblionumber, create a new item record.
151 sub AddItemFromMarc {
152 my ( $source_item_marc, $biblionumber ) = @_;
153 my $dbh = C4::Context->dbh;
155 # parse item hash from MARC
156 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
157 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
159 my $localitemmarc = MARC::Record->new;
160 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
163 my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
164 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
165 return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
167 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
168 $item_values->{biblionumber} = $biblionumber;
169 # FIXME RM my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
170 my $item = Koha::Item->new( $item_values ); # FIXME Handle $unlinked_item_subfields
171 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
176 my ($biblionumber, $biblioitemnumber, $itemnumber)
177 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
179 Given a hash containing item column names as keys,
180 create a new Koha item record.
182 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
183 do not need to be supplied for general use; they exist
184 simply to allow them to be picked up from AddItemFromMarc.
186 The final optional parameter, C<$unlinked_item_subfields>, contains
187 an arrayref containing subfields present in the original MARC
188 representation of the item (e.g., from the item editor) that are
189 not mapped to C<items> columns directly but should instead
190 be stored in C<items.more_subfields_xml> and included in
191 the biblio items tag for display and indexing.
197 my $biblionumber = shift;
199 my $dbh = @_ ? shift : C4::Context->dbh;
200 my $unlinked_item_subfields;
202 $unlinked_item_subfields = shift;
205 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
207 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
210 $item->{'itemnumber'} = $itemnumber;
212 C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
214 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
215 if C4::Context->preference("CataloguingLog");
217 _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
219 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
222 =head2 AddItemBatchFromMarc
224 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
225 $biblionumber, $biblioitemnumber, $frameworkcode);
227 Efficiently create item records from a MARC biblio record with
228 embedded item fields. This routine is suitable for batch jobs.
230 This API assumes that the bib record has already been
231 saved to the C<biblio> and C<biblioitems> tables. It does
232 not expect that C<biblio_metadata.metadata> is populated, but it
233 will do so via a call to ModBibiloMarc.
235 The goal of this API is to have a similar effect to using AddBiblio
236 and AddItems in succession, but without inefficient repeated
237 parsing of the MARC XML bib record.
239 This function returns an arrayref of new itemsnumbers and an arrayref of item
240 errors encountered during the processing. Each entry in the errors
241 list is a hashref containing the following keys:
247 Sequence number of original item tag in the MARC record.
251 Item barcode, provide to assist in the construction of
252 useful error messages.
256 Code representing the error condition. Can be 'duplicate_barcode',
257 'invalid_homebranch', or 'invalid_holdingbranch'.
259 =item error_information
261 Additional information appropriate to the error condition.
267 sub AddItemBatchFromMarc {
268 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
270 my @itemnumbers = ();
272 my $dbh = C4::Context->dbh;
274 # We modify the record, so lets work on a clone so we don't change the
276 $record = $record->clone();
277 # loop through the item tags and start creating items
278 my @bad_item_fields = ();
279 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
280 my $item_sequence_num = 0;
281 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
282 $item_sequence_num++;
283 # we take the item field and stick it into a new
284 # MARC record -- this is required so far because (FIXME)
285 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
286 # and there is no TransformMarcFieldToKoha
287 my $temp_item_marc = MARC::Record->new();
288 $temp_item_marc->append_fields($item_field);
290 # add biblionumber and biblioitemnumber
291 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
292 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
293 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
294 $item->{'biblionumber'} = $biblionumber;
295 $item->{'biblioitemnumber'} = $biblioitemnumber;
297 # check for duplicate barcode
298 my %item_errors = CheckItemPreSave($item);
300 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
301 push @bad_item_fields, $item_field;
305 _set_derived_columns_for_add($item);
306 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
307 warn $error if $error;
308 push @itemnumbers, $itemnumber; # FIXME not checking error
309 $item->{'itemnumber'} = $itemnumber;
311 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
313 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
314 $item_field->replace_with($new_item_marc->field($itemtag));
317 # remove any MARC item fields for rejected items
318 foreach my $item_field (@bad_item_fields) {
319 $record->delete_field($item_field);
322 # update the MARC biblio
323 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
325 return (\@itemnumbers, \@errors);
328 =head2 ModItemFromMarc
330 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
332 This function updates an item record based on a supplied
333 C<MARC::Record> object containing an embedded item field.
334 This API is meant for the use of C<additem.pl>; for
335 other purposes, C<ModItem> should be used.
337 This function uses the hash %default_values_for_mod_from_marc,
338 which contains default values for item fields to
339 apply when modifying an item. This is needed because
340 if an item field's value is cleared, TransformMarcToKoha
341 does not include the column in the
342 hash that's passed to ModItem, which without
343 use of this hash makes it impossible to clear
344 an item field's value. See bug 2466.
346 Note that only columns that can be directly
347 changed from the cataloging and serials
348 item editors are included in this hash.
354 sub _build_default_values_for_mod_marc {
355 # Has no framework parameter anymore, since Default is authoritative
356 # for Koha to MARC mappings.
358 my $cache = Koha::Caches->get_instance();
359 my $cache_key = "default_value_for_mod_marc-";
360 my $cached = $cache->get_from_cache($cache_key);
361 return $cached if $cached;
363 my $default_values = {
365 booksellerid => undef,
367 'items.cn_source' => undef,
368 coded_location_qualifier => undef,
372 holdingbranch => undef,
374 itemcallnumber => undef,
377 itemnotes_nonpublic => undef,
380 permanent_location => undef,
385 replacementprice => undef,
386 replacementpricedate => undef,
389 stocknumber => undef,
393 my %default_values_for_mod_from_marc;
394 while ( my ( $field, $default_value ) = each %$default_values ) {
395 my $kohafield = $field;
396 $kohafield =~ s|^([^\.]+)$|items.$1|;
397 $default_values_for_mod_from_marc{$field} = $default_value
398 if C4::Biblio::GetMarcFromKohaField( $kohafield );
401 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
402 return \%default_values_for_mod_from_marc;
405 sub ModItemFromMarc {
406 my $item_marc = shift;
407 my $biblionumber = shift;
408 my $itemnumber = shift;
410 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
411 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
413 my $localitemmarc = MARC::Record->new;
414 $localitemmarc->append_fields( $item_marc->field($itemtag) );
415 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
416 my $default_values = _build_default_values_for_mod_marc();
417 foreach my $item_field ( keys %$default_values ) {
418 $item->{$item_field} = $default_values->{$item_field}
419 unless exists $item->{$item_field};
421 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
423 ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
430 { column => $newvalue },
434 [ unlinked_item_subfields => $unlinked_item_subfields, ]
439 Change one or more columns in an item record.
441 The first argument is a hashref mapping from item column
442 names to the new values. The second and third arguments
443 are the biblionumber and itemnumber, respectively.
444 The fourth, optional parameter (additional_params) may contain the keys
445 unlinked_item_subfields and log_action.
447 C<$unlinked_item_subfields> contains an arrayref containing
448 subfields present in the original MARC
449 representation of the item (e.g., from the item editor) that are
450 not mapped to C<items> columns directly but should instead
451 be stored in C<items.more_subfields_xml> and included in
452 the biblio items tag for display and indexing.
454 If one of the changed columns is used to calculate
455 the derived value of a column such as C<items.cn_sort>,
456 this routine will perform the necessary calculation
459 If log_action is set to false, the action will not be logged.
460 If log_action is true or undefined, the action will be logged.
465 my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
466 my $log_action = $additional_params->{log_action} // 1;
467 my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
469 return unless %$item;
470 $item->{'itemnumber'} = $itemnumber or return;
472 # if $biblionumber is undefined, get it from the current item
473 unless (defined $biblionumber) {
474 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
477 if ($unlinked_item_subfields) {
478 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
481 my @fields = qw( itemlost withdrawn damaged );
483 # Only retrieve the item if we need to set an "on" date field
484 if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
485 my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
486 for my $field (@fields) {
487 if ( defined( $item->{$field} )
488 and not $pre_mod_item->$field
489 and $item->{$field} )
491 $item->{ $field . '_on' } =
492 DateTime::Format::MySQL->format_datetime( dt_from_string() );
497 # If the field is defined but empty, we are removing and,
498 # and thus need to clear out the 'on' field as well
499 for my $field (@fields) {
500 if ( defined( $item->{$field} ) && !$item->{$field} ) {
501 $item->{ $field . '_on' } = undef;
506 _set_derived_columns_for_mod($item);
507 _do_column_fixes_for_mod($item);
510 # attempt to change itemnumber
511 # attempt to change biblionumber (if we want
512 # an API to relink an item to a different bib,
513 # it should be a separate function)
516 _koha_modify_item($item);
518 # request that bib be reindexed so that searching on current
519 # item status is possible
520 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
522 _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
524 logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
525 if $log_action && C4::Context->preference("CataloguingLog");
528 =head2 ModItemTransfer
530 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
532 Marks an item as being transferred from one branch to another and records the trigger.
536 sub ModItemTransfer {
537 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
539 my $dbh = C4::Context->dbh;
540 my $item = Koha::Items->find( $itemnumber );
542 # Remove the 'shelving cart' location status if it is being used.
543 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
545 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
547 #new entry in branchtransfers....
548 my $sth = $dbh->prepare(
549 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
550 VALUES (?, ?, NOW(), ?, ?)");
551 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
553 ModItem({ holdingbranch => $frombranch }, undef, $itemnumber, { log_action => 0 });
554 ModDateLastSeen($itemnumber);
558 =head2 ModDateLastSeen
560 ModDateLastSeen( $itemnumber, $leave_item_lost );
562 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
563 C<$itemnumber> is the item number
564 C<$leave_item_lost> determines if a lost item will be found or remain lost
568 sub ModDateLastSeen {
569 my ( $itemnumber, $leave_item_lost ) = @_;
571 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
574 $params->{datelastseen} = $today;
575 $params->{itemlost} = 0 unless $leave_item_lost;
577 ModItem( $params, undef, $itemnumber, { log_action => 0 } );
582 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
584 Exported function (core API) for deleting an item record in Koha.
591 my $itemnumber = $params->{itemnumber};
592 my $biblionumber = $params->{biblionumber};
594 unless ($biblionumber) {
595 my $item = Koha::Items->find( $itemnumber );
596 $biblionumber = $item ? $item->biblio->biblionumber : undef;
599 # If there is no biblionumber for the given itemnumber, there is nothing to delete
600 return 0 unless $biblionumber;
602 # FIXME check the item has no current issues
603 my $deleted = _koha_delete_item( $itemnumber );
605 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
607 _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
609 #search item field code
610 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
614 =head2 CheckItemPreSave
616 my $item_ref = TransformMarcToKoha($marc, 'items');
618 my %errors = CheckItemPreSave($item_ref);
619 if (exists $errors{'duplicate_barcode'}) {
620 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
621 } elsif (exists $errors{'invalid_homebranch'}) {
622 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
623 } elsif (exists $errors{'invalid_holdingbranch'}) {
624 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
629 Given a hashref containing item fields, determine if it can be
630 inserted or updated in the database. Specifically, checks for
631 database integrity issues, and returns a hash containing any
632 of the following keys, if applicable.
636 =item duplicate_barcode
638 Barcode, if it duplicates one already found in the database.
640 =item invalid_homebranch
642 Home branch, if not defined in branches table.
644 =item invalid_holdingbranch
646 Holding branch, if not defined in branches table.
650 This function does NOT implement any policy-related checks,
651 e.g., whether current operator is allowed to save an
652 item that has a given branch code.
656 sub CheckItemPreSave {
657 my $item_ref = shift;
661 # check for duplicate barcode
662 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
663 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
664 if ($existing_item) {
665 if (!exists $item_ref->{'itemnumber'} # new item
666 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
667 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
672 # check for valid home branch
673 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
674 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
675 unless (defined $home_library) {
676 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
680 # check for valid holding branch
681 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
682 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
683 unless (defined $holding_library) {
684 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
692 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
694 The following functions provide various ways of
695 getting an item record, a set of item records, or
696 lists of authorized values for certain item fields.
700 =head2 GetItemsForInventory
702 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
703 minlocation => $minlocation,
704 maxlocation => $maxlocation,
705 location => $location,
706 itemtype => $itemtype,
707 ignoreissued => $ignoreissued,
708 datelastseen => $datelastseen,
709 branchcode => $branchcode,
713 statushash => $statushash,
716 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
718 The sub returns a reference to a list of hashes, each containing
719 itemnumber, author, title, barcode, item callnumber, and date last
720 seen. It is ordered by callnumber then title.
722 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
723 the datelastseen can be used to specify that you want to see items not seen since a past date only.
724 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
725 $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.
727 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
731 sub GetItemsForInventory {
732 my ( $parameters ) = @_;
733 my $minlocation = $parameters->{'minlocation'} // '';
734 my $maxlocation = $parameters->{'maxlocation'} // '';
735 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
736 my $location = $parameters->{'location'} // '';
737 my $itemtype = $parameters->{'itemtype'} // '';
738 my $ignoreissued = $parameters->{'ignoreissued'} // '';
739 my $datelastseen = $parameters->{'datelastseen'} // '';
740 my $branchcode = $parameters->{'branchcode'} // '';
741 my $branch = $parameters->{'branch'} // '';
742 my $offset = $parameters->{'offset'} // '';
743 my $size = $parameters->{'size'} // '';
744 my $statushash = $parameters->{'statushash'} // '';
745 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
747 my $dbh = C4::Context->dbh;
748 my ( @bind_params, @where_strings );
750 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
751 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
753 my $select_columns = q{
754 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
756 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
759 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
760 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
763 for my $authvfield (keys %$statushash){
764 if ( scalar @{$statushash->{$authvfield}} > 0 ){
765 my $joinedvals = join ',', @{$statushash->{$authvfield}};
766 push @where_strings, "$authvfield in (" . $joinedvals . ")";
772 push @where_strings, 'items.cn_sort >= ?';
773 push @bind_params, $min_cnsort;
777 push @where_strings, 'items.cn_sort <= ?';
778 push @bind_params, $max_cnsort;
782 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
783 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
784 push @bind_params, $datelastseen;
788 push @where_strings, 'items.location = ?';
789 push @bind_params, $location;
793 if($branch eq "homebranch"){
794 push @where_strings, 'items.homebranch = ?';
796 push @where_strings, 'items.holdingbranch = ?';
798 push @bind_params, $branchcode;
802 push @where_strings, 'biblioitems.itemtype = ?';
803 push @bind_params, $itemtype;
806 if ( $ignoreissued) {
807 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
808 push @where_strings, 'issues.date_due IS NULL';
811 if ( $ignore_waiting_holds ) {
812 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
813 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
816 if ( @where_strings ) {
818 $query .= join ' AND ', @where_strings;
820 my $count_query = $select_count . $query;
821 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
822 $query .= " LIMIT $offset, $size" if ($offset and $size);
823 $query = $select_columns . $query;
824 my $sth = $dbh->prepare($query);
825 $sth->execute( @bind_params );
828 my $tmpresults = $sth->fetchall_arrayref({});
829 $sth = $dbh->prepare( $count_query );
830 $sth->execute( @bind_params );
831 my ($iTotalRecords) = $sth->fetchrow_array();
833 my @avs = Koha::AuthorisedValues->search(
834 { 'marc_subfield_structures.kohafield' => { '>' => '' },
835 'me.authorised_value' => { '>' => '' },
837 { join => { category => 'marc_subfield_structures' },
838 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
839 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
840 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
844 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
846 foreach my $row (@$tmpresults) {
849 foreach (keys %$row) {
852 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
855 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
861 return (\@results, $iTotalRecords);
866 @results = GetItemsInfo($biblionumber);
868 Returns information about items with the given biblionumber.
870 C<GetItemsInfo> returns a list of references-to-hash. Each element
871 contains a number of keys. Most of them are attributes from the
872 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
873 Koha database. Other keys include:
877 =item C<$data-E<gt>{branchname}>
879 The name (not the code) of the branch to which the book belongs.
881 =item C<$data-E<gt>{datelastseen}>
883 This is simply C<items.datelastseen>, except that while the date is
884 stored in YYYY-MM-DD format in the database, here it is converted to
885 DD/MM/YYYY format. A NULL date is returned as C<//>.
887 =item C<$data-E<gt>{datedue}>
889 =item C<$data-E<gt>{class}>
891 This is the concatenation of C<biblioitems.classification>, the book's
892 Dewey code, and C<biblioitems.subclass>.
894 =item C<$data-E<gt>{ocount}>
896 I think this is the number of copies of the book available.
898 =item C<$data-E<gt>{order}>
900 If this is set, it is set to C<One Order>.
907 my ( $biblionumber ) = @_;
908 my $dbh = C4::Context->dbh;
909 require C4::Languages;
910 my $language = C4::Languages::getlanguage();
916 biblioitems.itemtype,
919 biblioitems.publicationyear,
920 biblioitems.publishercode,
921 biblioitems.volumedate,
922 biblioitems.volumedesc,
925 items.notforloan as itemnotforloan,
926 issues.borrowernumber,
927 issues.date_due as datedue,
928 issues.onsite_checkout,
929 borrowers.cardnumber,
932 borrowers.branchcode as bcode,
934 serial.publisheddate,
935 itemtypes.description,
936 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
937 itemtypes.notforloan as notforloan_per_itemtype,
941 holding.opac_info as holding_branch_opac_info,
942 home.opac_info as home_branch_opac_info,
943 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
945 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
946 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
947 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
948 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
949 LEFT JOIN issues USING (itemnumber)
950 LEFT JOIN borrowers USING (borrowernumber)
951 LEFT JOIN serialitems USING (itemnumber)
952 LEFT JOIN serial USING (serialid)
953 LEFT JOIN itemtypes ON itemtypes.itemtype = "
954 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
956 LEFT JOIN tmp_holdsqueue USING (itemnumber)
957 LEFT JOIN localization ON itemtypes.itemtype = localization.code
958 AND localization.entity = 'itemtypes'
959 AND localization.lang = ?
962 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
963 my $sth = $dbh->prepare($query);
964 $sth->execute($language, $biblionumber);
969 my $userenv = C4::Context->userenv;
970 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
971 while ( my $data = $sth->fetchrow_hashref ) {
972 if ( $data->{borrowernumber} && $want_not_same_branch) {
973 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
976 $serial ||= $data->{'serial'};
979 # get notforloan complete status if applicable
980 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
981 $data->{notforloanvalue} = $descriptions->{lib} // '';
982 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
984 # get restricted status and description if applicable
985 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
986 $data->{restrictedvalue} = $descriptions->{lib} // '';
987 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
989 # my stack procedures
990 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
991 $data->{stack} = $descriptions->{lib} // '';
993 # Find the last 3 people who borrowed this item.
994 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
996 AND old_issues.borrowernumber = borrowers.borrowernumber
997 ORDER BY returndate DESC
999 $sth2->execute($data->{'itemnumber'});
1001 while (my $data2 = $sth2->fetchrow_hashref()) {
1002 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1003 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1004 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1008 $results[$i] = $data;
1013 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1017 =head2 GetItemsLocationInfo
1019 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1021 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1023 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1027 =item C<$data-E<gt>{homebranch}>
1029 Branch Name of the item's homebranch
1031 =item C<$data-E<gt>{holdingbranch}>
1033 Branch Name of the item's holdingbranch
1035 =item C<$data-E<gt>{location}>
1037 Item's shelving location code
1039 =item C<$data-E<gt>{location_intranet}>
1041 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1043 =item C<$data-E<gt>{location_opac}>
1045 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1048 =item C<$data-E<gt>{itemcallnumber}>
1050 Item's itemcallnumber
1052 =item C<$data-E<gt>{cn_sort}>
1054 Item's call number normalized for sorting
1060 sub GetItemsLocationInfo {
1061 my $biblionumber = shift;
1064 my $dbh = C4::Context->dbh;
1065 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1066 location, itemcallnumber, cn_sort
1067 FROM items, branches as a, branches as b
1068 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1069 AND biblionumber = ?
1070 ORDER BY cn_sort ASC";
1071 my $sth = $dbh->prepare($query);
1072 $sth->execute($biblionumber);
1074 while ( my $data = $sth->fetchrow_hashref ) {
1075 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1076 $av = $av->count ? $av->next : undef;
1077 $data->{location_intranet} = $av ? $av->lib : '';
1078 $data->{location_opac} = $av ? $av->opac_description : '';
1079 push @results, $data;
1084 =head2 GetHostItemsInfo
1086 $hostiteminfo = GetHostItemsInfo($hostfield);
1087 Returns the iteminfo for items linked to records via a host field
1091 sub GetHostItemsInfo {
1093 my @returnitemsInfo;
1095 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1096 return @returnitemsInfo;
1100 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1101 C4::Context->preference('marcflavour') eq 'NORMARC') {
1102 @fields = $record->field('773');
1103 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1104 @fields = $record->field('461');
1107 foreach my $hostfield ( @fields ) {
1108 my $hostbiblionumber = $hostfield->subfield("0");
1109 my $linkeditemnumber = $hostfield->subfield("9");
1110 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1111 foreach my $hostitemInfo (@hostitemInfos) {
1112 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1113 push @returnitemsInfo, $hostitemInfo;
1118 return @returnitemsInfo;
1121 =head2 get_hostitemnumbers_of
1123 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1125 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1127 Return a reference on a hash where key is a biblionumber and values are
1128 references on array of itemnumbers.
1133 sub get_hostitemnumbers_of {
1134 my ($biblionumber) = @_;
1136 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1140 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1141 return unless $marcrecord;
1143 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1145 my $marcflavor = C4::Context->preference('marcflavour');
1146 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1151 elsif ( $marcflavor eq 'UNIMARC' ) {
1157 foreach my $hostfield ( $marcrecord->field($tag) ) {
1158 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1159 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1160 my $linkeditemnumber = $hostfield->subfield($item_s);
1161 if ( ! $linkeditemnumber ) {
1162 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1165 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1166 push @returnhostitemnumbers, $linkeditemnumber
1170 return @returnhostitemnumbers;
1173 =head2 GetHiddenItemnumbers
1175 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1177 Given a list of items it checks which should be hidden from the OPAC given
1178 the current configuration. Returns a list of itemnumbers corresponding to
1179 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1184 sub GetHiddenItemnumbers {
1186 my $items = $params->{items};
1187 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1188 foreach my $except (split(/\|/, $exceptions)){
1189 if ($params->{'borcat'} eq $except){
1190 return; # we don't hide anything for this borrower category
1196 my $yaml = C4::Context->preference('OpacHiddenItems');
1197 return () if (! $yaml =~ /\S/ );
1198 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1201 $hidingrules = YAML::Load($yaml);
1204 warn "Unable to parse OpacHiddenItems syspref : $@";
1207 my $dbh = C4::Context->dbh;
1210 foreach my $item (@$items) {
1212 # We check each rule
1213 foreach my $field (keys %$hidingrules) {
1215 if (exists $item->{$field}) {
1216 $val = $item->{$field};
1219 my $query = "SELECT $field from items where itemnumber = ?";
1220 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1222 $val = '' unless defined $val;
1224 # If the results matches the values in the yaml file
1225 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1227 # We add the itemnumber to the list
1228 push @resultitems, $item->{'itemnumber'};
1230 # If at least one rule matched for an item, no need to test the others
1235 return @resultitems;
1238 =head1 LIMITED USE FUNCTIONS
1240 The following functions, while part of the public API,
1241 are not exported. This is generally because they are
1242 meant to be used by only one script for a specific
1243 purpose, and should not be used in any other context
1244 without careful thought.
1250 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1252 Returns MARC::Record of the item passed in parameter.
1253 This function is meant for use only in C<cataloguing/additem.pl>,
1254 where it is needed to support that script's MARC-like
1260 my ( $biblionumber, $itemnumber ) = @_;
1262 # GetMarcItem has been revised so that it does the following:
1263 # 1. Gets the item information from the items table.
1264 # 2. Converts it to a MARC field for storage in the bib record.
1266 # The previous behavior was:
1267 # 1. Get the bib record.
1268 # 2. Return the MARC tag corresponding to the item record.
1270 # The difference is that one treats the items row as authoritative,
1271 # while the other treats the MARC representation as authoritative
1272 # under certain circumstances.
1274 my $item = Koha::Items->find($itemnumber) or return;
1276 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1277 # Also, don't emit a subfield if the underlying field is blank.
1279 return Item2Marc($item->unblessed, $biblionumber);
1283 my ($itemrecord,$biblionumber)=@_;
1286 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1287 } keys %{ $itemrecord }
1289 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1290 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1291 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1292 "items.itemnumber", $framework,
1295 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1296 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1297 foreach my $field ($itemmarc->field($itemtag)){
1298 $field->add_subfields(@$unlinked_item_subfields);
1304 =head1 PRIVATE FUNCTIONS AND VARIABLES
1306 The following functions are not meant to be called
1307 directly, but are documented in order to explain
1308 the inner workings of C<C4::Items>.
1312 =head2 %derived_columns
1314 This hash keeps track of item columns that
1315 are strictly derived from other columns in
1316 the item record and are not meant to be set
1319 Each key in the hash should be the name of a
1320 column (as named by TransformMarcToKoha). Each
1321 value should be hashref whose keys are the
1322 columns on which the derived column depends. The
1323 hashref should also contain a 'BUILDER' key
1324 that is a reference to a sub that calculates
1329 my %derived_columns = (
1330 'items.cn_sort' => {
1331 'itemcallnumber' => 1,
1332 'items.cn_source' => 1,
1333 'BUILDER' => \&_calc_items_cn_sort,
1337 =head2 _set_derived_columns_for_add
1339 _set_derived_column_for_add($item);
1341 Given an item hash representing a new item to be added,
1342 calculate any derived columns. Currently the only
1343 such column is C<items.cn_sort>.
1347 sub _set_derived_columns_for_add {
1350 foreach my $column (keys %derived_columns) {
1351 my $builder = $derived_columns{$column}->{'BUILDER'};
1352 my $source_values = {};
1353 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1354 next if $source_column eq 'BUILDER';
1355 $source_values->{$source_column} = $item->{$source_column};
1357 $builder->($item, $source_values);
1361 =head2 _set_derived_columns_for_mod
1363 _set_derived_column_for_mod($item);
1365 Given an item hash representing a new item to be modified.
1366 calculate any derived columns. Currently the only
1367 such column is C<items.cn_sort>.
1369 This routine differs from C<_set_derived_columns_for_add>
1370 in that it needs to handle partial item records. In other
1371 words, the caller of C<ModItem> may have supplied only one
1372 or two columns to be changed, so this function needs to
1373 determine whether any of the columns to be changed affect
1374 any of the derived columns. Also, if a derived column
1375 depends on more than one column, but the caller is not
1376 changing all of then, this routine retrieves the unchanged
1377 values from the database in order to ensure a correct
1382 sub _set_derived_columns_for_mod {
1385 foreach my $column (keys %derived_columns) {
1386 my $builder = $derived_columns{$column}->{'BUILDER'};
1387 my $source_values = {};
1388 my %missing_sources = ();
1389 my $must_recalc = 0;
1390 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1391 next if $source_column eq 'BUILDER';
1392 if (exists $item->{$source_column}) {
1394 $source_values->{$source_column} = $item->{$source_column};
1396 $missing_sources{$source_column} = 1;
1400 foreach my $source_column (keys %missing_sources) {
1401 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1403 $builder->($item, $source_values);
1408 =head2 _do_column_fixes_for_mod
1410 _do_column_fixes_for_mod($item);
1412 Given an item hashref containing one or more
1413 columns to modify, fix up certain values.
1414 Specifically, set to 0 any passed value
1415 of C<notforloan>, C<damaged>, C<itemlost>, or
1416 C<withdrawn> that is either undefined or
1417 contains the empty string.
1421 sub _do_column_fixes_for_mod {
1424 if (exists $item->{'notforloan'} and
1425 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1426 $item->{'notforloan'} = 0;
1428 if (exists $item->{'damaged'} and
1429 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1430 $item->{'damaged'} = 0;
1432 if (exists $item->{'itemlost'} and
1433 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1434 $item->{'itemlost'} = 0;
1436 if (exists $item->{'withdrawn'} and
1437 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1438 $item->{'withdrawn'} = 0;
1441 exists $item->{location}
1442 and ( !defined $item->{location}
1443 || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
1444 and not $item->{permanent_location}
1447 $item->{'permanent_location'} = $item->{'location'};
1449 if (exists $item->{'timestamp'}) {
1450 delete $item->{'timestamp'};
1454 =head2 _get_single_item_column
1456 _get_single_item_column($column, $itemnumber);
1458 Retrieves the value of a single column from an C<items>
1459 row specified by C<$itemnumber>.
1463 sub _get_single_item_column {
1465 my $itemnumber = shift;
1467 my $dbh = C4::Context->dbh;
1468 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1469 $sth->execute($itemnumber);
1470 my ($value) = $sth->fetchrow();
1474 =head2 _calc_items_cn_sort
1476 _calc_items_cn_sort($item, $source_values);
1478 Helper routine to calculate C<items.cn_sort>.
1482 sub _calc_items_cn_sort {
1484 my $source_values = shift;
1486 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1489 =head2 _koha_new_item
1491 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1493 Perform the actual insert into the C<items> table.
1497 sub _koha_new_item {
1498 my ( $item, $barcode ) = @_;
1499 my $dbh=C4::Context->dbh;
1501 $item->{permanent_location} //= $item->{location};
1502 _mod_item_dates( $item );
1504 "INSERT INTO items SET
1506 biblioitemnumber = ?,
1508 dateaccessioned = ?,
1512 replacementprice = ?,
1513 replacementpricedate = ?,
1514 datelastborrowed = ?,
1522 coded_location_qualifier = ?,
1525 itemnotes_nonpublic = ?,
1528 permanent_location = ?,
1540 more_subfields_xml = ?,
1545 my $sth = $dbh->prepare($query);
1546 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1548 $item->{'biblionumber'},
1549 $item->{'biblioitemnumber'},
1551 $item->{'dateaccessioned'},
1552 $item->{'booksellerid'},
1553 $item->{'homebranch'},
1555 $item->{'replacementprice'},
1556 $item->{'replacementpricedate'} || $today,
1557 $item->{datelastborrowed},
1558 $item->{datelastseen} || $today,
1560 $item->{'notforloan'},
1562 $item->{'itemlost'},
1563 $item->{'withdrawn'},
1564 $item->{'itemcallnumber'},
1565 $item->{'coded_location_qualifier'},
1566 $item->{'restricted'},
1567 $item->{'itemnotes'},
1568 $item->{'itemnotes_nonpublic'},
1569 $item->{'holdingbranch'},
1570 $item->{'location'},
1571 $item->{'permanent_location'},
1574 $item->{'renewals'},
1575 $item->{'reserves'},
1576 $item->{'items.cn_source'},
1577 $item->{'items.cn_sort'},
1580 $item->{'materials'},
1582 $item->{'enumchron'},
1583 $item->{'more_subfields_xml'},
1584 $item->{'copynumber'},
1585 $item->{'stocknumber'},
1586 $item->{'new_status'},
1590 if ( defined $sth->errstr ) {
1591 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1594 $itemnumber = $dbh->{'mysql_insertid'};
1597 return ( $itemnumber, $error );
1600 =head2 MoveItemFromBiblio
1602 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1604 Moves an item from a biblio to another
1606 Returns undef if the move failed or the biblionumber of the destination record otherwise
1610 sub MoveItemFromBiblio {
1611 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1612 my $dbh = C4::Context->dbh;
1613 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1614 SELECT biblioitemnumber
1616 WHERE biblionumber = ?
1617 |, undef, $tobiblio );
1618 my $return = $dbh->do(q|
1620 SET biblioitemnumber = ?,
1622 WHERE itemnumber = ?
1623 AND biblionumber = ?
1624 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1626 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1627 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1628 # Checking if the item we want to move is in an order
1629 require C4::Acquisition;
1630 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1632 # Replacing the biblionumber within the order if necessary
1633 $order->{'biblionumber'} = $tobiblio;
1634 C4::Acquisition::ModOrder($order);
1637 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1638 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1641 SET biblionumber = ?
1642 WHERE itemnumber = ?
1643 |, undef, $tobiblio, $itemnumber );
1650 =head2 ItemSafeToDelete
1652 ItemSafeToDelete( $biblionumber, $itemnumber);
1654 Exported function (core API) for checking whether an item record is safe to delete.
1656 returns 1 if the item is safe to delete,
1658 "book_on_loan" if the item is checked out,
1660 "not_same_branch" if the item is blocked by independent branches,
1662 "book_reserved" if the there are holds aganst the item, or
1664 "linked_analytics" if the item has linked analytic records.
1668 sub ItemSafeToDelete {
1669 my ( $biblionumber, $itemnumber ) = @_;
1671 my $dbh = C4::Context->dbh;
1675 my $countanalytics = GetAnalyticsCount($itemnumber);
1677 my $item = Koha::Items->find($itemnumber) or return;
1679 if ($item->checkout) {
1680 $status = "book_on_loan";
1682 elsif ( defined C4::Context->userenv
1683 and !C4::Context->IsSuperLibrarian()
1684 and C4::Context->preference("IndependentBranches")
1685 and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1687 $status = "not_same_branch";
1690 # check it doesn't have a waiting reserve
1691 my $sth = $dbh->prepare(
1693 SELECT COUNT(*) FROM reserves
1694 WHERE (found = 'W' OR found = 'T')
1698 $sth->execute($itemnumber);
1699 my ($reserve) = $sth->fetchrow;
1701 $status = "book_reserved";
1703 elsif ( $countanalytics > 0 ) {
1704 $status = "linked_analytics";
1715 DelItemCheck( $biblionumber, $itemnumber);
1717 Exported function (core API) for deleting an item record in Koha if there no current issue.
1719 DelItemCheck wraps ItemSafeToDelete around DelItem.
1724 my ( $biblionumber, $itemnumber ) = @_;
1725 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1727 if ( $status == 1 ) {
1730 biblionumber => $biblionumber,
1731 itemnumber => $itemnumber
1738 =head2 _koha_modify_item
1740 my ($itemnumber,$error) =_koha_modify_item( $item );
1742 Perform the actual update of the C<items> row. Note that this
1743 routine accepts a hashref specifying the columns to update.
1747 sub _koha_modify_item {
1749 my $dbh=C4::Context->dbh;
1752 my $query = "UPDATE items SET ";
1754 _mod_item_dates( $item );
1755 for my $key ( keys %$item ) {
1756 next if ( $key eq 'itemnumber' );
1758 push @bind, $item->{$key};
1761 $query .= " WHERE itemnumber=?";
1762 push @bind, $item->{'itemnumber'};
1763 my $sth = $dbh->prepare($query);
1764 $sth->execute(@bind);
1766 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1769 return ($item->{'itemnumber'},$error);
1772 sub _mod_item_dates { # date formatting for date fields in item hash
1774 return if !$item || ref($item) ne 'HASH';
1777 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1779 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1780 # NOTE: We do not (yet) have items fields ending with datetime
1781 # Fields with _on$ have been handled already
1783 foreach my $key ( @keys ) {
1784 next if !defined $item->{$key}; # skip undefs
1785 my $dt = eval { dt_from_string( $item->{$key} ) };
1786 # eval: dt_from_string will die on us if we pass illegal dates
1789 if( defined $dt && ref($dt) eq 'DateTime' ) {
1790 if( $key =~ /datetime/ ) {
1791 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1793 $newstr = DateTime::Format::MySQL->format_date($dt);
1796 $item->{$key} = $newstr; # might be undef to clear garbage
1800 =head2 _koha_delete_item
1802 _koha_delete_item( $itemnum );
1804 Internal function to delete an item record from the koha tables
1808 sub _koha_delete_item {
1809 my ( $itemnum ) = @_;
1811 my $dbh = C4::Context->dbh;
1812 # save the deleted item to deleteditems table
1813 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1814 $sth->execute($itemnum);
1815 my $data = $sth->fetchrow_hashref();
1817 # There is no item to delete
1818 return 0 unless $data;
1820 my $query = "INSERT INTO deleteditems SET ";
1822 foreach my $key ( keys %$data ) {
1823 next if ( $key eq 'timestamp' ); # timestamp will be set by db
1824 $query .= "$key = ?,";
1825 push( @bind, $data->{$key} );
1828 $sth = $dbh->prepare($query);
1829 $sth->execute(@bind);
1831 # delete from items table
1832 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1833 my $deleted = $sth->execute($itemnum);
1834 return ( $deleted == 1 ) ? 1 : 0;
1837 =head2 _marc_from_item_hash
1839 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1841 Given an item hash representing a complete item record,
1842 create a C<MARC::Record> object containing an embedded
1843 tag representing that item.
1845 The third, optional parameter C<$unlinked_item_subfields> is
1846 an arrayref of subfields (not mapped to C<items> fields per the
1847 framework) to be added to the MARC representation
1852 sub _marc_from_item_hash {
1854 my $frameworkcode = shift;
1855 my $unlinked_item_subfields;
1857 $unlinked_item_subfields = shift;
1860 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1861 # Also, don't emit a subfield if the underlying field is blank.
1862 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1863 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1864 : () } keys %{ $item } };
1866 my $item_marc = MARC::Record->new();
1867 foreach my $item_field ( keys %{$mungeditem} ) {
1868 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1869 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1870 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1871 foreach my $value (@values){
1872 if ( my $field = $item_marc->field($tag) ) {
1873 $field->add_subfields( $subfield => $value );
1875 my $add_subfields = [];
1876 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1877 $add_subfields = $unlinked_item_subfields;
1879 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1887 =head2 _repack_item_errors
1889 Add an error message hash generated by C<CheckItemPreSave>
1890 to a list of errors.
1894 sub _repack_item_errors {
1895 my $item_sequence_num = shift;
1896 my $item_ref = shift;
1897 my $error_ref = shift;
1899 my @repacked_errors = ();
1901 foreach my $error_code (sort keys %{ $error_ref }) {
1902 my $repacked_error = {};
1903 $repacked_error->{'item_sequence'} = $item_sequence_num;
1904 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1905 $repacked_error->{'error_code'} = $error_code;
1906 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1907 push @repacked_errors, $repacked_error;
1910 return @repacked_errors;
1913 =head2 _get_unlinked_item_subfields
1915 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1919 sub _get_unlinked_item_subfields {
1920 my $original_item_marc = shift;
1921 my $frameworkcode = shift;
1923 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1925 # assume that this record has only one field, and that that
1926 # field contains only the item information
1928 my @fields = $original_item_marc->fields();
1929 if ($#fields > -1) {
1930 my $field = $fields[0];
1931 my $tag = $field->tag();
1932 foreach my $subfield ($field->subfields()) {
1933 if (defined $subfield->[1] and
1934 $subfield->[1] ne '' and
1935 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1936 push @$subfields, $subfield->[0] => $subfield->[1];
1943 =head2 _get_unlinked_subfields_xml
1945 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1949 sub _get_unlinked_subfields_xml {
1950 my $unlinked_item_subfields = shift;
1953 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1954 my $marc = MARC::Record->new();
1955 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1956 # used in the framework
1957 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1958 $marc->encoding("UTF-8");
1959 $xml = $marc->as_xml("USMARC");
1965 =head2 _parse_unlinked_item_subfields_from_xml
1967 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1971 sub _parse_unlinked_item_subfields_from_xml {
1973 require C4::Charset;
1974 return unless defined $xml and $xml ne "";
1975 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1976 my $unlinked_subfields = [];
1977 my @fields = $marc->fields();
1978 if ($#fields > -1) {
1979 foreach my $subfield ($fields[0]->subfields()) {
1980 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1983 return $unlinked_subfields;
1986 =head2 GetAnalyticsCount
1988 $count= &GetAnalyticsCount($itemnumber)
1990 counts Usage of itemnumber in Analytical bibliorecords.
1994 sub GetAnalyticsCount {
1995 my ($itemnumber) = @_;
1997 ### ZOOM search here
1999 $query= "hi=".$itemnumber;
2000 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2001 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2005 sub _SearchItems_build_where_fragment {
2008 my $dbh = C4::Context->dbh;
2011 if (exists($filter->{conjunction})) {
2012 my (@where_strs, @where_args);
2013 foreach my $f (@{ $filter->{filters} }) {
2014 my $fragment = _SearchItems_build_where_fragment($f);
2016 push @where_strs, $fragment->{str};
2017 push @where_args, @{ $fragment->{args} };
2022 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2025 args => \@where_args,
2029 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2030 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2031 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2032 my @operators = qw(= != > < >= <= like);
2033 my $field = $filter->{field} // q{};
2034 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2035 my $op = $filter->{operator};
2036 my $query = $filter->{query};
2038 if (!$op or (0 == grep { $_ eq $op } @operators)) {
2039 $op = '='; # default operator
2043 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2045 my $marcsubfield = $2;
2046 my ($kohafield) = $dbh->selectrow_array(q|
2047 SELECT kohafield FROM marc_subfield_structure
2048 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2049 |, undef, $marcfield, $marcsubfield);
2052 $column = $kohafield;
2054 # MARC field is not linked to a DB field so we need to use
2055 # ExtractValue on marcxml from biblio_metadata or
2056 # items.more_subfields_xml, depending on the MARC field.
2059 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2060 if ($marcfield eq $itemfield) {
2061 $sqlfield = 'more_subfields_xml';
2062 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2064 $sqlfield = 'metadata'; # From biblio_metadata
2065 if ($marcfield < 10) {
2066 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2068 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2071 $column = "ExtractValue($sqlfield, '$xpath')";
2073 } elsif ($field eq 'issues') {
2074 # Consider NULL as 0 for issues count
2075 $column = 'COALESCE(issues,0)';
2080 if (ref $query eq 'ARRAY') {
2083 } elsif ($op eq '!=') {
2087 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2092 str => "$column $op ?",
2099 return $where_fragment;
2104 my ($items, $total) = SearchItems($filter, $params);
2106 Perform a search among items
2108 $filter is a reference to a hash which can be a filter, or a combination of filters.
2110 A filter has the following keys:
2114 =item * field: the name of a SQL column in table items
2116 =item * query: the value to search in this column
2118 =item * operator: comparison operator. Can be one of = != > < >= <= like
2122 A combination of filters hash the following keys:
2126 =item * conjunction: 'AND' or 'OR'
2128 =item * filters: array ref of filters
2132 $params is a reference to a hash that can contain the following parameters:
2136 =item * rows: Number of items to return. 0 returns everything (default: 0)
2138 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2141 =item * sortby: A SQL column name in items table to sort on
2143 =item * sortorder: 'ASC' or 'DESC'
2150 my ($filter, $params) = @_;
2154 return unless ref $filter eq 'HASH';
2155 return unless ref $params eq 'HASH';
2157 # Default parameters
2158 $params->{rows} ||= 0;
2159 $params->{page} ||= 1;
2160 $params->{sortby} ||= 'itemnumber';
2161 $params->{sortorder} ||= 'ASC';
2163 my ($where_str, @where_args);
2164 my $where_fragment = _SearchItems_build_where_fragment($filter);
2165 if ($where_fragment) {
2166 $where_str = $where_fragment->{str};
2167 @where_args = @{ $where_fragment->{args} };
2170 my $dbh = C4::Context->dbh;
2172 SELECT SQL_CALC_FOUND_ROWS items.*
2174 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2175 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2176 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2179 if (defined $where_str and $where_str ne '') {
2180 $query .= qq{ AND $where_str };
2183 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2184 push @where_args, C4::Context->preference('marcflavour');
2186 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2187 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2188 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2189 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2190 ? $params->{sortby} : 'itemnumber';
2191 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2192 $query .= qq{ ORDER BY $sortby $sortorder };
2194 my $rows = $params->{rows};
2197 my $offset = $rows * ($params->{page}-1);
2198 $query .= qq { LIMIT ?, ? };
2199 push @limit_args, $offset, $rows;
2202 my $sth = $dbh->prepare($query);
2203 my $rv = $sth->execute(@where_args, @limit_args);
2205 return unless ($rv);
2206 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2208 return ($sth->fetchall_arrayref({}), $total_rows);
2212 =head1 OTHER FUNCTIONS
2216 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2218 Find the given $subfield in the given $tag in the given
2219 MARC::Record $record. If the subfield is found, returns
2220 the (indicators, value) pair; otherwise, (undef, undef) is
2224 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2225 I suggest we export it from this module.
2230 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2233 if ( $tagfield < 10 ) {
2234 if ( $record->field($tagfield) ) {
2235 push @result, $record->field($tagfield)->data();
2240 foreach my $field ( $record->field($tagfield) ) {
2241 my @subfields = $field->subfields();
2242 foreach my $subfield (@subfields) {
2243 if ( @$subfield[0] eq $insubfield ) {
2244 push @result, @$subfield[1];
2245 $indicator = $field->indicator(1) . $field->indicator(2);
2250 return ( $indicator, @result );
2254 =head2 PrepareItemrecordDisplay
2256 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2258 Returns a hash with all the fields for Display a given item data in a template
2260 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2264 sub PrepareItemrecordDisplay {
2266 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2268 my $dbh = C4::Context->dbh;
2269 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2270 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2272 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2273 # a shared data structure. No plugin (including custom ones) should change
2274 # its contents. See also GetMarcStructure.
2275 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2277 # return nothing if we don't have found an existing framework.
2278 return q{} unless $tagslib;
2281 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2285 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2287 SELECT authorised_value,lib FROM authorised_values
2290 LEFT JOIN authorised_values_branches ON ( id = av_id )
2295 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2296 $query .= qq{ ORDER BY lib};
2297 my $authorised_values_sth = $dbh->prepare( $query );
2298 foreach my $tag ( sort keys %{$tagslib} ) {
2301 # loop through each subfield
2303 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2304 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2305 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2306 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2308 $subfield_data{tag} = $tag;
2309 $subfield_data{subfield} = $subfield;
2310 $subfield_data{countsubfield} = $cntsubf++;
2311 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2312 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2314 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2315 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2316 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2317 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2318 $subfield_data{hidden} = "display:none"
2319 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2320 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2321 my ( $x, $defaultvalue );
2323 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2325 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2326 if ( !defined $defaultvalue ) {
2327 $defaultvalue = q||;
2329 $defaultvalue =~ s/"/"/g;
2332 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2334 # search for itemcallnumber if applicable
2335 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2336 && C4::Context->preference('itemcallnumber') && $itemrecord) {
2337 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2338 my $CNtag = substr( $itemcn_pref, 0, 3 );
2339 next unless my $field = $itemrecord->field($CNtag);
2340 my $CNsubfields = substr( $itemcn_pref, 3 );
2341 $defaultvalue = $field->as_string( $CNsubfields, ' ');
2342 last if $defaultvalue;
2345 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2347 && $defaultvalues->{'callnumber'} ) {
2348 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2349 # if the item record exists, only use default value if the item has no callnumber
2350 $defaultvalue = $defaultvalues->{callnumber};
2351 } elsif ( !$itemrecord and $defaultvalues ) {
2352 # if the item record *doesn't* exists, always use the default value
2353 $defaultvalue = $defaultvalues->{callnumber};
2356 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2358 && $defaultvalues->{'branchcode'} ) {
2359 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2360 $defaultvalue = $defaultvalues->{branchcode};
2363 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2365 && $defaultvalues->{'location'} ) {
2367 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2368 # if the item record exists, only use default value if the item has no locationr
2369 $defaultvalue = $defaultvalues->{location};
2370 } elsif ( !$itemrecord and $defaultvalues ) {
2371 # if the item record *doesn't* exists, always use the default value
2372 $defaultvalue = $defaultvalues->{location};
2375 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2376 my @authorised_values;
2379 # builds list, depending on authorised value...
2381 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2382 if ( ( C4::Context->preference("IndependentBranches") )
2383 && !C4::Context->IsSuperLibrarian() ) {
2384 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2385 $sth->execute( C4::Context->userenv->{branch} );
2386 push @authorised_values, ""
2387 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2388 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2389 push @authorised_values, $branchcode;
2390 $authorised_lib{$branchcode} = $branchname;
2393 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2395 push @authorised_values, ""
2396 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2397 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2398 push @authorised_values, $branchcode;
2399 $authorised_lib{$branchcode} = $branchname;
2403 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2404 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2405 $defaultvalue = $defaultvalues->{branchcode};
2409 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2410 my $itemtypes = Koha::ItemTypes->search_with_localization;
2411 push @authorised_values, ""
2412 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2413 while ( my $itemtype = $itemtypes->next ) {
2414 push @authorised_values, $itemtype->itemtype;
2415 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2417 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2418 $defaultvalue = $defaultvalues->{'itemtype'};
2422 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2423 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2425 my $class_sources = GetClassSources();
2426 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2428 foreach my $class_source (sort keys %$class_sources) {
2429 next unless $class_sources->{$class_source}->{'used'} or
2430 ($class_source eq $default_source);
2431 push @authorised_values, $class_source;
2432 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2435 $defaultvalue = $default_source;
2437 #---- "true" authorised value
2439 $authorised_values_sth->execute(
2440 $tagslib->{$tag}->{$subfield}->{authorised_value},
2441 $branch_limit ? $branch_limit : ()
2443 push @authorised_values, ""
2444 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2445 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2446 push @authorised_values, $value;
2447 $authorised_lib{$value} = $lib;
2450 $subfield_data{marc_value} = {
2452 values => \@authorised_values,
2453 default => $defaultvalue // q{},
2454 labels => \%authorised_lib,
2456 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2458 require Koha::FrameworkPlugin;
2459 my $plugin = Koha::FrameworkPlugin->new({
2460 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2463 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2464 $plugin->build( $pars );
2465 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2466 $defaultvalue = $field->subfield($subfield) || q{};
2468 if( !$plugin->errstr ) {
2469 #TODO Move html to template; see report 12176/13397
2470 my $tab= $plugin->noclick? '-1': '';
2471 my $class= $plugin->noclick? ' disabled': '';
2472 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2473 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2475 warn $plugin->errstr;
2476 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
2479 elsif ( $tag eq '' ) { # it's an hidden field
2480 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2482 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2483 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2485 elsif ( length($defaultvalue) > 100
2486 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2487 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2488 or (C4::Context->preference("marcflavour") eq "MARC21" and
2489 500 <= $tag && $tag < 600 )
2491 # oversize field (textarea)
2492 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2494 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2496 push( @loop_data, \%subfield_data );
2501 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2502 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2505 'itemtagfield' => $itemtagfield,
2506 'itemtagsubfield' => $itemtagsubfield,
2507 'itemnumber' => $itemnumber,
2508 'iteminformation' => \@loop_data
2512 sub ToggleNewStatus {
2513 my ( $params ) = @_;
2514 my @rules = @{ $params->{rules} };
2515 my $report_only = $params->{report_only};
2517 my $dbh = C4::Context->dbh;
2519 my @item_columns = map { "items.$_" } Koha::Items->columns;
2520 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2522 for my $rule ( @rules ) {
2523 my $age = $rule->{age};
2524 my $conditions = $rule->{conditions};
2525 my $substitutions = $rule->{substitutions};
2526 foreach ( @$substitutions ) {
2527 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2534 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2537 for my $condition ( @$conditions ) {
2539 grep { $_ eq $condition->{field} } @item_columns
2540 or grep { $_ eq $condition->{field} } @biblioitem_columns
2542 if ( $condition->{value} =~ /\|/ ) {
2543 my @values = split /\|/, $condition->{value};
2544 $query .= qq| AND $condition->{field} IN (|
2545 . join( ',', ('?') x scalar @values )
2547 push @params, @values;
2549 $query .= qq| AND $condition->{field} = ?|;
2550 push @params, $condition->{value};
2554 if ( defined $age ) {
2555 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2558 my $sth = $dbh->prepare($query);
2559 $sth->execute( @params );
2560 while ( my $values = $sth->fetchrow_hashref ) {
2561 my $biblionumber = $values->{biblionumber};
2562 my $itemnumber = $values->{itemnumber};
2563 for my $substitution ( @$substitutions ) {
2564 next unless $substitution->{field};
2565 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2566 C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
2567 unless $report_only;
2568 push @{ $report->{$itemnumber} }, $substitution;
2576 =head2 _after_item_action_hooks
2578 Helper method that takes care of calling all plugin hooks
2582 sub _after_item_action_hooks {
2585 my $item_id = $args->{item_id};
2586 my $action = $args->{action};
2588 if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
2590 my @plugins = Koha::Plugins->new->GetPlugins({
2591 method => 'after_item_action',
2596 my $item = Koha::Items->find( $item_id );
2598 foreach my $plugin ( @plugins ) {
2600 $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });