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 );
68 use Koha::Biblioitems;
71 use Koha::SearchEngine;
72 use Koha::SearchEngine::Indexer;
73 use Koha::SearchEngine::Search;
78 C4::Items - item management functions
82 This module contains an API for manipulating item
83 records in Koha, and is used by cataloguing, circulation,
84 acquisitions, and serials management.
86 # FIXME This POD is not up-to-date
87 A Koha item record is stored in two places: the
88 items table and embedded in a MARC tag in the XML
89 version of the associated bib record in C<biblioitems.marcxml>.
90 This is done to allow the item information to be readily
91 indexed (e.g., by Zebra), but means that each item
92 modification transaction must keep the items table
93 and the MARC XML in sync at all times.
95 The items table will be considered authoritative. In other
96 words, if there is ever a discrepancy between the items
97 table and the MARC XML, the items table should be considered
100 =head1 HISTORICAL NOTE
102 Most of the functions in C<C4::Items> were originally in
103 the C<C4::Biblio> module.
105 =head1 CORE EXPORTED FUNCTIONS
107 The following functions are meant for use by users
114 CartToShelf($itemnumber);
116 Set the current shelving location of the item record
117 to its stored permanent shelving location. This is
118 primarily used to indicate when an item whose current
119 location is a special processing ('PROC') or shelving cart
120 ('CART') location is back in the stacks.
125 my ( $itemnumber ) = @_;
127 unless ( $itemnumber ) {
128 croak "FAILED CartToShelf() - no itemnumber supplied";
131 my $item = Koha::Items->find($itemnumber);
132 if ( $item->location eq 'CART' ) {
133 $item->location($item->permanent_location)->store;
137 =head2 AddItemFromMarc
139 my ($biblionumber, $biblioitemnumber, $itemnumber)
140 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
142 Given a MARC::Record object containing an embedded item
143 record and a biblionumber, create a new item record.
145 The final optional parameter, C<$params>, may contain
146 'skip_record_index' key, which relayed down to Koha::Item/store,
147 there it prevents calling of index_records,
148 which takes most of the time in batch adds/deletes: index_records
149 to be called later in C<additem.pl> after the whole loop.
151 You may also optionally pass biblioitemnumber in the params hash to
152 boost performance of inserts by preventing a lookup in Koha::Item.
155 skip_record_index => 1|0
156 biblioitemnumber => $biblioitemnumber
160 sub AddItemFromMarc {
161 my $source_item_marc = shift;
162 my $biblionumber = shift;
163 my $params = @_ ? shift : {};
165 my $dbh = C4::Context->dbh;
167 # parse item hash from MARC
168 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
169 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
170 my $localitemmarc = MARC::Record->new;
171 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
173 my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
174 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
175 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
176 $item_values->{biblionumber} = $biblionumber;
177 $item_values->{biblioitemnumber} = $params->{biblioitemnumber};
178 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
179 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
180 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
181 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
184 =head2 AddItemBatchFromMarc
186 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
187 $biblionumber, $biblioitemnumber, $frameworkcode);
189 Efficiently create item records from a MARC biblio record with
190 embedded item fields. This routine is suitable for batch jobs.
192 This API assumes that the bib record has already been
193 saved to the C<biblio> and C<biblioitems> tables. It does
194 not expect that C<biblio_metadata.metadata> is populated, but it
195 will do so via a call to ModBibiloMarc.
197 The goal of this API is to have a similar effect to using AddBiblio
198 and AddItems in succession, but without inefficient repeated
199 parsing of the MARC XML bib record.
201 This function returns an arrayref of new itemsnumbers and an arrayref of item
202 errors encountered during the processing. Each entry in the errors
203 list is a hashref containing the following keys:
209 Sequence number of original item tag in the MARC record.
213 Item barcode, provide to assist in the construction of
214 useful error messages.
218 Code representing the error condition. Can be 'duplicate_barcode',
219 'invalid_homebranch', or 'invalid_holdingbranch'.
221 =item error_information
223 Additional information appropriate to the error condition.
229 sub AddItemBatchFromMarc {
230 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
231 my @itemnumbers = ();
233 my $dbh = C4::Context->dbh;
235 # We modify the record, so lets work on a clone so we don't change the
237 $record = $record->clone();
238 # loop through the item tags and start creating items
239 my @bad_item_fields = ();
240 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
241 my $item_sequence_num = 0;
242 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
243 $item_sequence_num++;
244 # we take the item field and stick it into a new
245 # MARC record -- this is required so far because (FIXME)
246 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
247 # and there is no TransformMarcFieldToKoha
248 my $temp_item_marc = MARC::Record->new();
249 $temp_item_marc->append_fields($item_field);
251 # add biblionumber and biblioitemnumber
252 my $item = TransformMarcToKoha({ record => $temp_item_marc, limit_table => 'items' });
253 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
254 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
255 $item->{'biblionumber'} = $biblionumber;
256 $item->{'biblioitemnumber'} = $biblioitemnumber;
257 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
258 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
260 # check for duplicate barcode
261 my %item_errors = CheckItemPreSave($item);
263 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
264 push @bad_item_fields, $item_field;
268 my $item_object = Koha::Item->new($item)->store;
269 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
271 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
273 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
274 $item_field->replace_with($new_item_marc->field($itemtag));
277 # remove any MARC item fields for rejected items
278 foreach my $item_field (@bad_item_fields) {
279 $record->delete_field($item_field);
282 return (\@itemnumbers, \@errors);
285 =head2 ModItemFromMarc
287 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
289 The final optional parameter, C<$params>, expected to contain
290 'skip_record_index' key, which relayed down to Koha::Item/store,
291 there it prevents calling of index_records,
292 which takes most of the time in batch adds/deletes: index_records better
293 to be called later in C<additem.pl> after the whole loop.
296 skip_record_index => 1|0
300 sub ModItemFromMarc {
301 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
303 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
304 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
306 my $localitemmarc = MARC::Record->new;
307 $localitemmarc->append_fields( $item_marc->field($itemtag) );
308 my $item_object = Koha::Items->find($itemnumber);
309 my $item = TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
311 # When importing items we blank this column, we need to set it to the existing value
312 # to prevent it being blanked by set_or_blank
313 $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
315 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
316 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
318 # Retrieving the values for the fields that are not linked
319 my @mapped_fields = Koha::MarcSubfieldStructures->search(
321 frameworkcode => $frameworkcode,
322 kohafield => { -like => "items.%" }
324 )->get_column('kohafield');
325 for my $c ( $item_object->_result->result_source->columns ) {
326 next if grep { "items.$c" eq $_ } @mapped_fields;
327 $item->{$c} = $item_object->$c;
330 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
331 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
332 $item->{itemnumber} = $itemnumber;
333 $item->{biblionumber} = $biblionumber;
335 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
336 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
337 $item_object = $item_object->set_or_blank($item);
338 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
340 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
342 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
343 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
344 $item_object->store({ skip_record_index => $params->{skip_record_index} });
346 return $item_object->unblessed;
349 =head2 ModItemTransfer
351 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
353 Marks an item as being transferred from one branch to another and records the trigger.
355 The last optional parameter allows for passing skip_record_index through to the items store call.
359 sub ModItemTransfer {
360 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
362 my $dbh = C4::Context->dbh;
363 my $item = Koha::Items->find( $itemnumber );
365 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
366 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
367 # will have been preceded by a check of branch transfer limits)
368 my $to_library = Koha::Libraries->find($tobranch);
369 my $transfer = $item->request_transfer(
378 # Immediately set the item to in transit if it is checked in
379 if ( !$item->checkout ) {
380 $item->holdingbranch($frombranch)->store(
383 skip_record_index => $params->{skip_record_index}
392 =head2 ModDateLastSeen
394 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
396 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
397 C<$itemnumber> is the item number
398 C<$leave_item_lost> determines if a lost item will be found or remain lost
400 The last optional parameter allows for passing skip_record_index through to the items store call.
404 sub ModDateLastSeen {
405 my ( $itemnumber, $leave_item_lost, $params ) = @_;
407 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
409 my $item = Koha::Items->find($itemnumber);
410 $item->datelastseen($today);
411 $item->itemlost(0) unless $leave_item_lost;
412 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
415 =head2 CheckItemPreSave
417 my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
419 my %errors = CheckItemPreSave($item_ref);
420 if (exists $errors{'duplicate_barcode'}) {
421 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
422 } elsif (exists $errors{'invalid_homebranch'}) {
423 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
424 } elsif (exists $errors{'invalid_holdingbranch'}) {
425 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
430 Given a hashref containing item fields, determine if it can be
431 inserted or updated in the database. Specifically, checks for
432 database integrity issues, and returns a hash containing any
433 of the following keys, if applicable.
437 =item duplicate_barcode
439 Barcode, if it duplicates one already found in the database.
441 =item invalid_homebranch
443 Home branch, if not defined in branches table.
445 =item invalid_holdingbranch
447 Holding branch, if not defined in branches table.
451 This function does NOT implement any policy-related checks,
452 e.g., whether current operator is allowed to save an
453 item that has a given branch code.
457 sub CheckItemPreSave {
458 my $item_ref = shift;
462 # check for duplicate barcode
463 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
464 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
465 if ($existing_item) {
466 if (!exists $item_ref->{'itemnumber'} # new item
467 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
468 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
473 # check for valid home branch
474 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
475 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
476 unless (defined $home_library) {
477 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
481 # check for valid holding branch
482 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
483 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
484 unless (defined $holding_library) {
485 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
493 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
495 The following functions provide various ways of
496 getting an item record, a set of item records, or
497 lists of authorized values for certain item fields.
501 =head2 GetItemsForInventory
503 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
504 minlocation => $minlocation,
505 maxlocation => $maxlocation,
506 location => $location,
507 ignoreissued => $ignoreissued,
508 datelastseen => $datelastseen,
509 branchcode => $branchcode,
513 statushash => $statushash,
514 itemtypes => \@itemsarray,
517 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
519 The sub returns a reference to a list of hashes, each containing
520 itemnumber, author, title, barcode, item callnumber, and date last
521 seen. It is ordered by callnumber then title.
523 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
524 the datelastseen can be used to specify that you want to see items not seen since a past date only.
525 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
526 $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.
528 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
532 sub GetItemsForInventory {
533 my ( $parameters ) = @_;
534 my $minlocation = $parameters->{'minlocation'} // '';
535 my $maxlocation = $parameters->{'maxlocation'} // '';
536 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
537 my $location = $parameters->{'location'} // '';
538 my $itemtype = $parameters->{'itemtype'} // '';
539 my $ignoreissued = $parameters->{'ignoreissued'} // '';
540 my $datelastseen = $parameters->{'datelastseen'} // '';
541 my $branchcode = $parameters->{'branchcode'} // '';
542 my $branch = $parameters->{'branch'} // '';
543 my $offset = $parameters->{'offset'} // '';
544 my $size = $parameters->{'size'} // '';
545 my $statushash = $parameters->{'statushash'} // '';
546 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
547 my $itemtypes = $parameters->{'itemtypes'} || [];
548 my $ccode = $parameters->{'ccode'} // '';
550 my $dbh = C4::Context->dbh;
551 my ( @bind_params, @where_strings );
553 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
554 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
556 my $select_columns = q{
557 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
560 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
563 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
564 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
567 for my $authvfield (keys %$statushash){
568 if ( scalar @{$statushash->{$authvfield}} > 0 ){
569 my $joinedvals = join ',', @{$statushash->{$authvfield}};
570 push @where_strings, "$authvfield in (" . $joinedvals . ")";
576 push @where_strings, 'ccode = ?';
577 push @bind_params, $ccode;
581 push @where_strings, 'items.cn_sort >= ?';
582 push @bind_params, $min_cnsort;
586 push @where_strings, 'items.cn_sort <= ?';
587 push @bind_params, $max_cnsort;
591 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
592 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
593 push @bind_params, $datelastseen;
597 push @where_strings, 'items.location = ?';
598 push @bind_params, $location;
602 if($branch eq "homebranch"){
603 push @where_strings, 'items.homebranch = ?';
605 push @where_strings, 'items.holdingbranch = ?';
607 push @bind_params, $branchcode;
611 push @where_strings, 'biblioitems.itemtype = ?';
612 push @bind_params, $itemtype;
614 if ( $ignoreissued) {
615 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
616 push @where_strings, 'issues.date_due IS NULL';
619 if ( $ignore_waiting_holds ) {
620 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
621 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
625 my $itemtypes_str = join ', ', @$itemtypes;
626 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
629 if ( @where_strings ) {
631 $query .= join ' AND ', @where_strings;
633 my $count_query = $select_count . $query;
634 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
635 $query .= " LIMIT $offset, $size" if ($offset and $size);
636 $query = $select_columns . $query;
637 my $sth = $dbh->prepare($query);
638 $sth->execute( @bind_params );
641 my $tmpresults = $sth->fetchall_arrayref({});
642 $sth = $dbh->prepare( $count_query );
643 $sth->execute( @bind_params );
644 my ($iTotalRecords) = $sth->fetchrow_array();
646 my @avs = Koha::AuthorisedValues->search(
647 { 'marc_subfield_structures.kohafield' => { '>' => '' },
648 'me.authorised_value' => { '>' => '' },
650 { join => { category => 'marc_subfield_structures' },
651 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
652 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
653 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
657 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
659 foreach my $row (@$tmpresults) {
662 foreach (keys %$row) {
665 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
668 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
674 return (\@results, $iTotalRecords);
679 @results = GetItemsInfo($biblionumber);
681 Returns information about items with the given biblionumber.
683 C<GetItemsInfo> returns a list of references-to-hash. Each element
684 contains a number of keys. Most of them are attributes from the
685 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
686 Koha database. Other keys include:
690 =item C<$data-E<gt>{branchname}>
692 The name (not the code) of the branch to which the book belongs.
694 =item C<$data-E<gt>{datelastseen}>
696 This is simply C<items.datelastseen>, except that while the date is
697 stored in YYYY-MM-DD format in the database, here it is converted to
698 DD/MM/YYYY format. A NULL date is returned as C<//>.
700 =item C<$data-E<gt>{datedue}>
702 =item C<$data-E<gt>{class}>
704 This is the concatenation of C<biblioitems.classification>, the book's
705 Dewey code, and C<biblioitems.subclass>.
707 =item C<$data-E<gt>{ocount}>
709 I think this is the number of copies of the book available.
711 =item C<$data-E<gt>{order}>
713 If this is set, it is set to C<One Order>.
720 my ( $biblionumber ) = @_;
721 my $dbh = C4::Context->dbh;
722 require C4::Languages;
723 my $language = C4::Languages::getlanguage();
729 biblioitems.itemtype,
732 biblioitems.publicationyear,
733 biblioitems.publishercode,
734 biblioitems.volumedate,
735 biblioitems.volumedesc,
738 items.notforloan as itemnotforloan,
739 issues.borrowernumber,
740 issues.date_due as datedue,
741 issues.onsite_checkout,
742 borrowers.cardnumber,
745 borrowers.branchcode as bcode,
747 serial.publisheddate,
748 itemtypes.description,
749 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
750 itemtypes.notforloan as notforloan_per_itemtype,
754 holding.opac_info as holding_branch_opac_info,
755 home.opac_info as home_branch_opac_info,
756 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
758 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
759 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
760 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
761 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
762 LEFT JOIN issues USING (itemnumber)
763 LEFT JOIN borrowers USING (borrowernumber)
764 LEFT JOIN serialitems USING (itemnumber)
765 LEFT JOIN serial USING (serialid)
766 LEFT JOIN itemtypes ON itemtypes.itemtype = "
767 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
769 LEFT JOIN tmp_holdsqueue USING (itemnumber)
770 LEFT JOIN localization ON itemtypes.itemtype = localization.code
771 AND localization.entity = 'itemtypes'
772 AND localization.lang = ?
775 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
776 my $sth = $dbh->prepare($query);
777 $sth->execute($language, $biblionumber);
782 my $userenv = C4::Context->userenv;
783 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
784 while ( my $data = $sth->fetchrow_hashref ) {
785 if ( $data->{borrowernumber} && $want_not_same_branch) {
786 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
789 $serial ||= $data->{'serial'};
792 # get notforloan complete status if applicable
793 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
794 $data->{notforloanvalue} = $descriptions->{lib} // '';
795 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
797 # get restricted status and description if applicable
798 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
799 $data->{restrictedvalue} = $descriptions->{lib} // '';
800 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
802 # my stack procedures
803 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
804 $data->{stack} = $descriptions->{lib} // '';
806 # Find the last 3 people who borrowed this item.
807 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
809 AND old_issues.borrowernumber = borrowers.borrowernumber
810 ORDER BY returndate DESC
812 $sth2->execute($data->{'itemnumber'});
814 while (my $data2 = $sth2->fetchrow_hashref()) {
815 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
816 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
817 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
821 $results[$i] = $data;
826 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
830 =head2 GetItemsLocationInfo
832 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
834 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
836 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
840 =item C<$data-E<gt>{homebranch}>
842 Branch Name of the item's homebranch
844 =item C<$data-E<gt>{holdingbranch}>
846 Branch Name of the item's holdingbranch
848 =item C<$data-E<gt>{location}>
850 Item's shelving location code
852 =item C<$data-E<gt>{location_intranet}>
854 The intranet description for the Shelving Location as set in authorised_values 'LOC'
856 =item C<$data-E<gt>{location_opac}>
858 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
861 =item C<$data-E<gt>{itemcallnumber}>
863 Item's itemcallnumber
865 =item C<$data-E<gt>{cn_sort}>
867 Item's call number normalized for sorting
873 sub GetItemsLocationInfo {
874 my $biblionumber = shift;
877 my $dbh = C4::Context->dbh;
878 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
879 location, itemcallnumber, cn_sort
880 FROM items, branches as a, branches as b
881 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
883 ORDER BY cn_sort ASC";
884 my $sth = $dbh->prepare($query);
885 $sth->execute($biblionumber);
887 while ( my $data = $sth->fetchrow_hashref ) {
888 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
889 $av = $av->count ? $av->next : undef;
890 $data->{location_intranet} = $av ? $av->lib : '';
891 $data->{location_opac} = $av ? $av->opac_description : '';
892 push @results, $data;
897 =head2 GetHostItemsInfo
899 $hostiteminfo = GetHostItemsInfo($hostfield);
900 Returns the iteminfo for items linked to records via a host field
904 sub GetHostItemsInfo {
908 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
909 return @returnitemsInfo;
913 if( C4::Context->preference('marcflavour') eq 'MARC21' ) {
914 @fields = $record->field('773');
915 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
916 @fields = $record->field('461');
919 foreach my $hostfield ( @fields ) {
920 my $hostbiblionumber = $hostfield->subfield("0");
921 my $linkeditemnumber = $hostfield->subfield("9");
922 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
923 foreach my $hostitemInfo (@hostitemInfos) {
924 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
925 push @returnitemsInfo, $hostitemInfo;
930 return @returnitemsInfo;
933 =head2 get_hostitemnumbers_of
935 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
937 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
939 Return a reference on a hash where key is a biblionumber and values are
940 references on array of itemnumbers.
945 sub get_hostitemnumbers_of {
946 my ($biblionumber) = @_;
948 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
952 my $biblio = Koha::Biblios->find($biblionumber);
953 my $marcrecord = $biblio->metadata->record;
954 return unless $marcrecord;
956 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
958 my $marcflavor = C4::Context->preference('marcflavour');
959 if ( $marcflavor eq 'MARC21' ) {
964 elsif ( $marcflavor eq 'UNIMARC' ) {
970 foreach my $hostfield ( $marcrecord->field($tag) ) {
971 my $hostbiblionumber = $hostfield->subfield($biblio_s);
972 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
973 my $linkeditemnumber = $hostfield->subfield($item_s);
974 if ( ! $linkeditemnumber ) {
975 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
978 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
979 push @returnhostitemnumbers, $linkeditemnumber
983 return @returnhostitemnumbers;
986 =head2 GetHiddenItemnumbers
988 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
990 Given a list of items it checks which should be hidden from the OPAC given
991 the current configuration. Returns a list of itemnumbers corresponding to
992 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
997 sub GetHiddenItemnumbers {
999 my $items = $params->{items};
1000 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1001 foreach my $except (split(/\|/, $exceptions)){
1002 if ($params->{'borcat'} eq $except){
1003 return; # we don't hide anything for this borrower category
1009 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
1012 unless $hidingrules;
1014 my $dbh = C4::Context->dbh;
1017 foreach my $item (@$items) {
1019 # We check each rule
1020 foreach my $field (keys %$hidingrules) {
1022 if (exists $item->{$field}) {
1023 $val = $item->{$field};
1026 my $query = "SELECT $field from items where itemnumber = ?";
1027 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1029 $val = '' unless defined $val;
1031 # If the results matches the values in the yaml file
1032 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1034 # We add the itemnumber to the list
1035 push @resultitems, $item->{'itemnumber'};
1037 # If at least one rule matched for an item, no need to test the others
1042 return @resultitems;
1045 =head1 LIMITED USE FUNCTIONS
1047 The following functions, while part of the public API,
1048 are not exported. This is generally because they are
1049 meant to be used by only one script for a specific
1050 purpose, and should not be used in any other context
1051 without careful thought.
1057 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1059 Returns MARC::Record of the item passed in parameter.
1060 This function is meant for use only in C<cataloguing/additem.pl>,
1061 where it is needed to support that script's MARC-like
1067 my ( $biblionumber, $itemnumber ) = @_;
1069 # GetMarcItem has been revised so that it does the following:
1070 # 1. Gets the item information from the items table.
1071 # 2. Converts it to a MARC field for storage in the bib record.
1073 # The previous behavior was:
1074 # 1. Get the bib record.
1075 # 2. Return the MARC tag corresponding to the item record.
1077 # The difference is that one treats the items row as authoritative,
1078 # while the other treats the MARC representation as authoritative
1079 # under certain circumstances.
1081 my $item = Koha::Items->find($itemnumber) or return;
1083 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1084 # Also, don't emit a subfield if the underlying field is blank.
1086 return Item2Marc($item->unblessed, $biblionumber);
1090 my ($itemrecord,$biblionumber)=@_;
1093 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1094 } keys %{ $itemrecord }
1096 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1097 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1098 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1099 "items.itemnumber", $framework,
1102 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1103 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1104 foreach my $field ($itemmarc->field($itemtag)){
1105 $field->add_subfields(@$unlinked_item_subfields);
1111 =head1 PRIVATE FUNCTIONS AND VARIABLES
1113 The following functions are not meant to be called
1114 directly, but are documented in order to explain
1115 the inner workings of C<C4::Items>.
1119 =head2 _marc_from_item_hash
1121 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1123 Given an item hash representing a complete item record,
1124 create a C<MARC::Record> object containing an embedded
1125 tag representing that item.
1127 The third, optional parameter C<$unlinked_item_subfields> is
1128 an arrayref of subfields (not mapped to C<items> fields per the
1129 framework) to be added to the MARC representation
1134 sub _marc_from_item_hash {
1136 my $frameworkcode = shift;
1137 my $unlinked_item_subfields;
1139 $unlinked_item_subfields = shift;
1142 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1143 # Also, don't emit a subfield if the underlying field is blank.
1144 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1145 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1146 : () } keys %{ $item } };
1148 my $item_marc = MARC::Record->new();
1149 foreach my $item_field ( keys %{$mungeditem} ) {
1150 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1151 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1152 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1153 foreach my $value (@values){
1154 if ( my $field = $item_marc->field($tag) ) {
1155 $field->add_subfields( $subfield => $value );
1157 my $add_subfields = [];
1158 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1159 $add_subfields = $unlinked_item_subfields;
1161 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1169 =head2 _repack_item_errors
1171 Add an error message hash generated by C<CheckItemPreSave>
1172 to a list of errors.
1176 sub _repack_item_errors {
1177 my $item_sequence_num = shift;
1178 my $item_ref = shift;
1179 my $error_ref = shift;
1181 my @repacked_errors = ();
1183 foreach my $error_code (sort keys %{ $error_ref }) {
1184 my $repacked_error = {};
1185 $repacked_error->{'item_sequence'} = $item_sequence_num;
1186 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1187 $repacked_error->{'error_code'} = $error_code;
1188 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1189 push @repacked_errors, $repacked_error;
1192 return @repacked_errors;
1195 =head2 _get_unlinked_item_subfields
1197 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1201 sub _get_unlinked_item_subfields {
1202 my $original_item_marc = shift;
1203 my $frameworkcode = shift;
1205 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1207 # assume that this record has only one field, and that that
1208 # field contains only the item information
1210 my @fields = $original_item_marc->fields();
1211 if ($#fields > -1) {
1212 my $field = $fields[0];
1213 my $tag = $field->tag();
1214 foreach my $subfield ($field->subfields()) {
1215 if (defined $subfield->[1] and
1216 $subfield->[1] ne '' and
1217 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1218 push @$subfields, $subfield->[0] => $subfield->[1];
1225 =head2 _get_unlinked_subfields_xml
1227 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1231 sub _get_unlinked_subfields_xml {
1232 my $unlinked_item_subfields = shift;
1235 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1236 my $marc = MARC::Record->new();
1237 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1238 # used in the framework
1239 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1240 $marc->encoding("UTF-8");
1241 $xml = $marc->as_xml("USMARC");
1247 =head2 _parse_unlinked_item_subfields_from_xml
1249 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1253 sub _parse_unlinked_item_subfields_from_xml {
1255 require C4::Charset;
1256 return unless defined $xml and $xml ne "";
1257 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1258 my $unlinked_subfields = [];
1259 my @fields = $marc->fields();
1260 if ($#fields > -1) {
1261 foreach my $subfield ($fields[0]->subfields()) {
1262 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1265 return $unlinked_subfields;
1268 =head2 GetAnalyticsCount
1270 $count= &GetAnalyticsCount($itemnumber)
1272 counts Usage of itemnumber in Analytical bibliorecords.
1276 sub GetAnalyticsCount {
1277 my ($itemnumber) = @_;
1279 if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
1283 ### ZOOM search here
1285 $query= "hi=".$itemnumber;
1286 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1287 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1291 sub _SearchItems_build_where_fragment {
1294 my $dbh = C4::Context->dbh;
1297 if (exists($filter->{conjunction})) {
1298 my (@where_strs, @where_args);
1299 foreach my $f (@{ $filter->{filters} }) {
1300 my $fragment = _SearchItems_build_where_fragment($f);
1302 push @where_strs, $fragment->{str};
1303 push @where_args, @{ $fragment->{args} };
1308 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1311 args => \@where_args,
1315 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1316 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1317 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1318 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1319 my @operators = qw(= != > < >= <= is like);
1320 push @operators, 'not like';
1321 my $field = $filter->{field} // q{};
1322 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1323 my $op = $filter->{operator};
1324 my $query = $filter->{query};
1325 my $ifnull = $filter->{ifnull};
1327 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1328 $op = '='; # default operator
1332 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1334 my $marcsubfield = $2;
1335 my ($kohafield) = $dbh->selectrow_array(q|
1336 SELECT kohafield FROM marc_subfield_structure
1337 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1338 |, undef, $marcfield, $marcsubfield);
1341 $column = $kohafield;
1343 # MARC field is not linked to a DB field so we need to use
1344 # ExtractValue on marcxml from biblio_metadata or
1345 # items.more_subfields_xml, depending on the MARC field.
1348 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1349 if ($marcfield eq $itemfield) {
1350 $sqlfield = 'more_subfields_xml';
1351 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1353 $sqlfield = 'metadata'; # From biblio_metadata
1354 if ($marcfield < 10) {
1355 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1357 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1360 $column = "ExtractValue($sqlfield, '$xpath')";
1366 if ( defined $ifnull ) {
1367 $column = "COALESCE($column, ?)";
1370 if (ref $query eq 'ARRAY') {
1373 } elsif ($op eq '!=') {
1377 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1380 } elsif ( $op eq 'is' ) {
1382 str => "$column $op $query",
1387 str => "$column $op ?",
1392 if ( defined $ifnull ) {
1393 unshift @{ $where_fragment->{args} }, $ifnull;
1398 return $where_fragment;
1403 my ($items, $total) = SearchItems($filter, $params);
1405 Perform a search among items
1407 $filter is a reference to a hash which can be a filter, or a combination of filters.
1409 A filter has the following keys:
1413 =item * field: the name of a SQL column in table items
1415 =item * query: the value to search in this column
1417 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1421 A combination of filters hash the following keys:
1425 =item * conjunction: 'AND' or 'OR'
1427 =item * filters: array ref of filters
1431 $params is a reference to a hash that can contain the following parameters:
1435 =item * rows: Number of items to return. 0 returns everything (default: 0)
1437 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1440 =item * sortby: A SQL column name in items table to sort on
1442 =item * sortorder: 'ASC' or 'DESC'
1449 my ($filter, $params) = @_;
1453 return unless ref $filter eq 'HASH';
1454 return unless ref $params eq 'HASH';
1456 # Default parameters
1457 $params->{rows} ||= 0;
1458 $params->{page} ||= 1;
1459 $params->{sortby} ||= 'itemnumber';
1460 $params->{sortorder} ||= 'ASC';
1462 my ($where_str, @where_args);
1463 my $where_fragment = _SearchItems_build_where_fragment($filter);
1464 if ($where_fragment) {
1465 $where_str = $where_fragment->{str};
1466 @where_args = @{ $where_fragment->{args} };
1469 my $dbh = C4::Context->dbh;
1471 SELECT SQL_CALC_FOUND_ROWS items.*
1473 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1474 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1475 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1476 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1479 if (defined $where_str and $where_str ne '') {
1480 $query .= qq{ AND $where_str };
1483 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1484 push @where_args, C4::Context->preference('marcflavour');
1486 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1487 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1488 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1489 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1491 if ( $params->{sortby} eq 'availability' ) {
1492 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1493 $query .= qq{ ORDER BY onloan $sortorder };
1495 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1496 ? $params->{sortby} : 'itemnumber';
1497 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1498 $query .= qq{ ORDER BY $sortby $sortorder };
1501 my $rows = $params->{rows};
1504 my $offset = $rows * ($params->{page}-1);
1505 $query .= qq { LIMIT ?, ? };
1506 push @limit_args, $offset, $rows;
1509 my $sth = $dbh->prepare($query);
1510 my $rv = $sth->execute(@where_args, @limit_args);
1512 return unless ($rv);
1513 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1515 return ($sth->fetchall_arrayref({}), $total_rows);
1519 =head1 OTHER FUNCTIONS
1523 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1525 Find the given $subfield in the given $tag in the given
1526 MARC::Record $record. If the subfield is found, returns
1527 the (indicators, value) pair; otherwise, (undef, undef) is
1531 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1532 I suggest we export it from this module.
1537 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1540 if ( $tagfield < 10 ) {
1541 if ( $record->field($tagfield) ) {
1542 push @result, $record->field($tagfield)->data();
1547 foreach my $field ( $record->field($tagfield) ) {
1548 my @subfields = $field->subfields();
1549 foreach my $subfield (@subfields) {
1550 if ( @$subfield[0] eq $insubfield ) {
1551 push @result, @$subfield[1];
1552 $indicator = $field->indicator(1) . $field->indicator(2);
1557 return ( $indicator, @result );
1561 =head2 PrepareItemrecordDisplay
1563 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1565 Returns a hash with all the fields for Display a given item data in a template
1567 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1569 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1573 sub PrepareItemrecordDisplay {
1575 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1577 my $dbh = C4::Context->dbh;
1578 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1579 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1581 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1582 # a shared data structure. No plugin (including custom ones) should change
1583 # its contents. See also GetMarcStructure.
1584 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1586 # Pick the default location from NewItemsDefaultLocation
1587 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1588 $defaultvalues //= {};
1589 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1592 # return nothing if we don't have found an existing framework.
1593 return q{} unless $tagslib;
1596 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1600 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1602 SELECT authorised_value,lib FROM authorised_values
1605 LEFT JOIN authorised_values_branches ON ( id = av_id )
1610 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1611 $query .= qq{ ORDER BY lib};
1612 my $authorised_values_sth = $dbh->prepare( $query );
1613 foreach my $tag ( sort keys %{$tagslib} ) {
1616 # loop through each subfield
1618 foreach my $subfield (
1619 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1620 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1621 values %{ $tagslib->{$tag} } )
1623 next unless ( $subfield->{'tab'} );
1624 next if ( $subfield->{'tab'} ne "10" );
1626 $subfield_data{tag} = $tag;
1627 $subfield_data{subfield} = $subfield->{subfield};
1628 $subfield_data{countsubfield} = $cntsubf++;
1629 $subfield_data{kohafield} = $subfield->{kohafield};
1630 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1632 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1633 $subfield_data{marc_lib} = $subfield->{lib};
1634 $subfield_data{mandatory} = $subfield->{mandatory};
1635 $subfield_data{repeatable} = $subfield->{repeatable};
1636 $subfield_data{hidden} = "display:none"
1637 if ( ( $subfield->{hidden} > 4 )
1638 || ( $subfield->{hidden} < -4 ) );
1639 my ( $x, $defaultvalue );
1641 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1643 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1644 if ( !defined $defaultvalue ) {
1645 $defaultvalue = q||;
1647 $defaultvalue =~ s/"/"/g;
1648 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1649 my $today_dt = dt_from_string;
1650 my $year = $today_dt->strftime('%Y');
1651 my $shortyear = $today_dt->strftime('%y');
1652 my $month = $today_dt->strftime('%m');
1653 my $day = $today_dt->strftime('%d');
1654 $defaultvalue =~ s/<<YYYY>>/$year/g;
1655 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1656 $defaultvalue =~ s/<<MM>>/$month/g;
1657 $defaultvalue =~ s/<<DD>>/$day/g;
1659 # And <<USER>> with surname (?)
1661 ( C4::Context->userenv
1662 ? C4::Context->userenv->{'surname'}
1663 : "superlibrarian" );
1664 $defaultvalue =~ s/<<USER>>/$username/g;
1667 my $maxlength = $subfield->{maxlength};
1669 # search for itemcallnumber if applicable
1670 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1671 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1672 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1673 my $CNtag = substr( $itemcn_pref, 0, 3 );
1674 next unless my $field = $itemrecord->field($CNtag);
1675 my $CNsubfields = substr( $itemcn_pref, 3 );
1676 $CNsubfields = undef if $CNsubfields eq '';
1677 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1678 last if $defaultvalue;
1681 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1683 && $defaultvalues->{'callnumber'} ) {
1684 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1685 # if the item record exists, only use default value if the item has no callnumber
1686 $defaultvalue = $defaultvalues->{callnumber};
1687 } elsif ( !$itemrecord and $defaultvalues ) {
1688 # if the item record *doesn't* exists, always use the default value
1689 $defaultvalue = $defaultvalues->{callnumber};
1692 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1694 && $defaultvalues->{'branchcode'} ) {
1695 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1696 $defaultvalue = $defaultvalues->{branchcode};
1699 if ( ( $subfield->{kohafield} eq 'items.location' )
1701 && $defaultvalues->{'location'} ) {
1703 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1704 # if the item record exists, only use default value if the item has no locationr
1705 $defaultvalue = $defaultvalues->{location};
1706 } elsif ( !$itemrecord and $defaultvalues ) {
1707 # if the item record *doesn't* exists, always use the default value
1708 $defaultvalue = $defaultvalues->{location};
1711 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1713 && $defaultvalues->{'ccode'} ) {
1715 if ( !$itemrecord and $defaultvalues ) {
1716 # if the item record *doesn't* exists, always use the default value
1717 $defaultvalue = $defaultvalues->{ccode};
1720 if ( $subfield->{authorised_value} ) {
1721 my @authorised_values;
1724 # builds list, depending on authorised value...
1726 if ( $subfield->{'authorised_value'} eq "branches" ) {
1727 if ( ( C4::Context->preference("IndependentBranches") )
1728 && !C4::Context->IsSuperLibrarian() ) {
1729 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1730 $sth->execute( C4::Context->userenv->{branch} );
1731 push @authorised_values, ""
1732 unless ( $subfield->{mandatory} );
1733 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1734 push @authorised_values, $branchcode;
1735 $authorised_lib{$branchcode} = $branchname;
1738 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1740 push @authorised_values, ""
1741 unless ( $subfield->{mandatory} );
1742 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1743 push @authorised_values, $branchcode;
1744 $authorised_lib{$branchcode} = $branchname;
1748 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1749 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1750 $defaultvalue = $defaultvalues->{branchcode};
1754 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1755 my $itemtypes = Koha::ItemTypes->search_with_localization;
1756 push @authorised_values, "";
1757 while ( my $itemtype = $itemtypes->next ) {
1758 push @authorised_values, $itemtype->itemtype;
1759 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1761 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1762 $defaultvalue = $defaultvalues->{'itemtype'};
1766 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1767 push @authorised_values, "";
1769 my $class_sources = GetClassSources();
1770 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1772 foreach my $class_source (sort keys %$class_sources) {
1773 next unless $class_sources->{$class_source}->{'used'} or
1774 ($class_source eq $default_source);
1775 push @authorised_values, $class_source;
1776 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1779 $defaultvalue = $default_source;
1781 #---- "true" authorised value
1783 $authorised_values_sth->execute(
1784 $subfield->{authorised_value},
1785 $branch_limit ? $branch_limit : ()
1787 push @authorised_values, "";
1788 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1789 push @authorised_values, $value;
1790 $authorised_lib{$value} = $lib;
1793 $subfield_data{marc_value} = {
1795 values => \@authorised_values,
1796 default => $defaultvalue // q{},
1797 labels => \%authorised_lib,
1799 } elsif ( $subfield->{value_builder} ) {
1801 require Koha::FrameworkPlugin;
1802 my $plugin = Koha::FrameworkPlugin->new({
1803 name => $subfield->{value_builder},
1806 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1807 $plugin->build( $pars );
1808 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1809 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1811 if( !$plugin->errstr ) {
1812 #TODO Move html to template; see report 12176/13397
1813 my $tab= $plugin->noclick? '-1': '';
1814 my $class= $plugin->noclick? ' disabled': '';
1815 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1816 $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;
1818 warn $plugin->errstr;
1819 $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
1822 elsif ( $tag eq '' ) { # it's an hidden field
1823 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1825 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1826 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1828 elsif ( length($defaultvalue) > 100
1829 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1830 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1831 or (C4::Context->preference("marcflavour") eq "MARC21" and
1832 500 <= $tag && $tag < 600 )
1834 # oversize field (textarea)
1835 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1837 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1839 push( @loop_data, \%subfield_data );
1844 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1845 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1848 'itemtagfield' => $itemtagfield,
1849 'itemtagsubfield' => $itemtagsubfield,
1850 'itemnumber' => $itemnumber,
1851 'iteminformation' => \@loop_data
1855 sub ToggleNewStatus {
1856 my ( $params ) = @_;
1857 my @rules = @{ $params->{rules} };
1858 my $report_only = $params->{report_only};
1860 my $dbh = C4::Context->dbh;
1862 my @item_columns = map { "items.$_" } Koha::Items->columns;
1863 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1865 for my $rule ( @rules ) {
1866 my $age = $rule->{age};
1867 # Default to using items.dateaccessioned if there's an old item modification rule
1868 # missing an agefield value
1869 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1870 my $conditions = $rule->{conditions};
1871 my $substitutions = $rule->{substitutions};
1872 foreach ( @$substitutions ) {
1873 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1880 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1883 for my $condition ( @$conditions ) {
1884 next unless $condition->{field};
1886 grep { $_ eq $condition->{field} } @item_columns
1887 or grep { $_ eq $condition->{field} } @biblioitem_columns
1889 if ( $condition->{value} =~ /\|/ ) {
1890 my @values = split /\|/, $condition->{value};
1891 $query .= qq| AND $condition->{field} IN (|
1892 . join( ',', ('?') x scalar @values )
1894 push @params, @values;
1896 $query .= qq| AND $condition->{field} = ?|;
1897 push @params, $condition->{value};
1901 if ( defined $age ) {
1902 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1905 my $sth = $dbh->prepare($query);
1906 $sth->execute( @params );
1907 while ( my $values = $sth->fetchrow_hashref ) {
1908 my $biblionumber = $values->{biblionumber};
1909 my $itemnumber = $values->{itemnumber};
1910 my $item = Koha::Items->find($itemnumber);
1911 for my $substitution ( @$substitutions ) {
1912 my $field = $substitution->{item_field};
1913 my $value = $substitution->{value};
1914 next unless $substitution->{field};
1915 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1916 $item->$field($value);
1917 push @{ $report->{$itemnumber} }, $substitution;
1919 $item->store unless $report_only;