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 our (@ISA, @EXPORT_OK);
40 get_hostitemnumbers_of
46 PrepareItemrecordDisplay
54 use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
55 use Koha::DateUtils qw( dt_from_string output_pref );
57 use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
58 use C4::Log qw( logaction );
59 use List::MoreUtils qw( any );
60 use DateTime::Format::MySQL;
61 # debugging; so please don't remove this
63 use Koha::AuthorisedValues;
64 use Koha::DateUtils qw( dt_from_string output_pref );
67 use Koha::Biblioitems;
70 use Koha::SearchEngine;
71 use Koha::SearchEngine::Indexer;
72 use Koha::SearchEngine::Search;
77 C4::Items - item management functions
81 This module contains an API for manipulating item
82 records in Koha, and is used by cataloguing, circulation,
83 acquisitions, and serials management.
85 # FIXME This POD is not up-to-date
86 A Koha item record is stored in two places: the
87 items table and embedded in a MARC tag in the XML
88 version of the associated bib record in C<biblioitems.marcxml>.
89 This is done to allow the item information to be readily
90 indexed (e.g., by Zebra), but means that each item
91 modification transaction must keep the items table
92 and the MARC XML in sync at all times.
94 The items table will be considered authoritative. In other
95 words, if there is ever a discrepancy between the items
96 table and the MARC XML, the items table should be considered
99 =head1 HISTORICAL NOTE
101 Most of the functions in C<C4::Items> were originally in
102 the C<C4::Biblio> module.
104 =head1 CORE EXPORTED FUNCTIONS
106 The following functions are meant for use by users
113 CartToShelf($itemnumber);
115 Set the current shelving location of the item record
116 to its stored permanent shelving location. This is
117 primarily used to indicate when an item whose current
118 location is a special processing ('PROC') or shelving cart
119 ('CART') location is back in the stacks.
124 my ( $itemnumber ) = @_;
126 unless ( $itemnumber ) {
127 croak "FAILED CartToShelf() - no itemnumber supplied";
130 my $item = Koha::Items->find($itemnumber);
131 if ( $item->location eq 'CART' ) {
132 $item->location($item->permanent_location)->store;
136 =head2 AddItemFromMarc
138 my ($biblionumber, $biblioitemnumber, $itemnumber)
139 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
141 Given a MARC::Record object containing an embedded item
142 record and a biblionumber, create a new item record.
144 The final optional parameter, C<$params>, may contain
145 'skip_record_index' key, which relayed down to Koha::Item/store,
146 there it prevents calling of index_records,
147 which takes most of the time in batch adds/deletes: index_records
148 to be called later in C<additem.pl> after the whole loop.
150 You may also optionally pass biblioitemnumber in the params hash to
151 boost performance of inserts by preventing a lookup in Koha::Item.
154 skip_record_index => 1|0
155 biblioitemnumber => $biblioitemnumber
159 sub AddItemFromMarc {
160 my $source_item_marc = shift;
161 my $biblionumber = shift;
162 my $params = @_ ? shift : {};
164 my $dbh = C4::Context->dbh;
166 # parse item hash from MARC
167 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
168 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
169 my $localitemmarc = MARC::Record->new;
170 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
172 my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
173 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
174 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
175 $item_values->{biblionumber} = $biblionumber;
176 $item_values->{biblioitemnumber} = $params->{biblioitemnumber};
177 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
178 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
179 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
180 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
183 =head2 AddItemBatchFromMarc
185 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
186 $biblionumber, $biblioitemnumber, $frameworkcode);
188 Efficiently create item records from a MARC biblio record with
189 embedded item fields. This routine is suitable for batch jobs.
191 This API assumes that the bib record has already been
192 saved to the C<biblio> and C<biblioitems> tables. It does
193 not expect that C<biblio_metadata.metadata> is populated, but it
194 will do so via a call to ModBibiloMarc.
196 The goal of this API is to have a similar effect to using AddBiblio
197 and AddItems in succession, but without inefficient repeated
198 parsing of the MARC XML bib record.
200 This function returns an arrayref of new itemsnumbers and an arrayref of item
201 errors encountered during the processing. Each entry in the errors
202 list is a hashref containing the following keys:
208 Sequence number of original item tag in the MARC record.
212 Item barcode, provide to assist in the construction of
213 useful error messages.
217 Code representing the error condition. Can be 'duplicate_barcode',
218 'invalid_homebranch', or 'invalid_holdingbranch'.
220 =item error_information
222 Additional information appropriate to the error condition.
228 sub AddItemBatchFromMarc {
229 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
230 my @itemnumbers = ();
232 my $dbh = C4::Context->dbh;
234 # We modify the record, so lets work on a clone so we don't change the
236 $record = $record->clone();
237 # loop through the item tags and start creating items
238 my @bad_item_fields = ();
239 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
240 my $item_sequence_num = 0;
241 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
242 $item_sequence_num++;
243 # we take the item field and stick it into a new
244 # MARC record -- this is required so far because (FIXME)
245 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
246 # and there is no TransformMarcFieldToKoha
247 my $temp_item_marc = MARC::Record->new();
248 $temp_item_marc->append_fields($item_field);
250 # add biblionumber and biblioitemnumber
251 my $item = TransformMarcToKoha({ record => $temp_item_marc, limit_table => 'items' });
252 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
253 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
254 $item->{'biblionumber'} = $biblionumber;
255 $item->{'biblioitemnumber'} = $biblioitemnumber;
256 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
257 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
259 # check for duplicate barcode
260 my %item_errors = CheckItemPreSave($item);
262 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
263 push @bad_item_fields, $item_field;
267 my $item_object = Koha::Item->new($item)->store;
268 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
270 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
272 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
273 $item_field->replace_with($new_item_marc->field($itemtag));
276 # remove any MARC item fields for rejected items
277 foreach my $item_field (@bad_item_fields) {
278 $record->delete_field($item_field);
281 return (\@itemnumbers, \@errors);
284 =head2 ModItemFromMarc
286 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
288 The final optional parameter, C<$params>, expected to contain
289 'skip_record_index' key, which relayed down to Koha::Item/store,
290 there it prevents calling of index_records,
291 which takes most of the time in batch adds/deletes: index_records better
292 to be called later in C<additem.pl> after the whole loop.
295 skip_record_index => 1|0
299 sub ModItemFromMarc {
300 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
302 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
303 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
305 my $localitemmarc = MARC::Record->new;
306 $localitemmarc->append_fields( $item_marc->field($itemtag) );
307 my $item_object = Koha::Items->find($itemnumber);
308 my $item = TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
310 # When importing items we blank this column, we need to set it to the existing value
311 # to prevent it being blanked by set_or_blank
312 $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
314 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
315 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
317 # Retrieving the values for the fields that are not linked
318 my @mapped_fields = Koha::MarcSubfieldStructures->search(
320 frameworkcode => $frameworkcode,
321 kohafield => { -like => "items.%" }
323 )->get_column('kohafield');
324 for my $c ( $item_object->_result->result_source->columns ) {
325 next if grep { "items.$c" eq $_ } @mapped_fields;
326 $item->{$c} = $item_object->$c;
329 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
330 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
331 $item->{itemnumber} = $itemnumber;
332 $item->{biblionumber} = $biblionumber;
334 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
335 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
336 $item_object = $item_object->set_or_blank($item);
337 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
339 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
341 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
342 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
343 $item_object->store({ skip_record_index => $params->{skip_record_index} });
345 return $item_object->unblessed;
348 =head2 ModItemTransfer
350 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
352 Marks an item as being transferred from one branch to another and records the trigger.
354 The last optional parameter allows for passing skip_record_index through to the items store call.
358 sub ModItemTransfer {
359 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
361 my $dbh = C4::Context->dbh;
362 my $item = Koha::Items->find( $itemnumber );
364 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
365 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
366 # will have been preceded by a check of branch transfer limits)
367 my $to_library = Koha::Libraries->find($tobranch);
368 my $transfer = $item->request_transfer(
377 # Immediately set the item to in transit if it is checked in
378 if ( !$item->checkout ) {
379 $item->holdingbranch($frombranch)->store(
382 skip_record_index => $params->{skip_record_index}
391 =head2 ModDateLastSeen
393 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
395 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
396 C<$itemnumber> is the item number
397 C<$leave_item_lost> determines if a lost item will be found or remain lost
399 The last optional parameter allows for passing skip_record_index through to the items store call.
403 sub ModDateLastSeen {
404 my ( $itemnumber, $leave_item_lost, $params ) = @_;
406 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
408 my $item = Koha::Items->find($itemnumber);
409 $item->datelastseen($today);
410 $item->itemlost(0) unless $leave_item_lost;
411 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
414 =head2 CheckItemPreSave
416 my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
418 my %errors = CheckItemPreSave($item_ref);
419 if (exists $errors{'duplicate_barcode'}) {
420 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
421 } elsif (exists $errors{'invalid_homebranch'}) {
422 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
423 } elsif (exists $errors{'invalid_holdingbranch'}) {
424 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
429 Given a hashref containing item fields, determine if it can be
430 inserted or updated in the database. Specifically, checks for
431 database integrity issues, and returns a hash containing any
432 of the following keys, if applicable.
436 =item duplicate_barcode
438 Barcode, if it duplicates one already found in the database.
440 =item invalid_homebranch
442 Home branch, if not defined in branches table.
444 =item invalid_holdingbranch
446 Holding branch, if not defined in branches table.
450 This function does NOT implement any policy-related checks,
451 e.g., whether current operator is allowed to save an
452 item that has a given branch code.
456 sub CheckItemPreSave {
457 my $item_ref = shift;
461 # check for duplicate barcode
462 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
463 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
464 if ($existing_item) {
465 if (!exists $item_ref->{'itemnumber'} # new item
466 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
467 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
472 # check for valid home branch
473 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
474 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
475 unless (defined $home_library) {
476 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
480 # check for valid holding branch
481 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
482 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
483 unless (defined $holding_library) {
484 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
492 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
494 The following functions provide various ways of
495 getting an item record, a set of item records, or
496 lists of authorized values for certain item fields.
500 =head2 GetItemsForInventory
502 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
503 minlocation => $minlocation,
504 maxlocation => $maxlocation,
505 location => $location,
506 ignoreissued => $ignoreissued,
507 datelastseen => $datelastseen,
508 branchcode => $branchcode,
512 statushash => $statushash,
513 itemtypes => \@itemsarray,
516 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
518 The sub returns a reference to a list of hashes, each containing
519 itemnumber, author, title, barcode, item callnumber, and date last
520 seen. It is ordered by callnumber then title.
522 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
523 the datelastseen can be used to specify that you want to see items not seen since a past date only.
524 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
525 $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.
527 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
531 sub GetItemsForInventory {
532 my ( $parameters ) = @_;
533 my $minlocation = $parameters->{'minlocation'} // '';
534 my $maxlocation = $parameters->{'maxlocation'} // '';
535 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
536 my $location = $parameters->{'location'} // '';
537 my $itemtype = $parameters->{'itemtype'} // '';
538 my $ignoreissued = $parameters->{'ignoreissued'} // '';
539 my $datelastseen = $parameters->{'datelastseen'} // '';
540 my $branchcode = $parameters->{'branchcode'} // '';
541 my $branch = $parameters->{'branch'} // '';
542 my $offset = $parameters->{'offset'} // '';
543 my $size = $parameters->{'size'} // '';
544 my $statushash = $parameters->{'statushash'} // '';
545 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
546 my $itemtypes = $parameters->{'itemtypes'} || [];
547 my $ccode = $parameters->{'ccode'} // '';
549 my $dbh = C4::Context->dbh;
550 my ( @bind_params, @where_strings );
552 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
553 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
555 my $select_columns = q{
556 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
559 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
562 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
563 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
566 for my $authvfield (keys %$statushash){
567 if ( scalar @{$statushash->{$authvfield}} > 0 ){
568 my $joinedvals = join ',', @{$statushash->{$authvfield}};
569 push @where_strings, "$authvfield in (" . $joinedvals . ")";
575 push @where_strings, 'ccode = ?';
576 push @bind_params, $ccode;
580 push @where_strings, 'items.cn_sort >= ?';
581 push @bind_params, $min_cnsort;
585 push @where_strings, 'items.cn_sort <= ?';
586 push @bind_params, $max_cnsort;
590 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
591 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
592 push @bind_params, $datelastseen;
596 push @where_strings, 'items.location = ?';
597 push @bind_params, $location;
601 if($branch eq "homebranch"){
602 push @where_strings, 'items.homebranch = ?';
604 push @where_strings, 'items.holdingbranch = ?';
606 push @bind_params, $branchcode;
610 push @where_strings, 'biblioitems.itemtype = ?';
611 push @bind_params, $itemtype;
613 if ( $ignoreissued) {
614 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
615 push @where_strings, 'issues.date_due IS NULL';
618 if ( $ignore_waiting_holds ) {
619 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
620 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
624 my $itemtypes_str = join ', ', @$itemtypes;
625 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
628 if ( @where_strings ) {
630 $query .= join ' AND ', @where_strings;
632 my $count_query = $select_count . $query;
633 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
634 $query .= " LIMIT $offset, $size" if ($offset and $size);
635 $query = $select_columns . $query;
636 my $sth = $dbh->prepare($query);
637 $sth->execute( @bind_params );
640 my $tmpresults = $sth->fetchall_arrayref({});
641 $sth = $dbh->prepare( $count_query );
642 $sth->execute( @bind_params );
643 my ($iTotalRecords) = $sth->fetchrow_array();
645 my @avs = Koha::AuthorisedValues->search(
646 { 'marc_subfield_structures.kohafield' => { '>' => '' },
647 'me.authorised_value' => { '>' => '' },
649 { join => { category => 'marc_subfield_structures' },
650 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
651 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
652 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
656 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
658 foreach my $row (@$tmpresults) {
661 foreach (keys %$row) {
664 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
667 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
673 return (\@results, $iTotalRecords);
678 @results = GetItemsInfo($biblionumber);
680 Returns information about items with the given biblionumber.
682 C<GetItemsInfo> returns a list of references-to-hash. Each element
683 contains a number of keys. Most of them are attributes from the
684 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
685 Koha database. Other keys include:
689 =item C<$data-E<gt>{branchname}>
691 The name (not the code) of the branch to which the book belongs.
693 =item C<$data-E<gt>{datelastseen}>
695 This is simply C<items.datelastseen>, except that while the date is
696 stored in YYYY-MM-DD format in the database, here it is converted to
697 DD/MM/YYYY format. A NULL date is returned as C<//>.
699 =item C<$data-E<gt>{datedue}>
701 =item C<$data-E<gt>{class}>
703 This is the concatenation of C<biblioitems.classification>, the book's
704 Dewey code, and C<biblioitems.subclass>.
706 =item C<$data-E<gt>{ocount}>
708 I think this is the number of copies of the book available.
710 =item C<$data-E<gt>{order}>
712 If this is set, it is set to C<One Order>.
719 my ( $biblionumber ) = @_;
720 my $dbh = C4::Context->dbh;
721 require C4::Languages;
722 my $language = C4::Languages::getlanguage();
728 biblioitems.itemtype,
731 biblioitems.publicationyear,
732 biblioitems.publishercode,
733 biblioitems.volumedate,
734 biblioitems.volumedesc,
737 items.notforloan as itemnotforloan,
738 issues.borrowernumber,
739 issues.date_due as datedue,
740 issues.onsite_checkout,
741 borrowers.cardnumber,
744 borrowers.branchcode as bcode,
746 serial.publisheddate,
747 itemtypes.description,
748 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
749 itemtypes.notforloan as notforloan_per_itemtype,
753 holding.opac_info as holding_branch_opac_info,
754 home.opac_info as home_branch_opac_info,
755 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
757 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
758 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
759 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
760 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
761 LEFT JOIN issues USING (itemnumber)
762 LEFT JOIN borrowers USING (borrowernumber)
763 LEFT JOIN serialitems USING (itemnumber)
764 LEFT JOIN serial USING (serialid)
765 LEFT JOIN itemtypes ON itemtypes.itemtype = "
766 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
768 LEFT JOIN tmp_holdsqueue USING (itemnumber)
769 LEFT JOIN localization ON itemtypes.itemtype = localization.code
770 AND localization.entity = 'itemtypes'
771 AND localization.lang = ?
774 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
775 my $sth = $dbh->prepare($query);
776 $sth->execute($language, $biblionumber);
781 my $userenv = C4::Context->userenv;
782 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
783 while ( my $data = $sth->fetchrow_hashref ) {
784 if ( $data->{borrowernumber} && $want_not_same_branch) {
785 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
788 $serial ||= $data->{'serial'};
791 # get notforloan complete status if applicable
792 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
793 $data->{notforloanvalue} = $descriptions->{lib} // '';
794 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
796 # get restricted status and description if applicable
797 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
798 $data->{restrictedvalue} = $descriptions->{lib} // '';
799 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
801 # my stack procedures
802 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
803 $data->{stack} = $descriptions->{lib} // '';
805 # Find the last 3 people who borrowed this item.
806 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
808 AND old_issues.borrowernumber = borrowers.borrowernumber
809 ORDER BY returndate DESC
811 $sth2->execute($data->{'itemnumber'});
813 while (my $data2 = $sth2->fetchrow_hashref()) {
814 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
815 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
816 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
820 $results[$i] = $data;
825 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
829 =head2 GetItemsLocationInfo
831 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
833 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
835 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
839 =item C<$data-E<gt>{homebranch}>
841 Branch Name of the item's homebranch
843 =item C<$data-E<gt>{holdingbranch}>
845 Branch Name of the item's holdingbranch
847 =item C<$data-E<gt>{location}>
849 Item's shelving location code
851 =item C<$data-E<gt>{location_intranet}>
853 The intranet description for the Shelving Location as set in authorised_values 'LOC'
855 =item C<$data-E<gt>{location_opac}>
857 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
860 =item C<$data-E<gt>{itemcallnumber}>
862 Item's itemcallnumber
864 =item C<$data-E<gt>{cn_sort}>
866 Item's call number normalized for sorting
872 sub GetItemsLocationInfo {
873 my $biblionumber = shift;
876 my $dbh = C4::Context->dbh;
877 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
878 location, itemcallnumber, cn_sort
879 FROM items, branches as a, branches as b
880 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
882 ORDER BY cn_sort ASC";
883 my $sth = $dbh->prepare($query);
884 $sth->execute($biblionumber);
886 while ( my $data = $sth->fetchrow_hashref ) {
887 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
888 $av = $av->count ? $av->next : undef;
889 $data->{location_intranet} = $av ? $av->lib : '';
890 $data->{location_opac} = $av ? $av->opac_description : '';
891 push @results, $data;
896 =head2 GetHostItemsInfo
898 $hostiteminfo = GetHostItemsInfo($hostfield);
899 Returns the iteminfo for items linked to records via a host field
903 sub GetHostItemsInfo {
907 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
908 return @returnitemsInfo;
912 if( C4::Context->preference('marcflavour') eq 'MARC21' ) {
913 @fields = $record->field('773');
914 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
915 @fields = $record->field('461');
918 foreach my $hostfield ( @fields ) {
919 my $hostbiblionumber = $hostfield->subfield("0");
920 my $linkeditemnumber = $hostfield->subfield("9");
921 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
922 foreach my $hostitemInfo (@hostitemInfos) {
923 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
924 push @returnitemsInfo, $hostitemInfo;
929 return @returnitemsInfo;
932 =head2 get_hostitemnumbers_of
934 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
936 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
938 Return a reference on a hash where key is a biblionumber and values are
939 references on array of itemnumbers.
944 sub get_hostitemnumbers_of {
945 my ($biblionumber) = @_;
947 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
951 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
952 return unless $marcrecord;
954 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
956 my $marcflavor = C4::Context->preference('marcflavour');
957 if ( $marcflavor eq 'MARC21' ) {
962 elsif ( $marcflavor eq 'UNIMARC' ) {
968 foreach my $hostfield ( $marcrecord->field($tag) ) {
969 my $hostbiblionumber = $hostfield->subfield($biblio_s);
970 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
971 my $linkeditemnumber = $hostfield->subfield($item_s);
972 if ( ! $linkeditemnumber ) {
973 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
976 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
977 push @returnhostitemnumbers, $linkeditemnumber
981 return @returnhostitemnumbers;
984 =head2 GetHiddenItemnumbers
986 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
988 Given a list of items it checks which should be hidden from the OPAC given
989 the current configuration. Returns a list of itemnumbers corresponding to
990 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
995 sub GetHiddenItemnumbers {
997 my $items = $params->{items};
998 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
999 foreach my $except (split(/\|/, $exceptions)){
1000 if ($params->{'borcat'} eq $except){
1001 return; # we don't hide anything for this borrower category
1007 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
1010 unless $hidingrules;
1012 my $dbh = C4::Context->dbh;
1015 foreach my $item (@$items) {
1017 # We check each rule
1018 foreach my $field (keys %$hidingrules) {
1020 if (exists $item->{$field}) {
1021 $val = $item->{$field};
1024 my $query = "SELECT $field from items where itemnumber = ?";
1025 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1027 $val = '' unless defined $val;
1029 # If the results matches the values in the yaml file
1030 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1032 # We add the itemnumber to the list
1033 push @resultitems, $item->{'itemnumber'};
1035 # If at least one rule matched for an item, no need to test the others
1040 return @resultitems;
1043 =head1 LIMITED USE FUNCTIONS
1045 The following functions, while part of the public API,
1046 are not exported. This is generally because they are
1047 meant to be used by only one script for a specific
1048 purpose, and should not be used in any other context
1049 without careful thought.
1055 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1057 Returns MARC::Record of the item passed in parameter.
1058 This function is meant for use only in C<cataloguing/additem.pl>,
1059 where it is needed to support that script's MARC-like
1065 my ( $biblionumber, $itemnumber ) = @_;
1067 # GetMarcItem has been revised so that it does the following:
1068 # 1. Gets the item information from the items table.
1069 # 2. Converts it to a MARC field for storage in the bib record.
1071 # The previous behavior was:
1072 # 1. Get the bib record.
1073 # 2. Return the MARC tag corresponding to the item record.
1075 # The difference is that one treats the items row as authoritative,
1076 # while the other treats the MARC representation as authoritative
1077 # under certain circumstances.
1079 my $item = Koha::Items->find($itemnumber) or return;
1081 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1082 # Also, don't emit a subfield if the underlying field is blank.
1084 return Item2Marc($item->unblessed, $biblionumber);
1088 my ($itemrecord,$biblionumber)=@_;
1091 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1092 } keys %{ $itemrecord }
1094 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1095 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1096 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1097 "items.itemnumber", $framework,
1100 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1101 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1102 foreach my $field ($itemmarc->field($itemtag)){
1103 $field->add_subfields(@$unlinked_item_subfields);
1109 =head1 PRIVATE FUNCTIONS AND VARIABLES
1111 The following functions are not meant to be called
1112 directly, but are documented in order to explain
1113 the inner workings of C<C4::Items>.
1117 =head2 _marc_from_item_hash
1119 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1121 Given an item hash representing a complete item record,
1122 create a C<MARC::Record> object containing an embedded
1123 tag representing that item.
1125 The third, optional parameter C<$unlinked_item_subfields> is
1126 an arrayref of subfields (not mapped to C<items> fields per the
1127 framework) to be added to the MARC representation
1132 sub _marc_from_item_hash {
1134 my $frameworkcode = shift;
1135 my $unlinked_item_subfields;
1137 $unlinked_item_subfields = shift;
1140 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1141 # Also, don't emit a subfield if the underlying field is blank.
1142 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1143 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1144 : () } keys %{ $item } };
1146 my $item_marc = MARC::Record->new();
1147 foreach my $item_field ( keys %{$mungeditem} ) {
1148 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1149 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1150 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1151 foreach my $value (@values){
1152 if ( my $field = $item_marc->field($tag) ) {
1153 $field->add_subfields( $subfield => $value );
1155 my $add_subfields = [];
1156 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1157 $add_subfields = $unlinked_item_subfields;
1159 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1167 =head2 _repack_item_errors
1169 Add an error message hash generated by C<CheckItemPreSave>
1170 to a list of errors.
1174 sub _repack_item_errors {
1175 my $item_sequence_num = shift;
1176 my $item_ref = shift;
1177 my $error_ref = shift;
1179 my @repacked_errors = ();
1181 foreach my $error_code (sort keys %{ $error_ref }) {
1182 my $repacked_error = {};
1183 $repacked_error->{'item_sequence'} = $item_sequence_num;
1184 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1185 $repacked_error->{'error_code'} = $error_code;
1186 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1187 push @repacked_errors, $repacked_error;
1190 return @repacked_errors;
1193 =head2 _get_unlinked_item_subfields
1195 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1199 sub _get_unlinked_item_subfields {
1200 my $original_item_marc = shift;
1201 my $frameworkcode = shift;
1203 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1205 # assume that this record has only one field, and that that
1206 # field contains only the item information
1208 my @fields = $original_item_marc->fields();
1209 if ($#fields > -1) {
1210 my $field = $fields[0];
1211 my $tag = $field->tag();
1212 foreach my $subfield ($field->subfields()) {
1213 if (defined $subfield->[1] and
1214 $subfield->[1] ne '' and
1215 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1216 push @$subfields, $subfield->[0] => $subfield->[1];
1223 =head2 _get_unlinked_subfields_xml
1225 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1229 sub _get_unlinked_subfields_xml {
1230 my $unlinked_item_subfields = shift;
1233 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1234 my $marc = MARC::Record->new();
1235 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1236 # used in the framework
1237 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1238 $marc->encoding("UTF-8");
1239 $xml = $marc->as_xml("USMARC");
1245 =head2 _parse_unlinked_item_subfields_from_xml
1247 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1251 sub _parse_unlinked_item_subfields_from_xml {
1253 require C4::Charset;
1254 return unless defined $xml and $xml ne "";
1255 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1256 my $unlinked_subfields = [];
1257 my @fields = $marc->fields();
1258 if ($#fields > -1) {
1259 foreach my $subfield ($fields[0]->subfields()) {
1260 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1263 return $unlinked_subfields;
1266 =head2 GetAnalyticsCount
1268 $count= &GetAnalyticsCount($itemnumber)
1270 counts Usage of itemnumber in Analytical bibliorecords.
1274 sub GetAnalyticsCount {
1275 my ($itemnumber) = @_;
1277 ### ZOOM search here
1279 $query= "hi=".$itemnumber;
1280 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1281 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1285 sub _SearchItems_build_where_fragment {
1288 my $dbh = C4::Context->dbh;
1291 if (exists($filter->{conjunction})) {
1292 my (@where_strs, @where_args);
1293 foreach my $f (@{ $filter->{filters} }) {
1294 my $fragment = _SearchItems_build_where_fragment($f);
1296 push @where_strs, $fragment->{str};
1297 push @where_args, @{ $fragment->{args} };
1302 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1305 args => \@where_args,
1309 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1310 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1311 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1312 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1313 my @operators = qw(= != > < >= <= is like);
1314 push @operators, 'not like';
1315 my $field = $filter->{field} // q{};
1316 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1317 my $op = $filter->{operator};
1318 my $query = $filter->{query};
1319 my $ifnull = $filter->{ifnull};
1321 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1322 $op = '='; # default operator
1326 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1328 my $marcsubfield = $2;
1329 my ($kohafield) = $dbh->selectrow_array(q|
1330 SELECT kohafield FROM marc_subfield_structure
1331 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1332 |, undef, $marcfield, $marcsubfield);
1335 $column = $kohafield;
1337 # MARC field is not linked to a DB field so we need to use
1338 # ExtractValue on marcxml from biblio_metadata or
1339 # items.more_subfields_xml, depending on the MARC field.
1342 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1343 if ($marcfield eq $itemfield) {
1344 $sqlfield = 'more_subfields_xml';
1345 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1347 $sqlfield = 'metadata'; # From biblio_metadata
1348 if ($marcfield < 10) {
1349 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1351 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1354 $column = "ExtractValue($sqlfield, '$xpath')";
1360 if ( defined $ifnull ) {
1361 $column = "COALESCE($column, ?)";
1364 if (ref $query eq 'ARRAY') {
1367 } elsif ($op eq '!=') {
1371 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1374 } elsif ( $op eq 'is' ) {
1376 str => "$column $op $query",
1381 str => "$column $op ?",
1386 if ( defined $ifnull ) {
1387 unshift @{ $where_fragment->{args} }, $ifnull;
1392 return $where_fragment;
1397 my ($items, $total) = SearchItems($filter, $params);
1399 Perform a search among items
1401 $filter is a reference to a hash which can be a filter, or a combination of filters.
1403 A filter has the following keys:
1407 =item * field: the name of a SQL column in table items
1409 =item * query: the value to search in this column
1411 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1415 A combination of filters hash the following keys:
1419 =item * conjunction: 'AND' or 'OR'
1421 =item * filters: array ref of filters
1425 $params is a reference to a hash that can contain the following parameters:
1429 =item * rows: Number of items to return. 0 returns everything (default: 0)
1431 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1434 =item * sortby: A SQL column name in items table to sort on
1436 =item * sortorder: 'ASC' or 'DESC'
1443 my ($filter, $params) = @_;
1447 return unless ref $filter eq 'HASH';
1448 return unless ref $params eq 'HASH';
1450 # Default parameters
1451 $params->{rows} ||= 0;
1452 $params->{page} ||= 1;
1453 $params->{sortby} ||= 'itemnumber';
1454 $params->{sortorder} ||= 'ASC';
1456 my ($where_str, @where_args);
1457 my $where_fragment = _SearchItems_build_where_fragment($filter);
1458 if ($where_fragment) {
1459 $where_str = $where_fragment->{str};
1460 @where_args = @{ $where_fragment->{args} };
1463 my $dbh = C4::Context->dbh;
1465 SELECT SQL_CALC_FOUND_ROWS items.*
1467 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1468 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1469 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1470 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1473 if (defined $where_str and $where_str ne '') {
1474 $query .= qq{ AND $where_str };
1477 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1478 push @where_args, C4::Context->preference('marcflavour');
1480 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1481 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1482 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1483 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1485 if ( $params->{sortby} eq 'availability' ) {
1486 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1487 $query .= qq{ ORDER BY onloan $sortorder };
1489 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1490 ? $params->{sortby} : 'itemnumber';
1491 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1492 $query .= qq{ ORDER BY $sortby $sortorder };
1495 my $rows = $params->{rows};
1498 my $offset = $rows * ($params->{page}-1);
1499 $query .= qq { LIMIT ?, ? };
1500 push @limit_args, $offset, $rows;
1503 my $sth = $dbh->prepare($query);
1504 my $rv = $sth->execute(@where_args, @limit_args);
1506 return unless ($rv);
1507 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1509 return ($sth->fetchall_arrayref({}), $total_rows);
1513 =head1 OTHER FUNCTIONS
1517 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1519 Find the given $subfield in the given $tag in the given
1520 MARC::Record $record. If the subfield is found, returns
1521 the (indicators, value) pair; otherwise, (undef, undef) is
1525 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1526 I suggest we export it from this module.
1531 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1534 if ( $tagfield < 10 ) {
1535 if ( $record->field($tagfield) ) {
1536 push @result, $record->field($tagfield)->data();
1541 foreach my $field ( $record->field($tagfield) ) {
1542 my @subfields = $field->subfields();
1543 foreach my $subfield (@subfields) {
1544 if ( @$subfield[0] eq $insubfield ) {
1545 push @result, @$subfield[1];
1546 $indicator = $field->indicator(1) . $field->indicator(2);
1551 return ( $indicator, @result );
1555 =head2 PrepareItemrecordDisplay
1557 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1559 Returns a hash with all the fields for Display a given item data in a template
1561 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1563 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1567 sub PrepareItemrecordDisplay {
1569 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1571 my $dbh = C4::Context->dbh;
1572 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1573 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1575 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1576 # a shared data structure. No plugin (including custom ones) should change
1577 # its contents. See also GetMarcStructure.
1578 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1580 # Pick the default location from NewItemsDefaultLocation
1581 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1582 $defaultvalues //= {};
1583 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1586 # return nothing if we don't have found an existing framework.
1587 return q{} unless $tagslib;
1590 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1594 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1596 SELECT authorised_value,lib FROM authorised_values
1599 LEFT JOIN authorised_values_branches ON ( id = av_id )
1604 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1605 $query .= qq{ ORDER BY lib};
1606 my $authorised_values_sth = $dbh->prepare( $query );
1607 foreach my $tag ( sort keys %{$tagslib} ) {
1610 # loop through each subfield
1612 foreach my $subfield (
1613 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1614 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1615 values %{ $tagslib->{$tag} } )
1617 next unless ( $subfield->{'tab'} );
1618 next if ( $subfield->{'tab'} ne "10" );
1620 $subfield_data{tag} = $tag;
1621 $subfield_data{subfield} = $subfield->{subfield};
1622 $subfield_data{countsubfield} = $cntsubf++;
1623 $subfield_data{kohafield} = $subfield->{kohafield};
1624 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1626 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1627 $subfield_data{marc_lib} = $subfield->{lib};
1628 $subfield_data{mandatory} = $subfield->{mandatory};
1629 $subfield_data{repeatable} = $subfield->{repeatable};
1630 $subfield_data{hidden} = "display:none"
1631 if ( ( $subfield->{hidden} > 4 )
1632 || ( $subfield->{hidden} < -4 ) );
1633 my ( $x, $defaultvalue );
1635 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1637 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1638 if ( !defined $defaultvalue ) {
1639 $defaultvalue = q||;
1641 $defaultvalue =~ s/"/"/g;
1642 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1643 my $today_dt = dt_from_string;
1644 my $year = $today_dt->strftime('%Y');
1645 my $shortyear = $today_dt->strftime('%y');
1646 my $month = $today_dt->strftime('%m');
1647 my $day = $today_dt->strftime('%d');
1648 $defaultvalue =~ s/<<YYYY>>/$year/g;
1649 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1650 $defaultvalue =~ s/<<MM>>/$month/g;
1651 $defaultvalue =~ s/<<DD>>/$day/g;
1653 # And <<USER>> with surname (?)
1655 ( C4::Context->userenv
1656 ? C4::Context->userenv->{'surname'}
1657 : "superlibrarian" );
1658 $defaultvalue =~ s/<<USER>>/$username/g;
1661 my $maxlength = $subfield->{maxlength};
1663 # search for itemcallnumber if applicable
1664 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1665 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1666 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1667 my $CNtag = substr( $itemcn_pref, 0, 3 );
1668 next unless my $field = $itemrecord->field($CNtag);
1669 my $CNsubfields = substr( $itemcn_pref, 3 );
1670 $CNsubfields = undef if $CNsubfields eq '';
1671 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1672 last if $defaultvalue;
1675 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1677 && $defaultvalues->{'callnumber'} ) {
1678 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1679 # if the item record exists, only use default value if the item has no callnumber
1680 $defaultvalue = $defaultvalues->{callnumber};
1681 } elsif ( !$itemrecord and $defaultvalues ) {
1682 # if the item record *doesn't* exists, always use the default value
1683 $defaultvalue = $defaultvalues->{callnumber};
1686 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1688 && $defaultvalues->{'branchcode'} ) {
1689 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1690 $defaultvalue = $defaultvalues->{branchcode};
1693 if ( ( $subfield->{kohafield} eq 'items.location' )
1695 && $defaultvalues->{'location'} ) {
1697 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1698 # if the item record exists, only use default value if the item has no locationr
1699 $defaultvalue = $defaultvalues->{location};
1700 } elsif ( !$itemrecord and $defaultvalues ) {
1701 # if the item record *doesn't* exists, always use the default value
1702 $defaultvalue = $defaultvalues->{location};
1705 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1707 && $defaultvalues->{'ccode'} ) {
1709 if ( !$itemrecord and $defaultvalues ) {
1710 # if the item record *doesn't* exists, always use the default value
1711 $defaultvalue = $defaultvalues->{ccode};
1714 if ( $subfield->{authorised_value} ) {
1715 my @authorised_values;
1718 # builds list, depending on authorised value...
1720 if ( $subfield->{'authorised_value'} eq "branches" ) {
1721 if ( ( C4::Context->preference("IndependentBranches") )
1722 && !C4::Context->IsSuperLibrarian() ) {
1723 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1724 $sth->execute( C4::Context->userenv->{branch} );
1725 push @authorised_values, ""
1726 unless ( $subfield->{mandatory} );
1727 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1728 push @authorised_values, $branchcode;
1729 $authorised_lib{$branchcode} = $branchname;
1732 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1734 push @authorised_values, ""
1735 unless ( $subfield->{mandatory} );
1736 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1737 push @authorised_values, $branchcode;
1738 $authorised_lib{$branchcode} = $branchname;
1742 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1743 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1744 $defaultvalue = $defaultvalues->{branchcode};
1748 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1749 my $itemtypes = Koha::ItemTypes->search_with_localization;
1750 push @authorised_values, "";
1751 while ( my $itemtype = $itemtypes->next ) {
1752 push @authorised_values, $itemtype->itemtype;
1753 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1755 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1756 $defaultvalue = $defaultvalues->{'itemtype'};
1760 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1761 push @authorised_values, "";
1763 my $class_sources = GetClassSources();
1764 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1766 foreach my $class_source (sort keys %$class_sources) {
1767 next unless $class_sources->{$class_source}->{'used'} or
1768 ($class_source eq $default_source);
1769 push @authorised_values, $class_source;
1770 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1773 $defaultvalue = $default_source;
1775 #---- "true" authorised value
1777 $authorised_values_sth->execute(
1778 $subfield->{authorised_value},
1779 $branch_limit ? $branch_limit : ()
1781 push @authorised_values, "";
1782 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1783 push @authorised_values, $value;
1784 $authorised_lib{$value} = $lib;
1787 $subfield_data{marc_value} = {
1789 values => \@authorised_values,
1790 default => $defaultvalue // q{},
1791 labels => \%authorised_lib,
1793 } elsif ( $subfield->{value_builder} ) {
1795 require Koha::FrameworkPlugin;
1796 my $plugin = Koha::FrameworkPlugin->new({
1797 name => $subfield->{value_builder},
1800 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1801 $plugin->build( $pars );
1802 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1803 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1805 if( !$plugin->errstr ) {
1806 #TODO Move html to template; see report 12176/13397
1807 my $tab= $plugin->noclick? '-1': '';
1808 my $class= $plugin->noclick? ' disabled': '';
1809 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1810 $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;
1812 warn $plugin->errstr;
1813 $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
1816 elsif ( $tag eq '' ) { # it's an hidden field
1817 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1819 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1820 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1822 elsif ( length($defaultvalue) > 100
1823 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1824 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1825 or (C4::Context->preference("marcflavour") eq "MARC21" and
1826 500 <= $tag && $tag < 600 )
1828 # oversize field (textarea)
1829 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1831 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1833 push( @loop_data, \%subfield_data );
1838 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1839 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1842 'itemtagfield' => $itemtagfield,
1843 'itemtagsubfield' => $itemtagsubfield,
1844 'itemnumber' => $itemnumber,
1845 'iteminformation' => \@loop_data
1849 sub ToggleNewStatus {
1850 my ( $params ) = @_;
1851 my @rules = @{ $params->{rules} };
1852 my $report_only = $params->{report_only};
1854 my $dbh = C4::Context->dbh;
1856 my @item_columns = map { "items.$_" } Koha::Items->columns;
1857 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1859 for my $rule ( @rules ) {
1860 my $age = $rule->{age};
1861 # Default to using items.dateaccessioned if there's an old item modification rule
1862 # missing an agefield value
1863 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1864 my $conditions = $rule->{conditions};
1865 my $substitutions = $rule->{substitutions};
1866 foreach ( @$substitutions ) {
1867 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1874 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1877 for my $condition ( @$conditions ) {
1878 next unless $condition->{field};
1880 grep { $_ eq $condition->{field} } @item_columns
1881 or grep { $_ eq $condition->{field} } @biblioitem_columns
1883 if ( $condition->{value} =~ /\|/ ) {
1884 my @values = split /\|/, $condition->{value};
1885 $query .= qq| AND $condition->{field} IN (|
1886 . join( ',', ('?') x scalar @values )
1888 push @params, @values;
1890 $query .= qq| AND $condition->{field} = ?|;
1891 push @params, $condition->{value};
1895 if ( defined $age ) {
1896 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1899 my $sth = $dbh->prepare($query);
1900 $sth->execute( @params );
1901 while ( my $values = $sth->fetchrow_hashref ) {
1902 my $biblionumber = $values->{biblionumber};
1903 my $itemnumber = $values->{itemnumber};
1904 my $item = Koha::Items->find($itemnumber);
1905 for my $substitution ( @$substitutions ) {
1906 my $field = $substitution->{item_field};
1907 my $value = $substitution->{value};
1908 next unless $substitution->{field};
1909 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1910 $item->$field($value);
1911 push @{ $report->{$itemnumber} }, $substitution;
1913 $item->store unless $report_only;