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);
43 get_hostitemnumbers_of
51 PrepareItemrecordDisplay
64 use List::MoreUtils qw(any);
66 use DateTime::Format::MySQL;
67 use Data::Dumper; # used as part of logging item record changes, not just for
68 # debugging; so please don't remove this
70 use Koha::AuthorisedValues;
71 use Koha::DateUtils qw(dt_from_string);
74 use Koha::Biblioitems;
77 use Koha::SearchEngine;
78 use Koha::SearchEngine::Search;
83 C4::Items - item management functions
87 This module contains an API for manipulating item
88 records in Koha, and is used by cataloguing, circulation,
89 acquisitions, and serials management.
91 # FIXME This POD is not up-to-date
92 A Koha item record is stored in two places: the
93 items table and embedded in a MARC tag in the XML
94 version of the associated bib record in C<biblioitems.marcxml>.
95 This is done to allow the item information to be readily
96 indexed (e.g., by Zebra), but means that each item
97 modification transaction must keep the items table
98 and the MARC XML in sync at all times.
100 The items table will be considered authoritative. In other
101 words, if there is ever a discrepancy between the items
102 table and the MARC XML, the items table should be considered
105 =head1 HISTORICAL NOTE
107 Most of the functions in C<C4::Items> were originally in
108 the C<C4::Biblio> module.
110 =head1 CORE EXPORTED FUNCTIONS
112 The following functions are meant for use by users
119 CartToShelf($itemnumber);
121 Set the current shelving location of the item record
122 to its stored permanent shelving location. This is
123 primarily used to indicate when an item whose current
124 location is a special processing ('PROC') or shelving cart
125 ('CART') location is back in the stacks.
130 my ( $itemnumber ) = @_;
132 unless ( $itemnumber ) {
133 croak "FAILED CartToShelf() - no itemnumber supplied";
136 my $item = Koha::Items->find($itemnumber);
137 if ( $item->location eq 'CART' ) {
138 ModItem({ location => $item->permanent_location}, undef, $itemnumber);
142 =head2 AddItemFromMarc
144 my ($biblionumber, $biblioitemnumber, $itemnumber)
145 = AddItemFromMarc($source_item_marc, $biblionumber);
147 Given a MARC::Record object containing an embedded item
148 record and a biblionumber, create a new item record.
152 sub AddItemFromMarc {
153 my ( $source_item_marc, $biblionumber ) = @_;
154 my $dbh = C4::Context->dbh;
156 # parse item hash from MARC
157 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
158 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
160 my $localitemmarc = MARC::Record->new;
161 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
162 my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
163 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
164 return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
169 my ($biblionumber, $biblioitemnumber, $itemnumber)
170 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
172 Given a hash containing item column names as keys,
173 create a new Koha item record.
175 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
176 do not need to be supplied for general use; they exist
177 simply to allow them to be picked up from AddItemFromMarc.
179 The final optional parameter, C<$unlinked_item_subfields>, contains
180 an arrayref containing subfields present in the original MARC
181 representation of the item (e.g., from the item editor) that are
182 not mapped to C<items> columns directly but should instead
183 be stored in C<items.more_subfields_xml> and included in
184 the biblio items tag for display and indexing.
190 my $biblionumber = shift;
192 my $dbh = @_ ? shift : C4::Context->dbh;
193 my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
194 my $unlinked_item_subfields;
196 $unlinked_item_subfields = shift;
199 # needs old biblionumber and biblioitemnumber
200 $item->{'biblionumber'} = $biblionumber;
201 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
202 $sth->execute( $item->{'biblionumber'} );
203 ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
205 _set_defaults_for_add($item);
206 _set_derived_columns_for_add($item);
207 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
209 # FIXME - checks here
210 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
211 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
212 $itype_sth->execute( $item->{'biblionumber'} );
213 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
216 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
219 $item->{'itemnumber'} = $itemnumber;
221 C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
223 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
224 if C4::Context->preference("CataloguingLog");
226 _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
228 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
231 =head2 AddItemBatchFromMarc
233 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
234 $biblionumber, $biblioitemnumber, $frameworkcode);
236 Efficiently create item records from a MARC biblio record with
237 embedded item fields. This routine is suitable for batch jobs.
239 This API assumes that the bib record has already been
240 saved to the C<biblio> and C<biblioitems> tables. It does
241 not expect that C<biblio_metadata.metadata> is populated, but it
242 will do so via a call to ModBibiloMarc.
244 The goal of this API is to have a similar effect to using AddBiblio
245 and AddItems in succession, but without inefficient repeated
246 parsing of the MARC XML bib record.
248 This function returns an arrayref of new itemsnumbers and an arrayref of item
249 errors encountered during the processing. Each entry in the errors
250 list is a hashref containing the following keys:
256 Sequence number of original item tag in the MARC record.
260 Item barcode, provide to assist in the construction of
261 useful error messages.
265 Code representing the error condition. Can be 'duplicate_barcode',
266 'invalid_homebranch', or 'invalid_holdingbranch'.
268 =item error_information
270 Additional information appropriate to the error condition.
276 sub AddItemBatchFromMarc {
277 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
279 my @itemnumbers = ();
281 my $dbh = C4::Context->dbh;
283 # We modify the record, so lets work on a clone so we don't change the
285 $record = $record->clone();
286 # loop through the item tags and start creating items
287 my @bad_item_fields = ();
288 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
289 my $item_sequence_num = 0;
290 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
291 $item_sequence_num++;
292 # we take the item field and stick it into a new
293 # MARC record -- this is required so far because (FIXME)
294 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
295 # and there is no TransformMarcFieldToKoha
296 my $temp_item_marc = MARC::Record->new();
297 $temp_item_marc->append_fields($item_field);
299 # add biblionumber and biblioitemnumber
300 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
301 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
302 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
303 $item->{'biblionumber'} = $biblionumber;
304 $item->{'biblioitemnumber'} = $biblioitemnumber;
306 # check for duplicate barcode
307 my %item_errors = CheckItemPreSave($item);
309 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
310 push @bad_item_fields, $item_field;
314 _set_defaults_for_add($item);
315 _set_derived_columns_for_add($item);
316 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
317 warn $error if $error;
318 push @itemnumbers, $itemnumber; # FIXME not checking error
319 $item->{'itemnumber'} = $itemnumber;
321 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
323 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
324 $item_field->replace_with($new_item_marc->field($itemtag));
327 # remove any MARC item fields for rejected items
328 foreach my $item_field (@bad_item_fields) {
329 $record->delete_field($item_field);
332 # update the MARC biblio
333 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
335 return (\@itemnumbers, \@errors);
338 =head2 ModItemFromMarc
340 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
342 This function updates an item record based on a supplied
343 C<MARC::Record> object containing an embedded item field.
344 This API is meant for the use of C<additem.pl>; for
345 other purposes, C<ModItem> should be used.
347 This function uses the hash %default_values_for_mod_from_marc,
348 which contains default values for item fields to
349 apply when modifying an item. This is needed because
350 if an item field's value is cleared, TransformMarcToKoha
351 does not include the column in the
352 hash that's passed to ModItem, which without
353 use of this hash makes it impossible to clear
354 an item field's value. See bug 2466.
356 Note that only columns that can be directly
357 changed from the cataloging and serials
358 item editors are included in this hash.
364 sub _build_default_values_for_mod_marc {
365 # Has no framework parameter anymore, since Default is authoritative
366 # for Koha to MARC mappings.
368 my $cache = Koha::Caches->get_instance();
369 my $cache_key = "default_value_for_mod_marc-";
370 my $cached = $cache->get_from_cache($cache_key);
371 return $cached if $cached;
373 my $default_values = {
375 booksellerid => undef,
377 'items.cn_source' => undef,
378 coded_location_qualifier => undef,
382 holdingbranch => undef,
384 itemcallnumber => undef,
387 itemnotes_nonpublic => undef,
390 permanent_location => undef,
395 replacementprice => undef,
396 replacementpricedate => undef,
399 stocknumber => undef,
403 my %default_values_for_mod_from_marc;
404 while ( my ( $field, $default_value ) = each %$default_values ) {
405 my $kohafield = $field;
406 $kohafield =~ s|^([^\.]+)$|items.$1|;
407 $default_values_for_mod_from_marc{$field} = $default_value
408 if C4::Biblio::GetMarcFromKohaField( $kohafield );
411 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
412 return \%default_values_for_mod_from_marc;
415 sub ModItemFromMarc {
416 my $item_marc = shift;
417 my $biblionumber = shift;
418 my $itemnumber = shift;
420 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
421 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
423 my $localitemmarc = MARC::Record->new;
424 $localitemmarc->append_fields( $item_marc->field($itemtag) );
425 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
426 my $default_values = _build_default_values_for_mod_marc();
427 foreach my $item_field ( keys %$default_values ) {
428 $item->{$item_field} = $default_values->{$item_field}
429 unless exists $item->{$item_field};
431 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
433 ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
440 { column => $newvalue },
444 [ unlinked_item_subfields => $unlinked_item_subfields, ]
449 Change one or more columns in an item record.
451 The first argument is a hashref mapping from item column
452 names to the new values. The second and third arguments
453 are the biblionumber and itemnumber, respectively.
454 The fourth, optional parameter (additional_params) may contain the keys
455 unlinked_item_subfields and log_action.
457 C<$unlinked_item_subfields> contains an arrayref containing
458 subfields present in the original MARC
459 representation of the item (e.g., from the item editor) that are
460 not mapped to C<items> columns directly but should instead
461 be stored in C<items.more_subfields_xml> and included in
462 the biblio items tag for display and indexing.
464 If one of the changed columns is used to calculate
465 the derived value of a column such as C<items.cn_sort>,
466 this routine will perform the necessary calculation
469 If log_action is set to false, the action will not be logged.
470 If log_action is true or undefined, the action will be logged.
475 my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
476 my $log_action = $additional_params->{log_action} // 1;
477 my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
479 return unless %$item;
480 $item->{'itemnumber'} = $itemnumber or return;
482 # if $biblionumber is undefined, get it from the current item
483 unless (defined $biblionumber) {
484 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
487 if ($unlinked_item_subfields) {
488 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
491 my @fields = qw( itemlost withdrawn damaged );
493 # Only retrieve the item if we need to set an "on" date field
494 if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
495 my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
496 for my $field (@fields) {
497 if ( defined( $item->{$field} )
498 and not $pre_mod_item->$field
499 and $item->{$field} )
501 $item->{ $field . '_on' } =
502 DateTime::Format::MySQL->format_datetime( dt_from_string() );
507 # If the field is defined but empty, we are removing and,
508 # and thus need to clear out the 'on' field as well
509 for my $field (@fields) {
510 if ( defined( $item->{$field} ) && !$item->{$field} ) {
511 $item->{ $field . '_on' } = undef;
516 _set_derived_columns_for_mod($item);
517 _do_column_fixes_for_mod($item);
520 # attempt to change itemnumber
521 # attempt to change biblionumber (if we want
522 # an API to relink an item to a different bib,
523 # it should be a separate function)
526 _koha_modify_item($item);
528 # request that bib be reindexed so that searching on current
529 # item status is possible
530 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
532 _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
534 logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
535 if $log_action && C4::Context->preference("CataloguingLog");
538 =head2 ModItemTransfer
540 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
542 Marks an item as being transferred from one branch to another and records the trigger.
546 sub ModItemTransfer {
547 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
549 my $dbh = C4::Context->dbh;
550 my $item = Koha::Items->find( $itemnumber );
552 # Remove the 'shelving cart' location status if it is being used.
553 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
555 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
557 #new entry in branchtransfers....
558 my $sth = $dbh->prepare(
559 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
560 VALUES (?, ?, NOW(), ?, ?)");
561 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
563 ModItem({ holdingbranch => $frombranch }, undef, $itemnumber, { log_action => 0 });
564 ModDateLastSeen($itemnumber);
568 =head2 ModDateLastSeen
570 ModDateLastSeen( $itemnumber, $leave_item_lost );
572 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
573 C<$itemnumber> is the item number
574 C<$leave_item_lost> determines if a lost item will be found or remain lost
578 sub ModDateLastSeen {
579 my ( $itemnumber, $leave_item_lost ) = @_;
581 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
584 $params->{datelastseen} = $today;
585 $params->{itemlost} = 0 unless $leave_item_lost;
587 ModItem( $params, undef, $itemnumber, { log_action => 0 } );
592 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
594 Exported function (core API) for deleting an item record in Koha.
601 my $itemnumber = $params->{itemnumber};
602 my $biblionumber = $params->{biblionumber};
604 unless ($biblionumber) {
605 my $item = Koha::Items->find( $itemnumber );
606 $biblionumber = $item ? $item->biblio->biblionumber : undef;
609 # If there is no biblionumber for the given itemnumber, there is nothing to delete
610 return 0 unless $biblionumber;
612 # FIXME check the item has no current issues
613 my $deleted = _koha_delete_item( $itemnumber );
615 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
617 _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
619 #search item field code
620 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
624 =head2 CheckItemPreSave
626 my $item_ref = TransformMarcToKoha($marc, 'items');
628 my %errors = CheckItemPreSave($item_ref);
629 if (exists $errors{'duplicate_barcode'}) {
630 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
631 } elsif (exists $errors{'invalid_homebranch'}) {
632 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
633 } elsif (exists $errors{'invalid_holdingbranch'}) {
634 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
639 Given a hashref containing item fields, determine if it can be
640 inserted or updated in the database. Specifically, checks for
641 database integrity issues, and returns a hash containing any
642 of the following keys, if applicable.
646 =item duplicate_barcode
648 Barcode, if it duplicates one already found in the database.
650 =item invalid_homebranch
652 Home branch, if not defined in branches table.
654 =item invalid_holdingbranch
656 Holding branch, if not defined in branches table.
660 This function does NOT implement any policy-related checks,
661 e.g., whether current operator is allowed to save an
662 item that has a given branch code.
666 sub CheckItemPreSave {
667 my $item_ref = shift;
671 # check for duplicate barcode
672 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
673 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
674 if ($existing_item) {
675 if (!exists $item_ref->{'itemnumber'} # new item
676 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
677 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
682 # check for valid home branch
683 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
684 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
685 unless (defined $home_library) {
686 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
690 # check for valid holding branch
691 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
692 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
693 unless (defined $holding_library) {
694 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
702 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
704 The following functions provide various ways of
705 getting an item record, a set of item records, or
706 lists of authorized values for certain item fields.
710 =head2 GetItemsForInventory
712 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
713 minlocation => $minlocation,
714 maxlocation => $maxlocation,
715 location => $location,
716 itemtype => $itemtype,
717 ignoreissued => $ignoreissued,
718 datelastseen => $datelastseen,
719 branchcode => $branchcode,
723 statushash => $statushash,
726 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
728 The sub returns a reference to a list of hashes, each containing
729 itemnumber, author, title, barcode, item callnumber, and date last
730 seen. It is ordered by callnumber then title.
732 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
733 the datelastseen can be used to specify that you want to see items not seen since a past date only.
734 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
735 $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.
737 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
741 sub GetItemsForInventory {
742 my ( $parameters ) = @_;
743 my $minlocation = $parameters->{'minlocation'} // '';
744 my $maxlocation = $parameters->{'maxlocation'} // '';
745 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
746 my $location = $parameters->{'location'} // '';
747 my $itemtype = $parameters->{'itemtype'} // '';
748 my $ignoreissued = $parameters->{'ignoreissued'} // '';
749 my $datelastseen = $parameters->{'datelastseen'} // '';
750 my $branchcode = $parameters->{'branchcode'} // '';
751 my $branch = $parameters->{'branch'} // '';
752 my $offset = $parameters->{'offset'} // '';
753 my $size = $parameters->{'size'} // '';
754 my $statushash = $parameters->{'statushash'} // '';
755 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
757 my $dbh = C4::Context->dbh;
758 my ( @bind_params, @where_strings );
760 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
761 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
763 my $select_columns = q{
764 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
766 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
769 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
770 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
773 for my $authvfield (keys %$statushash){
774 if ( scalar @{$statushash->{$authvfield}} > 0 ){
775 my $joinedvals = join ',', @{$statushash->{$authvfield}};
776 push @where_strings, "$authvfield in (" . $joinedvals . ")";
782 push @where_strings, 'items.cn_sort >= ?';
783 push @bind_params, $min_cnsort;
787 push @where_strings, 'items.cn_sort <= ?';
788 push @bind_params, $max_cnsort;
792 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
793 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
794 push @bind_params, $datelastseen;
798 push @where_strings, 'items.location = ?';
799 push @bind_params, $location;
803 if($branch eq "homebranch"){
804 push @where_strings, 'items.homebranch = ?';
806 push @where_strings, 'items.holdingbranch = ?';
808 push @bind_params, $branchcode;
812 push @where_strings, 'biblioitems.itemtype = ?';
813 push @bind_params, $itemtype;
816 if ( $ignoreissued) {
817 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
818 push @where_strings, 'issues.date_due IS NULL';
821 if ( $ignore_waiting_holds ) {
822 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
823 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
826 if ( @where_strings ) {
828 $query .= join ' AND ', @where_strings;
830 my $count_query = $select_count . $query;
831 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
832 $query .= " LIMIT $offset, $size" if ($offset and $size);
833 $query = $select_columns . $query;
834 my $sth = $dbh->prepare($query);
835 $sth->execute( @bind_params );
838 my $tmpresults = $sth->fetchall_arrayref({});
839 $sth = $dbh->prepare( $count_query );
840 $sth->execute( @bind_params );
841 my ($iTotalRecords) = $sth->fetchrow_array();
843 my @avs = Koha::AuthorisedValues->search(
844 { 'marc_subfield_structures.kohafield' => { '>' => '' },
845 'me.authorised_value' => { '>' => '' },
847 { join => { category => 'marc_subfield_structures' },
848 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
849 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
850 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
854 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
856 foreach my $row (@$tmpresults) {
859 foreach (keys %$row) {
862 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
865 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
871 return (\@results, $iTotalRecords);
876 @results = GetItemsInfo($biblionumber);
878 Returns information about items with the given biblionumber.
880 C<GetItemsInfo> returns a list of references-to-hash. Each element
881 contains a number of keys. Most of them are attributes from the
882 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
883 Koha database. Other keys include:
887 =item C<$data-E<gt>{branchname}>
889 The name (not the code) of the branch to which the book belongs.
891 =item C<$data-E<gt>{datelastseen}>
893 This is simply C<items.datelastseen>, except that while the date is
894 stored in YYYY-MM-DD format in the database, here it is converted to
895 DD/MM/YYYY format. A NULL date is returned as C<//>.
897 =item C<$data-E<gt>{datedue}>
899 =item C<$data-E<gt>{class}>
901 This is the concatenation of C<biblioitems.classification>, the book's
902 Dewey code, and C<biblioitems.subclass>.
904 =item C<$data-E<gt>{ocount}>
906 I think this is the number of copies of the book available.
908 =item C<$data-E<gt>{order}>
910 If this is set, it is set to C<One Order>.
917 my ( $biblionumber ) = @_;
918 my $dbh = C4::Context->dbh;
919 require C4::Languages;
920 my $language = C4::Languages::getlanguage();
926 biblioitems.itemtype,
929 biblioitems.publicationyear,
930 biblioitems.publishercode,
931 biblioitems.volumedate,
932 biblioitems.volumedesc,
935 items.notforloan as itemnotforloan,
936 issues.borrowernumber,
937 issues.date_due as datedue,
938 issues.onsite_checkout,
939 borrowers.cardnumber,
942 borrowers.branchcode as bcode,
944 serial.publisheddate,
945 itemtypes.description,
946 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
947 itemtypes.notforloan as notforloan_per_itemtype,
951 holding.opac_info as holding_branch_opac_info,
952 home.opac_info as home_branch_opac_info,
953 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
955 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
956 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
957 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
958 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
959 LEFT JOIN issues USING (itemnumber)
960 LEFT JOIN borrowers USING (borrowernumber)
961 LEFT JOIN serialitems USING (itemnumber)
962 LEFT JOIN serial USING (serialid)
963 LEFT JOIN itemtypes ON itemtypes.itemtype = "
964 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
966 LEFT JOIN tmp_holdsqueue USING (itemnumber)
967 LEFT JOIN localization ON itemtypes.itemtype = localization.code
968 AND localization.entity = 'itemtypes'
969 AND localization.lang = ?
972 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
973 my $sth = $dbh->prepare($query);
974 $sth->execute($language, $biblionumber);
979 my $userenv = C4::Context->userenv;
980 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
981 while ( my $data = $sth->fetchrow_hashref ) {
982 if ( $data->{borrowernumber} && $want_not_same_branch) {
983 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
986 $serial ||= $data->{'serial'};
989 # get notforloan complete status if applicable
990 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
991 $data->{notforloanvalue} = $descriptions->{lib} // '';
992 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
994 # get restricted status and description if applicable
995 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
996 $data->{restrictedvalue} = $descriptions->{lib} // '';
997 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
999 # my stack procedures
1000 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1001 $data->{stack} = $descriptions->{lib} // '';
1003 # Find the last 3 people who borrowed this item.
1004 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1005 WHERE itemnumber = ?
1006 AND old_issues.borrowernumber = borrowers.borrowernumber
1007 ORDER BY returndate DESC
1009 $sth2->execute($data->{'itemnumber'});
1011 while (my $data2 = $sth2->fetchrow_hashref()) {
1012 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1013 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1014 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1018 $results[$i] = $data;
1023 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1027 =head2 GetItemsLocationInfo
1029 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1031 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1033 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1037 =item C<$data-E<gt>{homebranch}>
1039 Branch Name of the item's homebranch
1041 =item C<$data-E<gt>{holdingbranch}>
1043 Branch Name of the item's holdingbranch
1045 =item C<$data-E<gt>{location}>
1047 Item's shelving location code
1049 =item C<$data-E<gt>{location_intranet}>
1051 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1053 =item C<$data-E<gt>{location_opac}>
1055 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1058 =item C<$data-E<gt>{itemcallnumber}>
1060 Item's itemcallnumber
1062 =item C<$data-E<gt>{cn_sort}>
1064 Item's call number normalized for sorting
1070 sub GetItemsLocationInfo {
1071 my $biblionumber = shift;
1074 my $dbh = C4::Context->dbh;
1075 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1076 location, itemcallnumber, cn_sort
1077 FROM items, branches as a, branches as b
1078 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1079 AND biblionumber = ?
1080 ORDER BY cn_sort ASC";
1081 my $sth = $dbh->prepare($query);
1082 $sth->execute($biblionumber);
1084 while ( my $data = $sth->fetchrow_hashref ) {
1085 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1086 $av = $av->count ? $av->next : undef;
1087 $data->{location_intranet} = $av ? $av->lib : '';
1088 $data->{location_opac} = $av ? $av->opac_description : '';
1089 push @results, $data;
1094 =head2 GetHostItemsInfo
1096 $hostiteminfo = GetHostItemsInfo($hostfield);
1097 Returns the iteminfo for items linked to records via a host field
1101 sub GetHostItemsInfo {
1103 my @returnitemsInfo;
1105 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1106 return @returnitemsInfo;
1110 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1111 C4::Context->preference('marcflavour') eq 'NORMARC') {
1112 @fields = $record->field('773');
1113 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1114 @fields = $record->field('461');
1117 foreach my $hostfield ( @fields ) {
1118 my $hostbiblionumber = $hostfield->subfield("0");
1119 my $linkeditemnumber = $hostfield->subfield("9");
1120 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1121 foreach my $hostitemInfo (@hostitemInfos) {
1122 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1123 push @returnitemsInfo, $hostitemInfo;
1128 return @returnitemsInfo;
1131 =head2 get_hostitemnumbers_of
1133 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1135 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1137 Return a reference on a hash where key is a biblionumber and values are
1138 references on array of itemnumbers.
1143 sub get_hostitemnumbers_of {
1144 my ($biblionumber) = @_;
1146 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1150 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1151 return unless $marcrecord;
1153 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1155 my $marcflavor = C4::Context->preference('marcflavour');
1156 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1161 elsif ( $marcflavor eq 'UNIMARC' ) {
1167 foreach my $hostfield ( $marcrecord->field($tag) ) {
1168 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1169 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1170 my $linkeditemnumber = $hostfield->subfield($item_s);
1171 if ( ! $linkeditemnumber ) {
1172 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1175 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1176 push @returnhostitemnumbers, $linkeditemnumber
1180 return @returnhostitemnumbers;
1183 =head2 GetHiddenItemnumbers
1185 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1187 Given a list of items it checks which should be hidden from the OPAC given
1188 the current configuration. Returns a list of itemnumbers corresponding to
1189 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1194 sub GetHiddenItemnumbers {
1196 my $items = $params->{items};
1197 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1198 foreach my $except (split(/\|/, $exceptions)){
1199 if ($params->{'borcat'} eq $except){
1200 return; # we don't hide anything for this borrower category
1206 my $yaml = C4::Context->preference('OpacHiddenItems');
1207 return () if (! $yaml =~ /\S/ );
1208 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1211 $hidingrules = YAML::Load($yaml);
1214 warn "Unable to parse OpacHiddenItems syspref : $@";
1217 my $dbh = C4::Context->dbh;
1220 foreach my $item (@$items) {
1222 # We check each rule
1223 foreach my $field (keys %$hidingrules) {
1225 if (exists $item->{$field}) {
1226 $val = $item->{$field};
1229 my $query = "SELECT $field from items where itemnumber = ?";
1230 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1232 $val = '' unless defined $val;
1234 # If the results matches the values in the yaml file
1235 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1237 # We add the itemnumber to the list
1238 push @resultitems, $item->{'itemnumber'};
1240 # If at least one rule matched for an item, no need to test the others
1245 return @resultitems;
1248 =head1 LIMITED USE FUNCTIONS
1250 The following functions, while part of the public API,
1251 are not exported. This is generally because they are
1252 meant to be used by only one script for a specific
1253 purpose, and should not be used in any other context
1254 without careful thought.
1260 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1262 Returns MARC::Record of the item passed in parameter.
1263 This function is meant for use only in C<cataloguing/additem.pl>,
1264 where it is needed to support that script's MARC-like
1270 my ( $biblionumber, $itemnumber ) = @_;
1272 # GetMarcItem has been revised so that it does the following:
1273 # 1. Gets the item information from the items table.
1274 # 2. Converts it to a MARC field for storage in the bib record.
1276 # The previous behavior was:
1277 # 1. Get the bib record.
1278 # 2. Return the MARC tag corresponding to the item record.
1280 # The difference is that one treats the items row as authoritative,
1281 # while the other treats the MARC representation as authoritative
1282 # under certain circumstances.
1284 my $item = Koha::Items->find($itemnumber) or return;
1286 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1287 # Also, don't emit a subfield if the underlying field is blank.
1289 return Item2Marc($item->unblessed, $biblionumber);
1293 my ($itemrecord,$biblionumber)=@_;
1296 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1297 } keys %{ $itemrecord }
1299 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1300 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1301 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1302 "items.itemnumber", $framework,
1305 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1306 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1307 foreach my $field ($itemmarc->field($itemtag)){
1308 $field->add_subfields(@$unlinked_item_subfields);
1314 =head1 PRIVATE FUNCTIONS AND VARIABLES
1316 The following functions are not meant to be called
1317 directly, but are documented in order to explain
1318 the inner workings of C<C4::Items>.
1322 =head2 %derived_columns
1324 This hash keeps track of item columns that
1325 are strictly derived from other columns in
1326 the item record and are not meant to be set
1329 Each key in the hash should be the name of a
1330 column (as named by TransformMarcToKoha). Each
1331 value should be hashref whose keys are the
1332 columns on which the derived column depends. The
1333 hashref should also contain a 'BUILDER' key
1334 that is a reference to a sub that calculates
1339 my %derived_columns = (
1340 'items.cn_sort' => {
1341 'itemcallnumber' => 1,
1342 'items.cn_source' => 1,
1343 'BUILDER' => \&_calc_items_cn_sort,
1347 =head2 _set_derived_columns_for_add
1349 _set_derived_column_for_add($item);
1351 Given an item hash representing a new item to be added,
1352 calculate any derived columns. Currently the only
1353 such column is C<items.cn_sort>.
1357 sub _set_derived_columns_for_add {
1360 foreach my $column (keys %derived_columns) {
1361 my $builder = $derived_columns{$column}->{'BUILDER'};
1362 my $source_values = {};
1363 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1364 next if $source_column eq 'BUILDER';
1365 $source_values->{$source_column} = $item->{$source_column};
1367 $builder->($item, $source_values);
1371 =head2 _set_derived_columns_for_mod
1373 _set_derived_column_for_mod($item);
1375 Given an item hash representing a new item to be modified.
1376 calculate any derived columns. Currently the only
1377 such column is C<items.cn_sort>.
1379 This routine differs from C<_set_derived_columns_for_add>
1380 in that it needs to handle partial item records. In other
1381 words, the caller of C<ModItem> may have supplied only one
1382 or two columns to be changed, so this function needs to
1383 determine whether any of the columns to be changed affect
1384 any of the derived columns. Also, if a derived column
1385 depends on more than one column, but the caller is not
1386 changing all of then, this routine retrieves the unchanged
1387 values from the database in order to ensure a correct
1392 sub _set_derived_columns_for_mod {
1395 foreach my $column (keys %derived_columns) {
1396 my $builder = $derived_columns{$column}->{'BUILDER'};
1397 my $source_values = {};
1398 my %missing_sources = ();
1399 my $must_recalc = 0;
1400 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1401 next if $source_column eq 'BUILDER';
1402 if (exists $item->{$source_column}) {
1404 $source_values->{$source_column} = $item->{$source_column};
1406 $missing_sources{$source_column} = 1;
1410 foreach my $source_column (keys %missing_sources) {
1411 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1413 $builder->($item, $source_values);
1418 =head2 _do_column_fixes_for_mod
1420 _do_column_fixes_for_mod($item);
1422 Given an item hashref containing one or more
1423 columns to modify, fix up certain values.
1424 Specifically, set to 0 any passed value
1425 of C<notforloan>, C<damaged>, C<itemlost>, or
1426 C<withdrawn> that is either undefined or
1427 contains the empty string.
1431 sub _do_column_fixes_for_mod {
1434 if (exists $item->{'notforloan'} and
1435 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1436 $item->{'notforloan'} = 0;
1438 if (exists $item->{'damaged'} and
1439 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1440 $item->{'damaged'} = 0;
1442 if (exists $item->{'itemlost'} and
1443 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1444 $item->{'itemlost'} = 0;
1446 if (exists $item->{'withdrawn'} and
1447 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1448 $item->{'withdrawn'} = 0;
1451 exists $item->{location}
1452 and ( !defined $item->{location}
1453 || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
1454 and not $item->{permanent_location}
1457 $item->{'permanent_location'} = $item->{'location'};
1459 if (exists $item->{'timestamp'}) {
1460 delete $item->{'timestamp'};
1464 =head2 _get_single_item_column
1466 _get_single_item_column($column, $itemnumber);
1468 Retrieves the value of a single column from an C<items>
1469 row specified by C<$itemnumber>.
1473 sub _get_single_item_column {
1475 my $itemnumber = shift;
1477 my $dbh = C4::Context->dbh;
1478 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1479 $sth->execute($itemnumber);
1480 my ($value) = $sth->fetchrow();
1484 =head2 _calc_items_cn_sort
1486 _calc_items_cn_sort($item, $source_values);
1488 Helper routine to calculate C<items.cn_sort>.
1492 sub _calc_items_cn_sort {
1494 my $source_values = shift;
1496 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1499 =head2 _set_defaults_for_add
1501 _set_defaults_for_add($item_hash);
1503 Given an item hash representing an item to be added, set
1504 correct default values for columns whose default value
1505 is not handled by the DBMS. This includes the following
1512 C<items.dateaccessioned>
1534 sub _set_defaults_for_add {
1536 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1537 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1540 =head2 _koha_new_item
1542 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1544 Perform the actual insert into the C<items> table.
1548 sub _koha_new_item {
1549 my ( $item, $barcode ) = @_;
1550 my $dbh=C4::Context->dbh;
1552 $item->{permanent_location} //= $item->{location};
1553 _mod_item_dates( $item );
1555 "INSERT INTO items SET
1557 biblioitemnumber = ?,
1559 dateaccessioned = ?,
1563 replacementprice = ?,
1564 replacementpricedate = ?,
1565 datelastborrowed = ?,
1573 coded_location_qualifier = ?,
1576 itemnotes_nonpublic = ?,
1579 permanent_location = ?,
1591 more_subfields_xml = ?,
1596 my $sth = $dbh->prepare($query);
1597 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1599 $item->{'biblionumber'},
1600 $item->{'biblioitemnumber'},
1602 $item->{'dateaccessioned'},
1603 $item->{'booksellerid'},
1604 $item->{'homebranch'},
1606 $item->{'replacementprice'},
1607 $item->{'replacementpricedate'} || $today,
1608 $item->{datelastborrowed},
1609 $item->{datelastseen} || $today,
1611 $item->{'notforloan'},
1613 $item->{'itemlost'},
1614 $item->{'withdrawn'},
1615 $item->{'itemcallnumber'},
1616 $item->{'coded_location_qualifier'},
1617 $item->{'restricted'},
1618 $item->{'itemnotes'},
1619 $item->{'itemnotes_nonpublic'},
1620 $item->{'holdingbranch'},
1621 $item->{'location'},
1622 $item->{'permanent_location'},
1625 $item->{'renewals'},
1626 $item->{'reserves'},
1627 $item->{'items.cn_source'},
1628 $item->{'items.cn_sort'},
1631 $item->{'materials'},
1633 $item->{'enumchron'},
1634 $item->{'more_subfields_xml'},
1635 $item->{'copynumber'},
1636 $item->{'stocknumber'},
1637 $item->{'new_status'},
1641 if ( defined $sth->errstr ) {
1642 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1645 $itemnumber = $dbh->{'mysql_insertid'};
1648 return ( $itemnumber, $error );
1651 =head2 MoveItemFromBiblio
1653 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1655 Moves an item from a biblio to another
1657 Returns undef if the move failed or the biblionumber of the destination record otherwise
1661 sub MoveItemFromBiblio {
1662 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1663 my $dbh = C4::Context->dbh;
1664 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1665 SELECT biblioitemnumber
1667 WHERE biblionumber = ?
1668 |, undef, $tobiblio );
1669 my $return = $dbh->do(q|
1671 SET biblioitemnumber = ?,
1673 WHERE itemnumber = ?
1674 AND biblionumber = ?
1675 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1677 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1678 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1679 # Checking if the item we want to move is in an order
1680 require C4::Acquisition;
1681 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1683 # Replacing the biblionumber within the order if necessary
1684 $order->{'biblionumber'} = $tobiblio;
1685 C4::Acquisition::ModOrder($order);
1688 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1689 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1692 SET biblionumber = ?
1693 WHERE itemnumber = ?
1694 |, undef, $tobiblio, $itemnumber );
1701 =head2 ItemSafeToDelete
1703 ItemSafeToDelete( $biblionumber, $itemnumber);
1705 Exported function (core API) for checking whether an item record is safe to delete.
1707 returns 1 if the item is safe to delete,
1709 "book_on_loan" if the item is checked out,
1711 "not_same_branch" if the item is blocked by independent branches,
1713 "book_reserved" if the there are holds aganst the item, or
1715 "linked_analytics" if the item has linked analytic records.
1719 sub ItemSafeToDelete {
1720 my ( $biblionumber, $itemnumber ) = @_;
1722 my $dbh = C4::Context->dbh;
1726 my $countanalytics = GetAnalyticsCount($itemnumber);
1728 my $item = Koha::Items->find($itemnumber) or return;
1730 if ($item->checkout) {
1731 $status = "book_on_loan";
1733 elsif ( defined C4::Context->userenv
1734 and !C4::Context->IsSuperLibrarian()
1735 and C4::Context->preference("IndependentBranches")
1736 and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1738 $status = "not_same_branch";
1741 # check it doesn't have a waiting reserve
1742 my $sth = $dbh->prepare(
1744 SELECT COUNT(*) FROM reserves
1745 WHERE (found = 'W' OR found = 'T')
1749 $sth->execute($itemnumber);
1750 my ($reserve) = $sth->fetchrow;
1752 $status = "book_reserved";
1754 elsif ( $countanalytics > 0 ) {
1755 $status = "linked_analytics";
1766 DelItemCheck( $biblionumber, $itemnumber);
1768 Exported function (core API) for deleting an item record in Koha if there no current issue.
1770 DelItemCheck wraps ItemSafeToDelete around DelItem.
1775 my ( $biblionumber, $itemnumber ) = @_;
1776 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1778 if ( $status == 1 ) {
1781 biblionumber => $biblionumber,
1782 itemnumber => $itemnumber
1789 =head2 _koha_modify_item
1791 my ($itemnumber,$error) =_koha_modify_item( $item );
1793 Perform the actual update of the C<items> row. Note that this
1794 routine accepts a hashref specifying the columns to update.
1798 sub _koha_modify_item {
1800 my $dbh=C4::Context->dbh;
1803 my $query = "UPDATE items SET ";
1805 _mod_item_dates( $item );
1806 for my $key ( keys %$item ) {
1807 next if ( $key eq 'itemnumber' );
1809 push @bind, $item->{$key};
1812 $query .= " WHERE itemnumber=?";
1813 push @bind, $item->{'itemnumber'};
1814 my $sth = $dbh->prepare($query);
1815 $sth->execute(@bind);
1817 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1820 return ($item->{'itemnumber'},$error);
1823 sub _mod_item_dates { # date formatting for date fields in item hash
1825 return if !$item || ref($item) ne 'HASH';
1828 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1830 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1831 # NOTE: We do not (yet) have items fields ending with datetime
1832 # Fields with _on$ have been handled already
1834 foreach my $key ( @keys ) {
1835 next if !defined $item->{$key}; # skip undefs
1836 my $dt = eval { dt_from_string( $item->{$key} ) };
1837 # eval: dt_from_string will die on us if we pass illegal dates
1840 if( defined $dt && ref($dt) eq 'DateTime' ) {
1841 if( $key =~ /datetime/ ) {
1842 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1844 $newstr = DateTime::Format::MySQL->format_date($dt);
1847 $item->{$key} = $newstr; # might be undef to clear garbage
1851 =head2 _koha_delete_item
1853 _koha_delete_item( $itemnum );
1855 Internal function to delete an item record from the koha tables
1859 sub _koha_delete_item {
1860 my ( $itemnum ) = @_;
1862 my $dbh = C4::Context->dbh;
1863 # save the deleted item to deleteditems table
1864 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1865 $sth->execute($itemnum);
1866 my $data = $sth->fetchrow_hashref();
1868 # There is no item to delete
1869 return 0 unless $data;
1871 my $query = "INSERT INTO deleteditems SET ";
1873 foreach my $key ( keys %$data ) {
1874 next if ( $key eq 'timestamp' ); # timestamp will be set by db
1875 $query .= "$key = ?,";
1876 push( @bind, $data->{$key} );
1879 $sth = $dbh->prepare($query);
1880 $sth->execute(@bind);
1882 # delete from items table
1883 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1884 my $deleted = $sth->execute($itemnum);
1885 return ( $deleted == 1 ) ? 1 : 0;
1888 =head2 _marc_from_item_hash
1890 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1892 Given an item hash representing a complete item record,
1893 create a C<MARC::Record> object containing an embedded
1894 tag representing that item.
1896 The third, optional parameter C<$unlinked_item_subfields> is
1897 an arrayref of subfields (not mapped to C<items> fields per the
1898 framework) to be added to the MARC representation
1903 sub _marc_from_item_hash {
1905 my $frameworkcode = shift;
1906 my $unlinked_item_subfields;
1908 $unlinked_item_subfields = shift;
1911 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1912 # Also, don't emit a subfield if the underlying field is blank.
1913 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1914 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1915 : () } keys %{ $item } };
1917 my $item_marc = MARC::Record->new();
1918 foreach my $item_field ( keys %{$mungeditem} ) {
1919 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1920 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1921 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1922 foreach my $value (@values){
1923 if ( my $field = $item_marc->field($tag) ) {
1924 $field->add_subfields( $subfield => $value );
1926 my $add_subfields = [];
1927 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1928 $add_subfields = $unlinked_item_subfields;
1930 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1938 =head2 _repack_item_errors
1940 Add an error message hash generated by C<CheckItemPreSave>
1941 to a list of errors.
1945 sub _repack_item_errors {
1946 my $item_sequence_num = shift;
1947 my $item_ref = shift;
1948 my $error_ref = shift;
1950 my @repacked_errors = ();
1952 foreach my $error_code (sort keys %{ $error_ref }) {
1953 my $repacked_error = {};
1954 $repacked_error->{'item_sequence'} = $item_sequence_num;
1955 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1956 $repacked_error->{'error_code'} = $error_code;
1957 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1958 push @repacked_errors, $repacked_error;
1961 return @repacked_errors;
1964 =head2 _get_unlinked_item_subfields
1966 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1970 sub _get_unlinked_item_subfields {
1971 my $original_item_marc = shift;
1972 my $frameworkcode = shift;
1974 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1976 # assume that this record has only one field, and that that
1977 # field contains only the item information
1979 my @fields = $original_item_marc->fields();
1980 if ($#fields > -1) {
1981 my $field = $fields[0];
1982 my $tag = $field->tag();
1983 foreach my $subfield ($field->subfields()) {
1984 if (defined $subfield->[1] and
1985 $subfield->[1] ne '' and
1986 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1987 push @$subfields, $subfield->[0] => $subfield->[1];
1994 =head2 _get_unlinked_subfields_xml
1996 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2000 sub _get_unlinked_subfields_xml {
2001 my $unlinked_item_subfields = shift;
2004 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2005 my $marc = MARC::Record->new();
2006 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2007 # used in the framework
2008 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2009 $marc->encoding("UTF-8");
2010 $xml = $marc->as_xml("USMARC");
2016 =head2 _parse_unlinked_item_subfields_from_xml
2018 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2022 sub _parse_unlinked_item_subfields_from_xml {
2024 require C4::Charset;
2025 return unless defined $xml and $xml ne "";
2026 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2027 my $unlinked_subfields = [];
2028 my @fields = $marc->fields();
2029 if ($#fields > -1) {
2030 foreach my $subfield ($fields[0]->subfields()) {
2031 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2034 return $unlinked_subfields;
2037 =head2 GetAnalyticsCount
2039 $count= &GetAnalyticsCount($itemnumber)
2041 counts Usage of itemnumber in Analytical bibliorecords.
2045 sub GetAnalyticsCount {
2046 my ($itemnumber) = @_;
2048 ### ZOOM search here
2050 $query= "hi=".$itemnumber;
2051 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2052 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2056 sub _SearchItems_build_where_fragment {
2059 my $dbh = C4::Context->dbh;
2062 if (exists($filter->{conjunction})) {
2063 my (@where_strs, @where_args);
2064 foreach my $f (@{ $filter->{filters} }) {
2065 my $fragment = _SearchItems_build_where_fragment($f);
2067 push @where_strs, $fragment->{str};
2068 push @where_args, @{ $fragment->{args} };
2073 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2076 args => \@where_args,
2080 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2081 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2082 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2083 my @operators = qw(= != > < >= <= like);
2084 my $field = $filter->{field} // q{};
2085 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2086 my $op = $filter->{operator};
2087 my $query = $filter->{query};
2089 if (!$op or (0 == grep { $_ eq $op } @operators)) {
2090 $op = '='; # default operator
2094 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2096 my $marcsubfield = $2;
2097 my ($kohafield) = $dbh->selectrow_array(q|
2098 SELECT kohafield FROM marc_subfield_structure
2099 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2100 |, undef, $marcfield, $marcsubfield);
2103 $column = $kohafield;
2105 # MARC field is not linked to a DB field so we need to use
2106 # ExtractValue on marcxml from biblio_metadata or
2107 # items.more_subfields_xml, depending on the MARC field.
2110 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2111 if ($marcfield eq $itemfield) {
2112 $sqlfield = 'more_subfields_xml';
2113 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2115 $sqlfield = 'metadata'; # From biblio_metadata
2116 if ($marcfield < 10) {
2117 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2119 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2122 $column = "ExtractValue($sqlfield, '$xpath')";
2124 } elsif ($field eq 'issues') {
2125 # Consider NULL as 0 for issues count
2126 $column = 'COALESCE(issues,0)';
2131 if (ref $query eq 'ARRAY') {
2134 } elsif ($op eq '!=') {
2138 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2143 str => "$column $op ?",
2150 return $where_fragment;
2155 my ($items, $total) = SearchItems($filter, $params);
2157 Perform a search among items
2159 $filter is a reference to a hash which can be a filter, or a combination of filters.
2161 A filter has the following keys:
2165 =item * field: the name of a SQL column in table items
2167 =item * query: the value to search in this column
2169 =item * operator: comparison operator. Can be one of = != > < >= <= like
2173 A combination of filters hash the following keys:
2177 =item * conjunction: 'AND' or 'OR'
2179 =item * filters: array ref of filters
2183 $params is a reference to a hash that can contain the following parameters:
2187 =item * rows: Number of items to return. 0 returns everything (default: 0)
2189 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2192 =item * sortby: A SQL column name in items table to sort on
2194 =item * sortorder: 'ASC' or 'DESC'
2201 my ($filter, $params) = @_;
2205 return unless ref $filter eq 'HASH';
2206 return unless ref $params eq 'HASH';
2208 # Default parameters
2209 $params->{rows} ||= 0;
2210 $params->{page} ||= 1;
2211 $params->{sortby} ||= 'itemnumber';
2212 $params->{sortorder} ||= 'ASC';
2214 my ($where_str, @where_args);
2215 my $where_fragment = _SearchItems_build_where_fragment($filter);
2216 if ($where_fragment) {
2217 $where_str = $where_fragment->{str};
2218 @where_args = @{ $where_fragment->{args} };
2221 my $dbh = C4::Context->dbh;
2223 SELECT SQL_CALC_FOUND_ROWS items.*
2225 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2226 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2227 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2230 if (defined $where_str and $where_str ne '') {
2231 $query .= qq{ AND $where_str };
2234 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2235 push @where_args, C4::Context->preference('marcflavour');
2237 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2238 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2239 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2240 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2241 ? $params->{sortby} : 'itemnumber';
2242 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2243 $query .= qq{ ORDER BY $sortby $sortorder };
2245 my $rows = $params->{rows};
2248 my $offset = $rows * ($params->{page}-1);
2249 $query .= qq { LIMIT ?, ? };
2250 push @limit_args, $offset, $rows;
2253 my $sth = $dbh->prepare($query);
2254 my $rv = $sth->execute(@where_args, @limit_args);
2256 return unless ($rv);
2257 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2259 return ($sth->fetchall_arrayref({}), $total_rows);
2263 =head1 OTHER FUNCTIONS
2267 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2269 Find the given $subfield in the given $tag in the given
2270 MARC::Record $record. If the subfield is found, returns
2271 the (indicators, value) pair; otherwise, (undef, undef) is
2275 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2276 I suggest we export it from this module.
2281 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2284 if ( $tagfield < 10 ) {
2285 if ( $record->field($tagfield) ) {
2286 push @result, $record->field($tagfield)->data();
2291 foreach my $field ( $record->field($tagfield) ) {
2292 my @subfields = $field->subfields();
2293 foreach my $subfield (@subfields) {
2294 if ( @$subfield[0] eq $insubfield ) {
2295 push @result, @$subfield[1];
2296 $indicator = $field->indicator(1) . $field->indicator(2);
2301 return ( $indicator, @result );
2305 =head2 PrepareItemrecordDisplay
2307 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2309 Returns a hash with all the fields for Display a given item data in a template
2311 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2315 sub PrepareItemrecordDisplay {
2317 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2319 my $dbh = C4::Context->dbh;
2320 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2321 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2323 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2324 # a shared data structure. No plugin (including custom ones) should change
2325 # its contents. See also GetMarcStructure.
2326 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2328 # return nothing if we don't have found an existing framework.
2329 return q{} unless $tagslib;
2332 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2336 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2338 SELECT authorised_value,lib FROM authorised_values
2341 LEFT JOIN authorised_values_branches ON ( id = av_id )
2346 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2347 $query .= qq{ ORDER BY lib};
2348 my $authorised_values_sth = $dbh->prepare( $query );
2349 foreach my $tag ( sort keys %{$tagslib} ) {
2352 # loop through each subfield
2354 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2355 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2356 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2357 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2359 $subfield_data{tag} = $tag;
2360 $subfield_data{subfield} = $subfield;
2361 $subfield_data{countsubfield} = $cntsubf++;
2362 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2363 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2365 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2366 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2367 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2368 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2369 $subfield_data{hidden} = "display:none"
2370 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2371 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2372 my ( $x, $defaultvalue );
2374 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2376 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2377 if ( !defined $defaultvalue ) {
2378 $defaultvalue = q||;
2380 $defaultvalue =~ s/"/"/g;
2383 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2385 # search for itemcallnumber if applicable
2386 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2387 && C4::Context->preference('itemcallnumber') && $itemrecord) {
2388 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2389 my $CNtag = substr( $itemcn_pref, 0, 3 );
2390 next unless my $field = $itemrecord->field($CNtag);
2391 my $CNsubfields = substr( $itemcn_pref, 3 );
2392 $defaultvalue = $field->as_string( $CNsubfields, ' ');
2393 last if $defaultvalue;
2396 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2398 && $defaultvalues->{'callnumber'} ) {
2399 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2400 # if the item record exists, only use default value if the item has no callnumber
2401 $defaultvalue = $defaultvalues->{callnumber};
2402 } elsif ( !$itemrecord and $defaultvalues ) {
2403 # if the item record *doesn't* exists, always use the default value
2404 $defaultvalue = $defaultvalues->{callnumber};
2407 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2409 && $defaultvalues->{'branchcode'} ) {
2410 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2411 $defaultvalue = $defaultvalues->{branchcode};
2414 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2416 && $defaultvalues->{'location'} ) {
2418 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2419 # if the item record exists, only use default value if the item has no locationr
2420 $defaultvalue = $defaultvalues->{location};
2421 } elsif ( !$itemrecord and $defaultvalues ) {
2422 # if the item record *doesn't* exists, always use the default value
2423 $defaultvalue = $defaultvalues->{location};
2426 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2427 my @authorised_values;
2430 # builds list, depending on authorised value...
2432 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2433 if ( ( C4::Context->preference("IndependentBranches") )
2434 && !C4::Context->IsSuperLibrarian() ) {
2435 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2436 $sth->execute( C4::Context->userenv->{branch} );
2437 push @authorised_values, ""
2438 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2439 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2440 push @authorised_values, $branchcode;
2441 $authorised_lib{$branchcode} = $branchname;
2444 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2446 push @authorised_values, ""
2447 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2448 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2449 push @authorised_values, $branchcode;
2450 $authorised_lib{$branchcode} = $branchname;
2454 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2455 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2456 $defaultvalue = $defaultvalues->{branchcode};
2460 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2461 my $itemtypes = Koha::ItemTypes->search_with_localization;
2462 push @authorised_values, ""
2463 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2464 while ( my $itemtype = $itemtypes->next ) {
2465 push @authorised_values, $itemtype->itemtype;
2466 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2468 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2469 $defaultvalue = $defaultvalues->{'itemtype'};
2473 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2474 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2476 my $class_sources = GetClassSources();
2477 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2479 foreach my $class_source (sort keys %$class_sources) {
2480 next unless $class_sources->{$class_source}->{'used'} or
2481 ($class_source eq $default_source);
2482 push @authorised_values, $class_source;
2483 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2486 $defaultvalue = $default_source;
2488 #---- "true" authorised value
2490 $authorised_values_sth->execute(
2491 $tagslib->{$tag}->{$subfield}->{authorised_value},
2492 $branch_limit ? $branch_limit : ()
2494 push @authorised_values, ""
2495 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2496 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2497 push @authorised_values, $value;
2498 $authorised_lib{$value} = $lib;
2501 $subfield_data{marc_value} = {
2503 values => \@authorised_values,
2504 default => $defaultvalue // q{},
2505 labels => \%authorised_lib,
2507 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2509 require Koha::FrameworkPlugin;
2510 my $plugin = Koha::FrameworkPlugin->new({
2511 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2514 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2515 $plugin->build( $pars );
2516 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2517 $defaultvalue = $field->subfield($subfield) || q{};
2519 if( !$plugin->errstr ) {
2520 #TODO Move html to template; see report 12176/13397
2521 my $tab= $plugin->noclick? '-1': '';
2522 my $class= $plugin->noclick? ' disabled': '';
2523 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2524 $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;
2526 warn $plugin->errstr;
2527 $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
2530 elsif ( $tag eq '' ) { # it's an hidden field
2531 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2533 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2534 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2536 elsif ( length($defaultvalue) > 100
2537 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2538 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2539 or (C4::Context->preference("marcflavour") eq "MARC21" and
2540 500 <= $tag && $tag < 600 )
2542 # oversize field (textarea)
2543 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2545 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2547 push( @loop_data, \%subfield_data );
2552 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2553 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2556 'itemtagfield' => $itemtagfield,
2557 'itemtagsubfield' => $itemtagsubfield,
2558 'itemnumber' => $itemnumber,
2559 'iteminformation' => \@loop_data
2563 sub ToggleNewStatus {
2564 my ( $params ) = @_;
2565 my @rules = @{ $params->{rules} };
2566 my $report_only = $params->{report_only};
2568 my $dbh = C4::Context->dbh;
2570 my @item_columns = map { "items.$_" } Koha::Items->columns;
2571 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2573 for my $rule ( @rules ) {
2574 my $age = $rule->{age};
2575 my $conditions = $rule->{conditions};
2576 my $substitutions = $rule->{substitutions};
2577 foreach ( @$substitutions ) {
2578 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2585 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2588 for my $condition ( @$conditions ) {
2590 grep { $_ eq $condition->{field} } @item_columns
2591 or grep { $_ eq $condition->{field} } @biblioitem_columns
2593 if ( $condition->{value} =~ /\|/ ) {
2594 my @values = split /\|/, $condition->{value};
2595 $query .= qq| AND $condition->{field} IN (|
2596 . join( ',', ('?') x scalar @values )
2598 push @params, @values;
2600 $query .= qq| AND $condition->{field} = ?|;
2601 push @params, $condition->{value};
2605 if ( defined $age ) {
2606 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2609 my $sth = $dbh->prepare($query);
2610 $sth->execute( @params );
2611 while ( my $values = $sth->fetchrow_hashref ) {
2612 my $biblionumber = $values->{biblionumber};
2613 my $itemnumber = $values->{itemnumber};
2614 for my $substitution ( @$substitutions ) {
2615 next unless $substitution->{field};
2616 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2617 C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
2618 unless $report_only;
2619 push @{ $report->{$itemnumber} }, $substitution;
2627 =head2 _after_item_action_hooks
2629 Helper method that takes care of calling all plugin hooks
2633 sub _after_item_action_hooks {
2636 my $item_id = $args->{item_id};
2637 my $action = $args->{action};
2639 if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
2641 my @plugins = Koha::Plugins->new->GetPlugins({
2642 method => 'after_item_action',
2647 my $item = Koha::Items->find( $item_id );
2649 foreach my $plugin ( @plugins ) {
2651 $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });