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 $item->location($item->permanent_location)->store;
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) );
162 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
163 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
164 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
165 $item_values->{biblionumber} = $biblionumber;
166 my $item = Koha::Item->new( $item_values )->store;
167 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
170 =head2 AddItemBatchFromMarc
172 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
173 $biblionumber, $biblioitemnumber, $frameworkcode);
175 Efficiently create item records from a MARC biblio record with
176 embedded item fields. This routine is suitable for batch jobs.
178 This API assumes that the bib record has already been
179 saved to the C<biblio> and C<biblioitems> tables. It does
180 not expect that C<biblio_metadata.metadata> is populated, but it
181 will do so via a call to ModBibiloMarc.
183 The goal of this API is to have a similar effect to using AddBiblio
184 and AddItems in succession, but without inefficient repeated
185 parsing of the MARC XML bib record.
187 This function returns an arrayref of new itemsnumbers and an arrayref of item
188 errors encountered during the processing. Each entry in the errors
189 list is a hashref containing the following keys:
195 Sequence number of original item tag in the MARC record.
199 Item barcode, provide to assist in the construction of
200 useful error messages.
204 Code representing the error condition. Can be 'duplicate_barcode',
205 'invalid_homebranch', or 'invalid_holdingbranch'.
207 =item error_information
209 Additional information appropriate to the error condition.
215 sub AddItemBatchFromMarc {
216 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
218 my @itemnumbers = ();
220 my $dbh = C4::Context->dbh;
222 # We modify the record, so lets work on a clone so we don't change the
224 $record = $record->clone();
225 # loop through the item tags and start creating items
226 my @bad_item_fields = ();
227 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
228 my $item_sequence_num = 0;
229 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
230 $item_sequence_num++;
231 # we take the item field and stick it into a new
232 # MARC record -- this is required so far because (FIXME)
233 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
234 # and there is no TransformMarcFieldToKoha
235 my $temp_item_marc = MARC::Record->new();
236 $temp_item_marc->append_fields($item_field);
238 # add biblionumber and biblioitemnumber
239 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
240 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
241 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
242 $item->{'biblionumber'} = $biblionumber;
243 $item->{'biblioitemnumber'} = $biblioitemnumber;
245 # check for duplicate barcode
246 my %item_errors = CheckItemPreSave($item);
248 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
249 push @bad_item_fields, $item_field;
253 _set_derived_columns_for_add($item);
254 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
255 warn $error if $error;
256 push @itemnumbers, $itemnumber; # FIXME not checking error
257 $item->{'itemnumber'} = $itemnumber;
259 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
261 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
262 $item_field->replace_with($new_item_marc->field($itemtag));
265 # remove any MARC item fields for rejected items
266 foreach my $item_field (@bad_item_fields) {
267 $record->delete_field($item_field);
270 # update the MARC biblio
271 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
273 return (\@itemnumbers, \@errors);
276 =head2 ModItemFromMarc
278 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
280 This function updates an item record based on a supplied
281 C<MARC::Record> object containing an embedded item field.
282 This API is meant for the use of C<additem.pl>; for
283 other purposes, C<ModItem> should be used.
285 This function uses the hash %default_values_for_mod_from_marc,
286 which contains default values for item fields to
287 apply when modifying an item. This is needed because
288 if an item field's value is cleared, TransformMarcToKoha
289 does not include the column in the
290 hash that's passed to ModItem, which without
291 use of this hash makes it impossible to clear
292 an item field's value. See bug 2466.
294 Note that only columns that can be directly
295 changed from the cataloging and serials
296 item editors are included in this hash.
302 sub _build_default_values_for_mod_marc {
303 # Has no framework parameter anymore, since Default is authoritative
304 # for Koha to MARC mappings.
306 my $cache = Koha::Caches->get_instance();
307 my $cache_key = "default_value_for_mod_marc-";
308 my $cached = $cache->get_from_cache($cache_key);
309 return $cached if $cached;
311 my $default_values = {
313 booksellerid => undef,
315 'items.cn_source' => undef,
316 coded_location_qualifier => undef,
320 holdingbranch => undef,
322 itemcallnumber => undef,
325 itemnotes_nonpublic => undef,
328 permanent_location => undef,
333 replacementprice => undef,
334 replacementpricedate => undef,
337 stocknumber => undef,
341 my %default_values_for_mod_from_marc;
342 while ( my ( $field, $default_value ) = each %$default_values ) {
343 my $kohafield = $field;
344 $kohafield =~ s|^([^\.]+)$|items.$1|;
345 $default_values_for_mod_from_marc{$field} = $default_value
346 if C4::Biblio::GetMarcFromKohaField( $kohafield );
349 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
350 return \%default_values_for_mod_from_marc;
353 sub ModItemFromMarc {
354 my $item_marc = shift;
355 my $biblionumber = shift;
356 my $itemnumber = shift;
358 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
359 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
361 my $localitemmarc = MARC::Record->new;
362 $localitemmarc->append_fields( $item_marc->field($itemtag) );
363 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
364 my $default_values = _build_default_values_for_mod_marc();
365 foreach my $item_field ( keys %$default_values ) {
366 $item->{$item_field} = $default_values->{$item_field}
367 unless exists $item->{$item_field};
369 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
371 my $item_object = Koha::Items->find($itemnumber);
372 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
374 return $item_object->get_from_storage->unblessed;
380 { column => $newvalue },
384 [ unlinked_item_subfields => $unlinked_item_subfields, ]
389 Change one or more columns in an item record.
391 The first argument is a hashref mapping from item column
392 names to the new values. The second and third arguments
393 are the biblionumber and itemnumber, respectively.
394 The fourth, optional parameter (additional_params) may contain the keys
395 unlinked_item_subfields and log_action.
397 C<$unlinked_item_subfields> contains an arrayref containing
398 subfields present in the original MARC
399 representation of the item (e.g., from the item editor) that are
400 not mapped to C<items> columns directly but should instead
401 be stored in C<items.more_subfields_xml> and included in
402 the biblio items tag for display and indexing.
404 If one of the changed columns is used to calculate
405 the derived value of a column such as C<items.cn_sort>,
406 this routine will perform the necessary calculation
409 If log_action is set to false, the action will not be logged.
410 If log_action is true or undefined, the action will be logged.
415 my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
416 my $log_action = $additional_params->{log_action} // 1;
417 my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
419 return unless %$item;
420 $item->{'itemnumber'} = $itemnumber or return;
422 # if $biblionumber is undefined, get it from the current item
423 unless (defined $biblionumber) {
424 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
427 if ($unlinked_item_subfields) {
428 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
431 my @fields = qw( itemlost withdrawn damaged );
433 # Only retrieve the item if we need to set an "on" date field
434 if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
435 my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
436 for my $field (@fields) {
437 if ( defined( $item->{$field} )
438 and not $pre_mod_item->$field
439 and $item->{$field} )
441 $item->{ $field . '_on' } =
442 DateTime::Format::MySQL->format_datetime( dt_from_string() );
447 # If the field is defined but empty, we are removing and,
448 # and thus need to clear out the 'on' field as well
449 for my $field (@fields) {
450 if ( defined( $item->{$field} ) && !$item->{$field} ) {
451 $item->{ $field . '_on' } = undef;
456 _set_derived_columns_for_mod($item);
457 _do_column_fixes_for_mod($item);
460 # attempt to change itemnumber
461 # attempt to change biblionumber (if we want
462 # an API to relink an item to a different bib,
463 # it should be a separate function)
466 _koha_modify_item($item);
468 # request that bib be reindexed so that searching on current
469 # item status is possible
470 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
472 _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
474 logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
475 if $log_action && C4::Context->preference("CataloguingLog");
478 =head2 ModItemTransfer
480 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
482 Marks an item as being transferred from one branch to another and records the trigger.
486 sub ModItemTransfer {
487 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
489 my $dbh = C4::Context->dbh;
490 my $item = Koha::Items->find( $itemnumber );
492 # Remove the 'shelving cart' location status if it is being used.
493 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
495 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
497 #new entry in branchtransfers....
498 my $sth = $dbh->prepare(
499 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
500 VALUES (?, ?, NOW(), ?, ?)");
501 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
503 # FIXME we are fetching the item twice in the 2 next statements!
504 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
505 ModDateLastSeen($itemnumber);
509 =head2 ModDateLastSeen
511 ModDateLastSeen( $itemnumber, $leave_item_lost );
513 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
514 C<$itemnumber> is the item number
515 C<$leave_item_lost> determines if a lost item will be found or remain lost
519 sub ModDateLastSeen {
520 my ( $itemnumber, $leave_item_lost ) = @_;
522 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
524 my $item = Koha::Items->find($itemnumber);
525 $item->datelastseen($today);
526 $item->itemlost(0) unless $leave_item_lost;
527 $item->store({ log_action => 0 });
532 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
534 Exported function (core API) for deleting an item record in Koha.
541 my $itemnumber = $params->{itemnumber};
542 my $biblionumber = $params->{biblionumber};
544 unless ($biblionumber) {
545 my $item = Koha::Items->find( $itemnumber );
546 $biblionumber = $item ? $item->biblio->biblionumber : undef;
549 # If there is no biblionumber for the given itemnumber, there is nothing to delete
550 return 0 unless $biblionumber;
552 # FIXME check the item has no current issues
553 my $deleted = _koha_delete_item( $itemnumber );
555 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
557 _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
559 #search item field code
560 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
564 =head2 CheckItemPreSave
566 my $item_ref = TransformMarcToKoha($marc, 'items');
568 my %errors = CheckItemPreSave($item_ref);
569 if (exists $errors{'duplicate_barcode'}) {
570 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
571 } elsif (exists $errors{'invalid_homebranch'}) {
572 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
573 } elsif (exists $errors{'invalid_holdingbranch'}) {
574 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
579 Given a hashref containing item fields, determine if it can be
580 inserted or updated in the database. Specifically, checks for
581 database integrity issues, and returns a hash containing any
582 of the following keys, if applicable.
586 =item duplicate_barcode
588 Barcode, if it duplicates one already found in the database.
590 =item invalid_homebranch
592 Home branch, if not defined in branches table.
594 =item invalid_holdingbranch
596 Holding branch, if not defined in branches table.
600 This function does NOT implement any policy-related checks,
601 e.g., whether current operator is allowed to save an
602 item that has a given branch code.
606 sub CheckItemPreSave {
607 my $item_ref = shift;
611 # check for duplicate barcode
612 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
613 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
614 if ($existing_item) {
615 if (!exists $item_ref->{'itemnumber'} # new item
616 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
617 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
622 # check for valid home branch
623 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
624 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
625 unless (defined $home_library) {
626 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
630 # check for valid holding branch
631 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
632 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
633 unless (defined $holding_library) {
634 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
642 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
644 The following functions provide various ways of
645 getting an item record, a set of item records, or
646 lists of authorized values for certain item fields.
650 =head2 GetItemsForInventory
652 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
653 minlocation => $minlocation,
654 maxlocation => $maxlocation,
655 location => $location,
656 itemtype => $itemtype,
657 ignoreissued => $ignoreissued,
658 datelastseen => $datelastseen,
659 branchcode => $branchcode,
663 statushash => $statushash,
666 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
668 The sub returns a reference to a list of hashes, each containing
669 itemnumber, author, title, barcode, item callnumber, and date last
670 seen. It is ordered by callnumber then title.
672 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
673 the datelastseen can be used to specify that you want to see items not seen since a past date only.
674 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
675 $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.
677 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
681 sub GetItemsForInventory {
682 my ( $parameters ) = @_;
683 my $minlocation = $parameters->{'minlocation'} // '';
684 my $maxlocation = $parameters->{'maxlocation'} // '';
685 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
686 my $location = $parameters->{'location'} // '';
687 my $itemtype = $parameters->{'itemtype'} // '';
688 my $ignoreissued = $parameters->{'ignoreissued'} // '';
689 my $datelastseen = $parameters->{'datelastseen'} // '';
690 my $branchcode = $parameters->{'branchcode'} // '';
691 my $branch = $parameters->{'branch'} // '';
692 my $offset = $parameters->{'offset'} // '';
693 my $size = $parameters->{'size'} // '';
694 my $statushash = $parameters->{'statushash'} // '';
695 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
697 my $dbh = C4::Context->dbh;
698 my ( @bind_params, @where_strings );
700 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
701 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
703 my $select_columns = q{
704 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
706 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
709 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
710 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
713 for my $authvfield (keys %$statushash){
714 if ( scalar @{$statushash->{$authvfield}} > 0 ){
715 my $joinedvals = join ',', @{$statushash->{$authvfield}};
716 push @where_strings, "$authvfield in (" . $joinedvals . ")";
722 push @where_strings, 'items.cn_sort >= ?';
723 push @bind_params, $min_cnsort;
727 push @where_strings, 'items.cn_sort <= ?';
728 push @bind_params, $max_cnsort;
732 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
733 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
734 push @bind_params, $datelastseen;
738 push @where_strings, 'items.location = ?';
739 push @bind_params, $location;
743 if($branch eq "homebranch"){
744 push @where_strings, 'items.homebranch = ?';
746 push @where_strings, 'items.holdingbranch = ?';
748 push @bind_params, $branchcode;
752 push @where_strings, 'biblioitems.itemtype = ?';
753 push @bind_params, $itemtype;
756 if ( $ignoreissued) {
757 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
758 push @where_strings, 'issues.date_due IS NULL';
761 if ( $ignore_waiting_holds ) {
762 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
763 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
766 if ( @where_strings ) {
768 $query .= join ' AND ', @where_strings;
770 my $count_query = $select_count . $query;
771 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
772 $query .= " LIMIT $offset, $size" if ($offset and $size);
773 $query = $select_columns . $query;
774 my $sth = $dbh->prepare($query);
775 $sth->execute( @bind_params );
778 my $tmpresults = $sth->fetchall_arrayref({});
779 $sth = $dbh->prepare( $count_query );
780 $sth->execute( @bind_params );
781 my ($iTotalRecords) = $sth->fetchrow_array();
783 my @avs = Koha::AuthorisedValues->search(
784 { 'marc_subfield_structures.kohafield' => { '>' => '' },
785 'me.authorised_value' => { '>' => '' },
787 { join => { category => 'marc_subfield_structures' },
788 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
789 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
790 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
794 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
796 foreach my $row (@$tmpresults) {
799 foreach (keys %$row) {
802 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
805 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
811 return (\@results, $iTotalRecords);
816 @results = GetItemsInfo($biblionumber);
818 Returns information about items with the given biblionumber.
820 C<GetItemsInfo> returns a list of references-to-hash. Each element
821 contains a number of keys. Most of them are attributes from the
822 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
823 Koha database. Other keys include:
827 =item C<$data-E<gt>{branchname}>
829 The name (not the code) of the branch to which the book belongs.
831 =item C<$data-E<gt>{datelastseen}>
833 This is simply C<items.datelastseen>, except that while the date is
834 stored in YYYY-MM-DD format in the database, here it is converted to
835 DD/MM/YYYY format. A NULL date is returned as C<//>.
837 =item C<$data-E<gt>{datedue}>
839 =item C<$data-E<gt>{class}>
841 This is the concatenation of C<biblioitems.classification>, the book's
842 Dewey code, and C<biblioitems.subclass>.
844 =item C<$data-E<gt>{ocount}>
846 I think this is the number of copies of the book available.
848 =item C<$data-E<gt>{order}>
850 If this is set, it is set to C<One Order>.
857 my ( $biblionumber ) = @_;
858 my $dbh = C4::Context->dbh;
859 require C4::Languages;
860 my $language = C4::Languages::getlanguage();
866 biblioitems.itemtype,
869 biblioitems.publicationyear,
870 biblioitems.publishercode,
871 biblioitems.volumedate,
872 biblioitems.volumedesc,
875 items.notforloan as itemnotforloan,
876 issues.borrowernumber,
877 issues.date_due as datedue,
878 issues.onsite_checkout,
879 borrowers.cardnumber,
882 borrowers.branchcode as bcode,
884 serial.publisheddate,
885 itemtypes.description,
886 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
887 itemtypes.notforloan as notforloan_per_itemtype,
891 holding.opac_info as holding_branch_opac_info,
892 home.opac_info as home_branch_opac_info,
893 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
895 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
896 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
897 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
898 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
899 LEFT JOIN issues USING (itemnumber)
900 LEFT JOIN borrowers USING (borrowernumber)
901 LEFT JOIN serialitems USING (itemnumber)
902 LEFT JOIN serial USING (serialid)
903 LEFT JOIN itemtypes ON itemtypes.itemtype = "
904 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
906 LEFT JOIN tmp_holdsqueue USING (itemnumber)
907 LEFT JOIN localization ON itemtypes.itemtype = localization.code
908 AND localization.entity = 'itemtypes'
909 AND localization.lang = ?
912 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
913 my $sth = $dbh->prepare($query);
914 $sth->execute($language, $biblionumber);
919 my $userenv = C4::Context->userenv;
920 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
921 while ( my $data = $sth->fetchrow_hashref ) {
922 if ( $data->{borrowernumber} && $want_not_same_branch) {
923 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
926 $serial ||= $data->{'serial'};
929 # get notforloan complete status if applicable
930 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
931 $data->{notforloanvalue} = $descriptions->{lib} // '';
932 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
934 # get restricted status and description if applicable
935 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
936 $data->{restrictedvalue} = $descriptions->{lib} // '';
937 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
939 # my stack procedures
940 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
941 $data->{stack} = $descriptions->{lib} // '';
943 # Find the last 3 people who borrowed this item.
944 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
946 AND old_issues.borrowernumber = borrowers.borrowernumber
947 ORDER BY returndate DESC
949 $sth2->execute($data->{'itemnumber'});
951 while (my $data2 = $sth2->fetchrow_hashref()) {
952 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
953 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
954 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
958 $results[$i] = $data;
963 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
967 =head2 GetItemsLocationInfo
969 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
971 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
973 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
977 =item C<$data-E<gt>{homebranch}>
979 Branch Name of the item's homebranch
981 =item C<$data-E<gt>{holdingbranch}>
983 Branch Name of the item's holdingbranch
985 =item C<$data-E<gt>{location}>
987 Item's shelving location code
989 =item C<$data-E<gt>{location_intranet}>
991 The intranet description for the Shelving Location as set in authorised_values 'LOC'
993 =item C<$data-E<gt>{location_opac}>
995 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
998 =item C<$data-E<gt>{itemcallnumber}>
1000 Item's itemcallnumber
1002 =item C<$data-E<gt>{cn_sort}>
1004 Item's call number normalized for sorting
1010 sub GetItemsLocationInfo {
1011 my $biblionumber = shift;
1014 my $dbh = C4::Context->dbh;
1015 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1016 location, itemcallnumber, cn_sort
1017 FROM items, branches as a, branches as b
1018 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1019 AND biblionumber = ?
1020 ORDER BY cn_sort ASC";
1021 my $sth = $dbh->prepare($query);
1022 $sth->execute($biblionumber);
1024 while ( my $data = $sth->fetchrow_hashref ) {
1025 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1026 $av = $av->count ? $av->next : undef;
1027 $data->{location_intranet} = $av ? $av->lib : '';
1028 $data->{location_opac} = $av ? $av->opac_description : '';
1029 push @results, $data;
1034 =head2 GetHostItemsInfo
1036 $hostiteminfo = GetHostItemsInfo($hostfield);
1037 Returns the iteminfo for items linked to records via a host field
1041 sub GetHostItemsInfo {
1043 my @returnitemsInfo;
1045 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1046 return @returnitemsInfo;
1050 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1051 C4::Context->preference('marcflavour') eq 'NORMARC') {
1052 @fields = $record->field('773');
1053 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1054 @fields = $record->field('461');
1057 foreach my $hostfield ( @fields ) {
1058 my $hostbiblionumber = $hostfield->subfield("0");
1059 my $linkeditemnumber = $hostfield->subfield("9");
1060 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1061 foreach my $hostitemInfo (@hostitemInfos) {
1062 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1063 push @returnitemsInfo, $hostitemInfo;
1068 return @returnitemsInfo;
1071 =head2 get_hostitemnumbers_of
1073 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1075 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1077 Return a reference on a hash where key is a biblionumber and values are
1078 references on array of itemnumbers.
1083 sub get_hostitemnumbers_of {
1084 my ($biblionumber) = @_;
1086 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1090 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1091 return unless $marcrecord;
1093 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1095 my $marcflavor = C4::Context->preference('marcflavour');
1096 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1101 elsif ( $marcflavor eq 'UNIMARC' ) {
1107 foreach my $hostfield ( $marcrecord->field($tag) ) {
1108 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1109 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1110 my $linkeditemnumber = $hostfield->subfield($item_s);
1111 if ( ! $linkeditemnumber ) {
1112 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1115 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1116 push @returnhostitemnumbers, $linkeditemnumber
1120 return @returnhostitemnumbers;
1123 =head2 GetHiddenItemnumbers
1125 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1127 Given a list of items it checks which should be hidden from the OPAC given
1128 the current configuration. Returns a list of itemnumbers corresponding to
1129 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1134 sub GetHiddenItemnumbers {
1136 my $items = $params->{items};
1137 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1138 foreach my $except (split(/\|/, $exceptions)){
1139 if ($params->{'borcat'} eq $except){
1140 return; # we don't hide anything for this borrower category
1146 my $yaml = C4::Context->preference('OpacHiddenItems');
1147 return () if (! $yaml =~ /\S/ );
1148 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1151 $hidingrules = YAML::Load($yaml);
1154 warn "Unable to parse OpacHiddenItems syspref : $@";
1157 my $dbh = C4::Context->dbh;
1160 foreach my $item (@$items) {
1162 # We check each rule
1163 foreach my $field (keys %$hidingrules) {
1165 if (exists $item->{$field}) {
1166 $val = $item->{$field};
1169 my $query = "SELECT $field from items where itemnumber = ?";
1170 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1172 $val = '' unless defined $val;
1174 # If the results matches the values in the yaml file
1175 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1177 # We add the itemnumber to the list
1178 push @resultitems, $item->{'itemnumber'};
1180 # If at least one rule matched for an item, no need to test the others
1185 return @resultitems;
1188 =head1 LIMITED USE FUNCTIONS
1190 The following functions, while part of the public API,
1191 are not exported. This is generally because they are
1192 meant to be used by only one script for a specific
1193 purpose, and should not be used in any other context
1194 without careful thought.
1200 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1202 Returns MARC::Record of the item passed in parameter.
1203 This function is meant for use only in C<cataloguing/additem.pl>,
1204 where it is needed to support that script's MARC-like
1210 my ( $biblionumber, $itemnumber ) = @_;
1212 # GetMarcItem has been revised so that it does the following:
1213 # 1. Gets the item information from the items table.
1214 # 2. Converts it to a MARC field for storage in the bib record.
1216 # The previous behavior was:
1217 # 1. Get the bib record.
1218 # 2. Return the MARC tag corresponding to the item record.
1220 # The difference is that one treats the items row as authoritative,
1221 # while the other treats the MARC representation as authoritative
1222 # under certain circumstances.
1224 my $item = Koha::Items->find($itemnumber) or return;
1226 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1227 # Also, don't emit a subfield if the underlying field is blank.
1229 return Item2Marc($item->unblessed, $biblionumber);
1233 my ($itemrecord,$biblionumber)=@_;
1236 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1237 } keys %{ $itemrecord }
1239 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1240 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1241 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1242 "items.itemnumber", $framework,
1245 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1246 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1247 foreach my $field ($itemmarc->field($itemtag)){
1248 $field->add_subfields(@$unlinked_item_subfields);
1254 =head1 PRIVATE FUNCTIONS AND VARIABLES
1256 The following functions are not meant to be called
1257 directly, but are documented in order to explain
1258 the inner workings of C<C4::Items>.
1262 =head2 %derived_columns
1264 This hash keeps track of item columns that
1265 are strictly derived from other columns in
1266 the item record and are not meant to be set
1269 Each key in the hash should be the name of a
1270 column (as named by TransformMarcToKoha). Each
1271 value should be hashref whose keys are the
1272 columns on which the derived column depends. The
1273 hashref should also contain a 'BUILDER' key
1274 that is a reference to a sub that calculates
1279 my %derived_columns = (
1280 'items.cn_sort' => {
1281 'itemcallnumber' => 1,
1282 'items.cn_source' => 1,
1283 'BUILDER' => \&_calc_items_cn_sort,
1287 =head2 _set_derived_columns_for_add
1289 _set_derived_column_for_add($item);
1291 Given an item hash representing a new item to be added,
1292 calculate any derived columns. Currently the only
1293 such column is C<items.cn_sort>.
1297 sub _set_derived_columns_for_add {
1300 foreach my $column (keys %derived_columns) {
1301 my $builder = $derived_columns{$column}->{'BUILDER'};
1302 my $source_values = {};
1303 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1304 next if $source_column eq 'BUILDER';
1305 $source_values->{$source_column} = $item->{$source_column};
1307 $builder->($item, $source_values);
1311 =head2 _set_derived_columns_for_mod
1313 _set_derived_column_for_mod($item);
1315 Given an item hash representing a new item to be modified.
1316 calculate any derived columns. Currently the only
1317 such column is C<items.cn_sort>.
1319 This routine differs from C<_set_derived_columns_for_add>
1320 in that it needs to handle partial item records. In other
1321 words, the caller of C<ModItem> may have supplied only one
1322 or two columns to be changed, so this function needs to
1323 determine whether any of the columns to be changed affect
1324 any of the derived columns. Also, if a derived column
1325 depends on more than one column, but the caller is not
1326 changing all of then, this routine retrieves the unchanged
1327 values from the database in order to ensure a correct
1332 sub _set_derived_columns_for_mod {
1335 foreach my $column (keys %derived_columns) {
1336 my $builder = $derived_columns{$column}->{'BUILDER'};
1337 my $source_values = {};
1338 my %missing_sources = ();
1339 my $must_recalc = 0;
1340 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1341 next if $source_column eq 'BUILDER';
1342 if (exists $item->{$source_column}) {
1344 $source_values->{$source_column} = $item->{$source_column};
1346 $missing_sources{$source_column} = 1;
1350 foreach my $source_column (keys %missing_sources) {
1351 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1353 $builder->($item, $source_values);
1358 =head2 _do_column_fixes_for_mod
1360 _do_column_fixes_for_mod($item);
1362 Given an item hashref containing one or more
1363 columns to modify, fix up certain values.
1364 Specifically, set to 0 any passed value
1365 of C<notforloan>, C<damaged>, C<itemlost>, or
1366 C<withdrawn> that is either undefined or
1367 contains the empty string.
1371 sub _do_column_fixes_for_mod {
1374 if (exists $item->{'notforloan'} and
1375 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1376 $item->{'notforloan'} = 0;
1378 if (exists $item->{'damaged'} and
1379 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1380 $item->{'damaged'} = 0;
1382 if (exists $item->{'itemlost'} and
1383 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1384 $item->{'itemlost'} = 0;
1386 if (exists $item->{'withdrawn'} and
1387 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1388 $item->{'withdrawn'} = 0;
1391 exists $item->{location}
1392 and ( !defined $item->{location}
1393 || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
1394 and not $item->{permanent_location}
1397 $item->{'permanent_location'} = $item->{'location'};
1399 if (exists $item->{'timestamp'}) {
1400 delete $item->{'timestamp'};
1404 =head2 _get_single_item_column
1406 _get_single_item_column($column, $itemnumber);
1408 Retrieves the value of a single column from an C<items>
1409 row specified by C<$itemnumber>.
1413 sub _get_single_item_column {
1415 my $itemnumber = shift;
1417 my $dbh = C4::Context->dbh;
1418 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1419 $sth->execute($itemnumber);
1420 my ($value) = $sth->fetchrow();
1424 =head2 _calc_items_cn_sort
1426 _calc_items_cn_sort($item, $source_values);
1428 Helper routine to calculate C<items.cn_sort>.
1432 sub _calc_items_cn_sort {
1434 my $source_values = shift;
1436 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1439 =head2 _koha_new_item
1441 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1443 Perform the actual insert into the C<items> table.
1447 sub _koha_new_item {
1448 my ( $item, $barcode ) = @_;
1449 my $dbh=C4::Context->dbh;
1451 $item->{permanent_location} //= $item->{location};
1452 _mod_item_dates( $item );
1454 "INSERT INTO items SET
1456 biblioitemnumber = ?,
1458 dateaccessioned = ?,
1462 replacementprice = ?,
1463 replacementpricedate = ?,
1464 datelastborrowed = ?,
1472 coded_location_qualifier = ?,
1475 itemnotes_nonpublic = ?,
1478 permanent_location = ?,
1490 more_subfields_xml = ?,
1495 my $sth = $dbh->prepare($query);
1496 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1498 $item->{'biblionumber'},
1499 $item->{'biblioitemnumber'},
1501 $item->{'dateaccessioned'},
1502 $item->{'booksellerid'},
1503 $item->{'homebranch'},
1505 $item->{'replacementprice'},
1506 $item->{'replacementpricedate'} || $today,
1507 $item->{datelastborrowed},
1508 $item->{datelastseen} || $today,
1510 $item->{'notforloan'},
1512 $item->{'itemlost'},
1513 $item->{'withdrawn'},
1514 $item->{'itemcallnumber'},
1515 $item->{'coded_location_qualifier'},
1516 $item->{'restricted'},
1517 $item->{'itemnotes'},
1518 $item->{'itemnotes_nonpublic'},
1519 $item->{'holdingbranch'},
1520 $item->{'location'},
1521 $item->{'permanent_location'},
1524 $item->{'renewals'},
1525 $item->{'reserves'},
1526 $item->{'items.cn_source'},
1527 $item->{'items.cn_sort'},
1530 $item->{'materials'},
1532 $item->{'enumchron'},
1533 $item->{'more_subfields_xml'},
1534 $item->{'copynumber'},
1535 $item->{'stocknumber'},
1536 $item->{'new_status'},
1540 if ( defined $sth->errstr ) {
1541 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1544 $itemnumber = $dbh->{'mysql_insertid'};
1547 return ( $itemnumber, $error );
1550 =head2 MoveItemFromBiblio
1552 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1554 Moves an item from a biblio to another
1556 Returns undef if the move failed or the biblionumber of the destination record otherwise
1560 sub MoveItemFromBiblio {
1561 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1562 my $dbh = C4::Context->dbh;
1563 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1564 SELECT biblioitemnumber
1566 WHERE biblionumber = ?
1567 |, undef, $tobiblio );
1568 my $return = $dbh->do(q|
1570 SET biblioitemnumber = ?,
1572 WHERE itemnumber = ?
1573 AND biblionumber = ?
1574 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1576 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1577 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1578 # Checking if the item we want to move is in an order
1579 require C4::Acquisition;
1580 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1582 # Replacing the biblionumber within the order if necessary
1583 $order->{'biblionumber'} = $tobiblio;
1584 C4::Acquisition::ModOrder($order);
1587 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1588 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1591 SET biblionumber = ?
1592 WHERE itemnumber = ?
1593 |, undef, $tobiblio, $itemnumber );
1600 =head2 ItemSafeToDelete
1602 ItemSafeToDelete( $biblionumber, $itemnumber);
1604 Exported function (core API) for checking whether an item record is safe to delete.
1606 returns 1 if the item is safe to delete,
1608 "book_on_loan" if the item is checked out,
1610 "not_same_branch" if the item is blocked by independent branches,
1612 "book_reserved" if the there are holds aganst the item, or
1614 "linked_analytics" if the item has linked analytic records.
1618 sub ItemSafeToDelete {
1619 my ( $biblionumber, $itemnumber ) = @_;
1621 my $dbh = C4::Context->dbh;
1625 my $countanalytics = GetAnalyticsCount($itemnumber);
1627 my $item = Koha::Items->find($itemnumber) or return;
1629 if ($item->checkout) {
1630 $status = "book_on_loan";
1632 elsif ( defined C4::Context->userenv
1633 and !C4::Context->IsSuperLibrarian()
1634 and C4::Context->preference("IndependentBranches")
1635 and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1637 $status = "not_same_branch";
1640 # check it doesn't have a waiting reserve
1641 my $sth = $dbh->prepare(
1643 SELECT COUNT(*) FROM reserves
1644 WHERE (found = 'W' OR found = 'T')
1648 $sth->execute($itemnumber);
1649 my ($reserve) = $sth->fetchrow;
1651 $status = "book_reserved";
1653 elsif ( $countanalytics > 0 ) {
1654 $status = "linked_analytics";
1665 DelItemCheck( $biblionumber, $itemnumber);
1667 Exported function (core API) for deleting an item record in Koha if there no current issue.
1669 DelItemCheck wraps ItemSafeToDelete around DelItem.
1674 my ( $biblionumber, $itemnumber ) = @_;
1675 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1677 if ( $status == 1 ) {
1680 biblionumber => $biblionumber,
1681 itemnumber => $itemnumber
1688 =head2 _koha_modify_item
1690 my ($itemnumber,$error) =_koha_modify_item( $item );
1692 Perform the actual update of the C<items> row. Note that this
1693 routine accepts a hashref specifying the columns to update.
1697 sub _koha_modify_item {
1699 my $dbh=C4::Context->dbh;
1702 my $query = "UPDATE items SET ";
1704 _mod_item_dates( $item );
1705 for my $key ( keys %$item ) {
1706 next if ( $key eq 'itemnumber' );
1708 push @bind, $item->{$key};
1711 $query .= " WHERE itemnumber=?";
1712 push @bind, $item->{'itemnumber'};
1713 my $sth = $dbh->prepare($query);
1714 $sth->execute(@bind);
1716 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1719 return ($item->{'itemnumber'},$error);
1722 sub _mod_item_dates { # date formatting for date fields in item hash
1724 return if !$item || ref($item) ne 'HASH';
1727 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1729 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1730 # NOTE: We do not (yet) have items fields ending with datetime
1731 # Fields with _on$ have been handled already
1733 foreach my $key ( @keys ) {
1734 next if !defined $item->{$key}; # skip undefs
1735 my $dt = eval { dt_from_string( $item->{$key} ) };
1736 # eval: dt_from_string will die on us if we pass illegal dates
1739 if( defined $dt && ref($dt) eq 'DateTime' ) {
1740 if( $key =~ /datetime/ ) {
1741 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1743 $newstr = DateTime::Format::MySQL->format_date($dt);
1746 $item->{$key} = $newstr; # might be undef to clear garbage
1750 =head2 _koha_delete_item
1752 _koha_delete_item( $itemnum );
1754 Internal function to delete an item record from the koha tables
1758 sub _koha_delete_item {
1759 my ( $itemnum ) = @_;
1761 my $dbh = C4::Context->dbh;
1762 # save the deleted item to deleteditems table
1763 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1764 $sth->execute($itemnum);
1765 my $data = $sth->fetchrow_hashref();
1767 # There is no item to delete
1768 return 0 unless $data;
1770 my $query = "INSERT INTO deleteditems SET ";
1772 foreach my $key ( keys %$data ) {
1773 next if ( $key eq 'timestamp' ); # timestamp will be set by db
1774 $query .= "$key = ?,";
1775 push( @bind, $data->{$key} );
1778 $sth = $dbh->prepare($query);
1779 $sth->execute(@bind);
1781 # delete from items table
1782 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1783 my $deleted = $sth->execute($itemnum);
1784 return ( $deleted == 1 ) ? 1 : 0;
1787 =head2 _marc_from_item_hash
1789 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1791 Given an item hash representing a complete item record,
1792 create a C<MARC::Record> object containing an embedded
1793 tag representing that item.
1795 The third, optional parameter C<$unlinked_item_subfields> is
1796 an arrayref of subfields (not mapped to C<items> fields per the
1797 framework) to be added to the MARC representation
1802 sub _marc_from_item_hash {
1804 my $frameworkcode = shift;
1805 my $unlinked_item_subfields;
1807 $unlinked_item_subfields = shift;
1810 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1811 # Also, don't emit a subfield if the underlying field is blank.
1812 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1813 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1814 : () } keys %{ $item } };
1816 my $item_marc = MARC::Record->new();
1817 foreach my $item_field ( keys %{$mungeditem} ) {
1818 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1819 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1820 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1821 foreach my $value (@values){
1822 if ( my $field = $item_marc->field($tag) ) {
1823 $field->add_subfields( $subfield => $value );
1825 my $add_subfields = [];
1826 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1827 $add_subfields = $unlinked_item_subfields;
1829 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1837 =head2 _repack_item_errors
1839 Add an error message hash generated by C<CheckItemPreSave>
1840 to a list of errors.
1844 sub _repack_item_errors {
1845 my $item_sequence_num = shift;
1846 my $item_ref = shift;
1847 my $error_ref = shift;
1849 my @repacked_errors = ();
1851 foreach my $error_code (sort keys %{ $error_ref }) {
1852 my $repacked_error = {};
1853 $repacked_error->{'item_sequence'} = $item_sequence_num;
1854 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1855 $repacked_error->{'error_code'} = $error_code;
1856 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1857 push @repacked_errors, $repacked_error;
1860 return @repacked_errors;
1863 =head2 _get_unlinked_item_subfields
1865 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1869 sub _get_unlinked_item_subfields {
1870 my $original_item_marc = shift;
1871 my $frameworkcode = shift;
1873 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1875 # assume that this record has only one field, and that that
1876 # field contains only the item information
1878 my @fields = $original_item_marc->fields();
1879 if ($#fields > -1) {
1880 my $field = $fields[0];
1881 my $tag = $field->tag();
1882 foreach my $subfield ($field->subfields()) {
1883 if (defined $subfield->[1] and
1884 $subfield->[1] ne '' and
1885 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1886 push @$subfields, $subfield->[0] => $subfield->[1];
1893 =head2 _get_unlinked_subfields_xml
1895 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1899 sub _get_unlinked_subfields_xml {
1900 my $unlinked_item_subfields = shift;
1903 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1904 my $marc = MARC::Record->new();
1905 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1906 # used in the framework
1907 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1908 $marc->encoding("UTF-8");
1909 $xml = $marc->as_xml("USMARC");
1915 =head2 _parse_unlinked_item_subfields_from_xml
1917 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1921 sub _parse_unlinked_item_subfields_from_xml {
1923 require C4::Charset;
1924 return unless defined $xml and $xml ne "";
1925 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1926 my $unlinked_subfields = [];
1927 my @fields = $marc->fields();
1928 if ($#fields > -1) {
1929 foreach my $subfield ($fields[0]->subfields()) {
1930 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1933 return $unlinked_subfields;
1936 =head2 GetAnalyticsCount
1938 $count= &GetAnalyticsCount($itemnumber)
1940 counts Usage of itemnumber in Analytical bibliorecords.
1944 sub GetAnalyticsCount {
1945 my ($itemnumber) = @_;
1947 ### ZOOM search here
1949 $query= "hi=".$itemnumber;
1950 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1951 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1955 sub _SearchItems_build_where_fragment {
1958 my $dbh = C4::Context->dbh;
1961 if (exists($filter->{conjunction})) {
1962 my (@where_strs, @where_args);
1963 foreach my $f (@{ $filter->{filters} }) {
1964 my $fragment = _SearchItems_build_where_fragment($f);
1966 push @where_strs, $fragment->{str};
1967 push @where_args, @{ $fragment->{args} };
1972 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1975 args => \@where_args,
1979 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1980 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1981 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1982 my @operators = qw(= != > < >= <= like);
1983 my $field = $filter->{field} // q{};
1984 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1985 my $op = $filter->{operator};
1986 my $query = $filter->{query};
1988 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1989 $op = '='; # default operator
1993 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1995 my $marcsubfield = $2;
1996 my ($kohafield) = $dbh->selectrow_array(q|
1997 SELECT kohafield FROM marc_subfield_structure
1998 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1999 |, undef, $marcfield, $marcsubfield);
2002 $column = $kohafield;
2004 # MARC field is not linked to a DB field so we need to use
2005 # ExtractValue on marcxml from biblio_metadata or
2006 # items.more_subfields_xml, depending on the MARC field.
2009 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2010 if ($marcfield eq $itemfield) {
2011 $sqlfield = 'more_subfields_xml';
2012 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2014 $sqlfield = 'metadata'; # From biblio_metadata
2015 if ($marcfield < 10) {
2016 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2018 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2021 $column = "ExtractValue($sqlfield, '$xpath')";
2023 } elsif ($field eq 'issues') {
2024 # Consider NULL as 0 for issues count
2025 $column = 'COALESCE(issues,0)';
2030 if (ref $query eq 'ARRAY') {
2033 } elsif ($op eq '!=') {
2037 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2042 str => "$column $op ?",
2049 return $where_fragment;
2054 my ($items, $total) = SearchItems($filter, $params);
2056 Perform a search among items
2058 $filter is a reference to a hash which can be a filter, or a combination of filters.
2060 A filter has the following keys:
2064 =item * field: the name of a SQL column in table items
2066 =item * query: the value to search in this column
2068 =item * operator: comparison operator. Can be one of = != > < >= <= like
2072 A combination of filters hash the following keys:
2076 =item * conjunction: 'AND' or 'OR'
2078 =item * filters: array ref of filters
2082 $params is a reference to a hash that can contain the following parameters:
2086 =item * rows: Number of items to return. 0 returns everything (default: 0)
2088 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2091 =item * sortby: A SQL column name in items table to sort on
2093 =item * sortorder: 'ASC' or 'DESC'
2100 my ($filter, $params) = @_;
2104 return unless ref $filter eq 'HASH';
2105 return unless ref $params eq 'HASH';
2107 # Default parameters
2108 $params->{rows} ||= 0;
2109 $params->{page} ||= 1;
2110 $params->{sortby} ||= 'itemnumber';
2111 $params->{sortorder} ||= 'ASC';
2113 my ($where_str, @where_args);
2114 my $where_fragment = _SearchItems_build_where_fragment($filter);
2115 if ($where_fragment) {
2116 $where_str = $where_fragment->{str};
2117 @where_args = @{ $where_fragment->{args} };
2120 my $dbh = C4::Context->dbh;
2122 SELECT SQL_CALC_FOUND_ROWS items.*
2124 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2125 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2126 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2129 if (defined $where_str and $where_str ne '') {
2130 $query .= qq{ AND $where_str };
2133 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2134 push @where_args, C4::Context->preference('marcflavour');
2136 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2137 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2138 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2139 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2140 ? $params->{sortby} : 'itemnumber';
2141 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2142 $query .= qq{ ORDER BY $sortby $sortorder };
2144 my $rows = $params->{rows};
2147 my $offset = $rows * ($params->{page}-1);
2148 $query .= qq { LIMIT ?, ? };
2149 push @limit_args, $offset, $rows;
2152 my $sth = $dbh->prepare($query);
2153 my $rv = $sth->execute(@where_args, @limit_args);
2155 return unless ($rv);
2156 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2158 return ($sth->fetchall_arrayref({}), $total_rows);
2162 =head1 OTHER FUNCTIONS
2166 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2168 Find the given $subfield in the given $tag in the given
2169 MARC::Record $record. If the subfield is found, returns
2170 the (indicators, value) pair; otherwise, (undef, undef) is
2174 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2175 I suggest we export it from this module.
2180 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2183 if ( $tagfield < 10 ) {
2184 if ( $record->field($tagfield) ) {
2185 push @result, $record->field($tagfield)->data();
2190 foreach my $field ( $record->field($tagfield) ) {
2191 my @subfields = $field->subfields();
2192 foreach my $subfield (@subfields) {
2193 if ( @$subfield[0] eq $insubfield ) {
2194 push @result, @$subfield[1];
2195 $indicator = $field->indicator(1) . $field->indicator(2);
2200 return ( $indicator, @result );
2204 =head2 PrepareItemrecordDisplay
2206 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2208 Returns a hash with all the fields for Display a given item data in a template
2210 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2214 sub PrepareItemrecordDisplay {
2216 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2218 my $dbh = C4::Context->dbh;
2219 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2220 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2222 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2223 # a shared data structure. No plugin (including custom ones) should change
2224 # its contents. See also GetMarcStructure.
2225 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2227 # return nothing if we don't have found an existing framework.
2228 return q{} unless $tagslib;
2231 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2235 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2237 SELECT authorised_value,lib FROM authorised_values
2240 LEFT JOIN authorised_values_branches ON ( id = av_id )
2245 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2246 $query .= qq{ ORDER BY lib};
2247 my $authorised_values_sth = $dbh->prepare( $query );
2248 foreach my $tag ( sort keys %{$tagslib} ) {
2251 # loop through each subfield
2253 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2254 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2255 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2256 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2258 $subfield_data{tag} = $tag;
2259 $subfield_data{subfield} = $subfield;
2260 $subfield_data{countsubfield} = $cntsubf++;
2261 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2262 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2264 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2265 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2266 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2267 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2268 $subfield_data{hidden} = "display:none"
2269 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2270 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2271 my ( $x, $defaultvalue );
2273 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2275 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2276 if ( !defined $defaultvalue ) {
2277 $defaultvalue = q||;
2279 $defaultvalue =~ s/"/"/g;
2282 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2284 # search for itemcallnumber if applicable
2285 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2286 && C4::Context->preference('itemcallnumber') && $itemrecord) {
2287 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2288 my $CNtag = substr( $itemcn_pref, 0, 3 );
2289 next unless my $field = $itemrecord->field($CNtag);
2290 my $CNsubfields = substr( $itemcn_pref, 3 );
2291 $defaultvalue = $field->as_string( $CNsubfields, ' ');
2292 last if $defaultvalue;
2295 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2297 && $defaultvalues->{'callnumber'} ) {
2298 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2299 # if the item record exists, only use default value if the item has no callnumber
2300 $defaultvalue = $defaultvalues->{callnumber};
2301 } elsif ( !$itemrecord and $defaultvalues ) {
2302 # if the item record *doesn't* exists, always use the default value
2303 $defaultvalue = $defaultvalues->{callnumber};
2306 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2308 && $defaultvalues->{'branchcode'} ) {
2309 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2310 $defaultvalue = $defaultvalues->{branchcode};
2313 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2315 && $defaultvalues->{'location'} ) {
2317 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2318 # if the item record exists, only use default value if the item has no locationr
2319 $defaultvalue = $defaultvalues->{location};
2320 } elsif ( !$itemrecord and $defaultvalues ) {
2321 # if the item record *doesn't* exists, always use the default value
2322 $defaultvalue = $defaultvalues->{location};
2325 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2326 my @authorised_values;
2329 # builds list, depending on authorised value...
2331 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2332 if ( ( C4::Context->preference("IndependentBranches") )
2333 && !C4::Context->IsSuperLibrarian() ) {
2334 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2335 $sth->execute( C4::Context->userenv->{branch} );
2336 push @authorised_values, ""
2337 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2338 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2339 push @authorised_values, $branchcode;
2340 $authorised_lib{$branchcode} = $branchname;
2343 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2345 push @authorised_values, ""
2346 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2347 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2348 push @authorised_values, $branchcode;
2349 $authorised_lib{$branchcode} = $branchname;
2353 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2354 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2355 $defaultvalue = $defaultvalues->{branchcode};
2359 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2360 my $itemtypes = Koha::ItemTypes->search_with_localization;
2361 push @authorised_values, ""
2362 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2363 while ( my $itemtype = $itemtypes->next ) {
2364 push @authorised_values, $itemtype->itemtype;
2365 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2367 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2368 $defaultvalue = $defaultvalues->{'itemtype'};
2372 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2373 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2375 my $class_sources = GetClassSources();
2376 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2378 foreach my $class_source (sort keys %$class_sources) {
2379 next unless $class_sources->{$class_source}->{'used'} or
2380 ($class_source eq $default_source);
2381 push @authorised_values, $class_source;
2382 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2385 $defaultvalue = $default_source;
2387 #---- "true" authorised value
2389 $authorised_values_sth->execute(
2390 $tagslib->{$tag}->{$subfield}->{authorised_value},
2391 $branch_limit ? $branch_limit : ()
2393 push @authorised_values, ""
2394 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2395 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2396 push @authorised_values, $value;
2397 $authorised_lib{$value} = $lib;
2400 $subfield_data{marc_value} = {
2402 values => \@authorised_values,
2403 default => $defaultvalue // q{},
2404 labels => \%authorised_lib,
2406 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2408 require Koha::FrameworkPlugin;
2409 my $plugin = Koha::FrameworkPlugin->new({
2410 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2413 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2414 $plugin->build( $pars );
2415 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2416 $defaultvalue = $field->subfield($subfield) || q{};
2418 if( !$plugin->errstr ) {
2419 #TODO Move html to template; see report 12176/13397
2420 my $tab= $plugin->noclick? '-1': '';
2421 my $class= $plugin->noclick? ' disabled': '';
2422 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2423 $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;
2425 warn $plugin->errstr;
2426 $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
2429 elsif ( $tag eq '' ) { # it's an hidden field
2430 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2432 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2433 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2435 elsif ( length($defaultvalue) > 100
2436 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2437 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2438 or (C4::Context->preference("marcflavour") eq "MARC21" and
2439 500 <= $tag && $tag < 600 )
2441 # oversize field (textarea)
2442 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2444 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2446 push( @loop_data, \%subfield_data );
2451 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2452 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2455 'itemtagfield' => $itemtagfield,
2456 'itemtagsubfield' => $itemtagsubfield,
2457 'itemnumber' => $itemnumber,
2458 'iteminformation' => \@loop_data
2462 sub ToggleNewStatus {
2463 my ( $params ) = @_;
2464 my @rules = @{ $params->{rules} };
2465 my $report_only = $params->{report_only};
2467 my $dbh = C4::Context->dbh;
2469 my @item_columns = map { "items.$_" } Koha::Items->columns;
2470 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2472 for my $rule ( @rules ) {
2473 my $age = $rule->{age};
2474 my $conditions = $rule->{conditions};
2475 my $substitutions = $rule->{substitutions};
2476 foreach ( @$substitutions ) {
2477 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2484 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2487 for my $condition ( @$conditions ) {
2489 grep { $_ eq $condition->{field} } @item_columns
2490 or grep { $_ eq $condition->{field} } @biblioitem_columns
2492 if ( $condition->{value} =~ /\|/ ) {
2493 my @values = split /\|/, $condition->{value};
2494 $query .= qq| AND $condition->{field} IN (|
2495 . join( ',', ('?') x scalar @values )
2497 push @params, @values;
2499 $query .= qq| AND $condition->{field} = ?|;
2500 push @params, $condition->{value};
2504 if ( defined $age ) {
2505 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2508 my $sth = $dbh->prepare($query);
2509 $sth->execute( @params );
2510 while ( my $values = $sth->fetchrow_hashref ) {
2511 my $biblionumber = $values->{biblionumber};
2512 my $itemnumber = $values->{itemnumber};
2513 my $item = Koha::Items->find($itemnumber);
2514 for my $substitution ( @$substitutions ) {
2515 my $field = $substitution->{item_field};
2516 my $value = $substitution->{value};
2517 next unless $substitution->{field};
2518 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2519 $item->$field($value);
2520 push @{ $report->{$itemnumber} }, $substitution;
2522 $item->store unless $report_only;
2529 =head2 _after_item_action_hooks
2531 Helper method that takes care of calling all plugin hooks
2535 sub _after_item_action_hooks {
2538 my $item_id = $args->{item_id};
2539 my $action = $args->{action};
2541 if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
2543 my @plugins = Koha::Plugins->new->GetPlugins({
2544 method => 'after_item_action',
2549 my $item = Koha::Items->find( $item_id );
2551 foreach my $plugin ( @plugins ) {
2553 $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });