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>, expected to 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.
151 skip_record_index => 1|0
155 sub AddItemFromMarc {
156 my $source_item_marc = shift;
157 my $biblionumber = shift;
158 my $params = @_ ? shift : {};
160 my $dbh = C4::Context->dbh;
162 # parse item hash from MARC
163 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
164 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
166 my $localitemmarc = MARC::Record->new;
167 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
169 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
170 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
171 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
172 $item_values->{biblionumber} = $biblionumber;
173 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
174 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
175 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
176 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
179 =head2 AddItemBatchFromMarc
181 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
182 $biblionumber, $biblioitemnumber, $frameworkcode);
184 Efficiently create item records from a MARC biblio record with
185 embedded item fields. This routine is suitable for batch jobs.
187 This API assumes that the bib record has already been
188 saved to the C<biblio> and C<biblioitems> tables. It does
189 not expect that C<biblio_metadata.metadata> is populated, but it
190 will do so via a call to ModBibiloMarc.
192 The goal of this API is to have a similar effect to using AddBiblio
193 and AddItems in succession, but without inefficient repeated
194 parsing of the MARC XML bib record.
196 This function returns an arrayref of new itemsnumbers and an arrayref of item
197 errors encountered during the processing. Each entry in the errors
198 list is a hashref containing the following keys:
204 Sequence number of original item tag in the MARC record.
208 Item barcode, provide to assist in the construction of
209 useful error messages.
213 Code representing the error condition. Can be 'duplicate_barcode',
214 'invalid_homebranch', or 'invalid_holdingbranch'.
216 =item error_information
218 Additional information appropriate to the error condition.
224 sub AddItemBatchFromMarc {
225 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
226 my @itemnumbers = ();
228 my $dbh = C4::Context->dbh;
230 # We modify the record, so lets work on a clone so we don't change the
232 $record = $record->clone();
233 # loop through the item tags and start creating items
234 my @bad_item_fields = ();
235 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
236 my $item_sequence_num = 0;
237 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
238 $item_sequence_num++;
239 # we take the item field and stick it into a new
240 # MARC record -- this is required so far because (FIXME)
241 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
242 # and there is no TransformMarcFieldToKoha
243 my $temp_item_marc = MARC::Record->new();
244 $temp_item_marc->append_fields($item_field);
246 # add biblionumber and biblioitemnumber
247 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
248 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
249 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
250 $item->{'biblionumber'} = $biblionumber;
251 $item->{'biblioitemnumber'} = $biblioitemnumber;
252 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
253 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
255 # check for duplicate barcode
256 my %item_errors = CheckItemPreSave($item);
258 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
259 push @bad_item_fields, $item_field;
263 my $item_object = Koha::Item->new($item)->store;
264 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
266 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
268 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
269 $item_field->replace_with($new_item_marc->field($itemtag));
272 # remove any MARC item fields for rejected items
273 foreach my $item_field (@bad_item_fields) {
274 $record->delete_field($item_field);
277 return (\@itemnumbers, \@errors);
280 =head2 ModItemFromMarc
282 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
284 The final optional parameter, C<$params>, expected to contain
285 'skip_record_index' key, which relayed down to Koha::Item/store,
286 there it prevents calling of index_records,
287 which takes most of the time in batch adds/deletes: index_records better
288 to be called later in C<additem.pl> after the whole loop.
291 skip_record_index => 1|0
295 sub ModItemFromMarc {
296 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
298 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
299 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
301 my $localitemmarc = MARC::Record->new;
302 $localitemmarc->append_fields( $item_marc->field($itemtag) );
303 my $item_object = Koha::Items->find($itemnumber);
304 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
306 # When importing items we blank this column, we need to set it to the existing value
307 # to prevent it being blanked by set_or_blank
308 $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
310 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
311 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
313 # Retrieving the values for the fields that are not linked
314 my @mapped_fields = Koha::MarcSubfieldStructures->search(
316 frameworkcode => $frameworkcode,
317 kohafield => { -like => "items.%" }
319 )->get_column('kohafield');
320 for my $c ( $item_object->_result->result_source->columns ) {
321 next if grep { "items.$c" eq $_ } @mapped_fields;
322 $item->{$c} = $item_object->$c;
325 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
326 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
327 $item->{itemnumber} = $itemnumber;
328 $item->{biblionumber} = $biblionumber;
330 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
331 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
332 $item_object = $item_object->set_or_blank($item);
333 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
335 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
337 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
338 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
339 $item_object->store({ skip_record_index => $params->{skip_record_index} });
341 return $item_object->unblessed;
344 =head2 ModItemTransfer
346 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
348 Marks an item as being transferred from one branch to another and records the trigger.
350 The last optional parameter allows for passing skip_record_index through to the items store call.
354 sub ModItemTransfer {
355 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
357 my $dbh = C4::Context->dbh;
358 my $item = Koha::Items->find( $itemnumber );
360 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
361 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
362 # will have been preceded by a check of branch transfer limits)
363 my $to_library = Koha::Libraries->find($tobranch);
364 my $transfer = $item->request_transfer(
373 # Immediately set the item to in transit if it is checked in
374 if ( !$item->checkout ) {
375 $item->holdingbranch($frombranch)->store(
378 skip_record_index => $params->{skip_record_index}
387 =head2 ModDateLastSeen
389 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
391 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
392 C<$itemnumber> is the item number
393 C<$leave_item_lost> determines if a lost item will be found or remain lost
395 The last optional parameter allows for passing skip_record_index through to the items store call.
399 sub ModDateLastSeen {
400 my ( $itemnumber, $leave_item_lost, $params ) = @_;
402 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
404 my $item = Koha::Items->find($itemnumber);
405 $item->datelastseen($today);
406 $item->itemlost(0) unless $leave_item_lost;
407 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
410 =head2 CheckItemPreSave
412 my $item_ref = TransformMarcToKoha($marc, 'items');
414 my %errors = CheckItemPreSave($item_ref);
415 if (exists $errors{'duplicate_barcode'}) {
416 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
417 } elsif (exists $errors{'invalid_homebranch'}) {
418 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
419 } elsif (exists $errors{'invalid_holdingbranch'}) {
420 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
425 Given a hashref containing item fields, determine if it can be
426 inserted or updated in the database. Specifically, checks for
427 database integrity issues, and returns a hash containing any
428 of the following keys, if applicable.
432 =item duplicate_barcode
434 Barcode, if it duplicates one already found in the database.
436 =item invalid_homebranch
438 Home branch, if not defined in branches table.
440 =item invalid_holdingbranch
442 Holding branch, if not defined in branches table.
446 This function does NOT implement any policy-related checks,
447 e.g., whether current operator is allowed to save an
448 item that has a given branch code.
452 sub CheckItemPreSave {
453 my $item_ref = shift;
457 # check for duplicate barcode
458 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
459 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
460 if ($existing_item) {
461 if (!exists $item_ref->{'itemnumber'} # new item
462 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
463 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
468 # check for valid home branch
469 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
470 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
471 unless (defined $home_library) {
472 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
476 # check for valid holding branch
477 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
478 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
479 unless (defined $holding_library) {
480 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
488 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
490 The following functions provide various ways of
491 getting an item record, a set of item records, or
492 lists of authorized values for certain item fields.
496 =head2 GetItemsForInventory
498 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
499 minlocation => $minlocation,
500 maxlocation => $maxlocation,
501 location => $location,
502 ignoreissued => $ignoreissued,
503 datelastseen => $datelastseen,
504 branchcode => $branchcode,
508 statushash => $statushash,
509 itemtypes => \@itemsarray,
512 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
514 The sub returns a reference to a list of hashes, each containing
515 itemnumber, author, title, barcode, item callnumber, and date last
516 seen. It is ordered by callnumber then title.
518 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
519 the datelastseen can be used to specify that you want to see items not seen since a past date only.
520 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
521 $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.
523 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
527 sub GetItemsForInventory {
528 my ( $parameters ) = @_;
529 my $minlocation = $parameters->{'minlocation'} // '';
530 my $maxlocation = $parameters->{'maxlocation'} // '';
531 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
532 my $location = $parameters->{'location'} // '';
533 my $itemtype = $parameters->{'itemtype'} // '';
534 my $ignoreissued = $parameters->{'ignoreissued'} // '';
535 my $datelastseen = $parameters->{'datelastseen'} // '';
536 my $branchcode = $parameters->{'branchcode'} // '';
537 my $branch = $parameters->{'branch'} // '';
538 my $offset = $parameters->{'offset'} // '';
539 my $size = $parameters->{'size'} // '';
540 my $statushash = $parameters->{'statushash'} // '';
541 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
542 my $itemtypes = $parameters->{'itemtypes'} || [];
543 my $ccode = $parameters->{'ccode'} // '';
545 my $dbh = C4::Context->dbh;
546 my ( @bind_params, @where_strings );
548 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
549 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
551 my $select_columns = q{
552 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
555 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
558 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
559 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
562 for my $authvfield (keys %$statushash){
563 if ( scalar @{$statushash->{$authvfield}} > 0 ){
564 my $joinedvals = join ',', @{$statushash->{$authvfield}};
565 push @where_strings, "$authvfield in (" . $joinedvals . ")";
571 push @where_strings, 'ccode = ?';
572 push @bind_params, $ccode;
576 push @where_strings, 'items.cn_sort >= ?';
577 push @bind_params, $min_cnsort;
581 push @where_strings, 'items.cn_sort <= ?';
582 push @bind_params, $max_cnsort;
586 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
587 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
588 push @bind_params, $datelastseen;
592 push @where_strings, 'items.location = ?';
593 push @bind_params, $location;
597 if($branch eq "homebranch"){
598 push @where_strings, 'items.homebranch = ?';
600 push @where_strings, 'items.holdingbranch = ?';
602 push @bind_params, $branchcode;
606 push @where_strings, 'biblioitems.itemtype = ?';
607 push @bind_params, $itemtype;
609 if ( $ignoreissued) {
610 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
611 push @where_strings, 'issues.date_due IS NULL';
614 if ( $ignore_waiting_holds ) {
615 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
616 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
620 my $itemtypes_str = join ', ', @$itemtypes;
621 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
624 if ( @where_strings ) {
626 $query .= join ' AND ', @where_strings;
628 my $count_query = $select_count . $query;
629 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
630 $query .= " LIMIT $offset, $size" if ($offset and $size);
631 $query = $select_columns . $query;
632 my $sth = $dbh->prepare($query);
633 $sth->execute( @bind_params );
636 my $tmpresults = $sth->fetchall_arrayref({});
637 $sth = $dbh->prepare( $count_query );
638 $sth->execute( @bind_params );
639 my ($iTotalRecords) = $sth->fetchrow_array();
641 my @avs = Koha::AuthorisedValues->search(
642 { 'marc_subfield_structures.kohafield' => { '>' => '' },
643 'me.authorised_value' => { '>' => '' },
645 { join => { category => 'marc_subfield_structures' },
646 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
647 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
648 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
652 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
654 foreach my $row (@$tmpresults) {
657 foreach (keys %$row) {
660 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
663 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
669 return (\@results, $iTotalRecords);
674 @results = GetItemsInfo($biblionumber);
676 Returns information about items with the given biblionumber.
678 C<GetItemsInfo> returns a list of references-to-hash. Each element
679 contains a number of keys. Most of them are attributes from the
680 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
681 Koha database. Other keys include:
685 =item C<$data-E<gt>{branchname}>
687 The name (not the code) of the branch to which the book belongs.
689 =item C<$data-E<gt>{datelastseen}>
691 This is simply C<items.datelastseen>, except that while the date is
692 stored in YYYY-MM-DD format in the database, here it is converted to
693 DD/MM/YYYY format. A NULL date is returned as C<//>.
695 =item C<$data-E<gt>{datedue}>
697 =item C<$data-E<gt>{class}>
699 This is the concatenation of C<biblioitems.classification>, the book's
700 Dewey code, and C<biblioitems.subclass>.
702 =item C<$data-E<gt>{ocount}>
704 I think this is the number of copies of the book available.
706 =item C<$data-E<gt>{order}>
708 If this is set, it is set to C<One Order>.
715 my ( $biblionumber ) = @_;
716 my $dbh = C4::Context->dbh;
717 require C4::Languages;
718 my $language = C4::Languages::getlanguage();
724 biblioitems.itemtype,
727 biblioitems.publicationyear,
728 biblioitems.publishercode,
729 biblioitems.volumedate,
730 biblioitems.volumedesc,
733 items.notforloan as itemnotforloan,
734 issues.borrowernumber,
735 issues.date_due as datedue,
736 issues.onsite_checkout,
737 borrowers.cardnumber,
740 borrowers.branchcode as bcode,
742 serial.publisheddate,
743 itemtypes.description,
744 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
745 itemtypes.notforloan as notforloan_per_itemtype,
749 holding.opac_info as holding_branch_opac_info,
750 home.opac_info as home_branch_opac_info,
751 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
753 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
754 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
755 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
756 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
757 LEFT JOIN issues USING (itemnumber)
758 LEFT JOIN borrowers USING (borrowernumber)
759 LEFT JOIN serialitems USING (itemnumber)
760 LEFT JOIN serial USING (serialid)
761 LEFT JOIN itemtypes ON itemtypes.itemtype = "
762 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
764 LEFT JOIN tmp_holdsqueue USING (itemnumber)
765 LEFT JOIN localization ON itemtypes.itemtype = localization.code
766 AND localization.entity = 'itemtypes'
767 AND localization.lang = ?
770 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
771 my $sth = $dbh->prepare($query);
772 $sth->execute($language, $biblionumber);
777 my $userenv = C4::Context->userenv;
778 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
779 while ( my $data = $sth->fetchrow_hashref ) {
780 if ( $data->{borrowernumber} && $want_not_same_branch) {
781 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
784 $serial ||= $data->{'serial'};
787 # get notforloan complete status if applicable
788 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
789 $data->{notforloanvalue} = $descriptions->{lib} // '';
790 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
792 # get restricted status and description if applicable
793 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
794 $data->{restrictedvalue} = $descriptions->{lib} // '';
795 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
797 # my stack procedures
798 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
799 $data->{stack} = $descriptions->{lib} // '';
801 # Find the last 3 people who borrowed this item.
802 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
804 AND old_issues.borrowernumber = borrowers.borrowernumber
805 ORDER BY returndate DESC
807 $sth2->execute($data->{'itemnumber'});
809 while (my $data2 = $sth2->fetchrow_hashref()) {
810 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
811 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
812 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
816 $results[$i] = $data;
821 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
825 =head2 GetItemsLocationInfo
827 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
829 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
831 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
835 =item C<$data-E<gt>{homebranch}>
837 Branch Name of the item's homebranch
839 =item C<$data-E<gt>{holdingbranch}>
841 Branch Name of the item's holdingbranch
843 =item C<$data-E<gt>{location}>
845 Item's shelving location code
847 =item C<$data-E<gt>{location_intranet}>
849 The intranet description for the Shelving Location as set in authorised_values 'LOC'
851 =item C<$data-E<gt>{location_opac}>
853 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
856 =item C<$data-E<gt>{itemcallnumber}>
858 Item's itemcallnumber
860 =item C<$data-E<gt>{cn_sort}>
862 Item's call number normalized for sorting
868 sub GetItemsLocationInfo {
869 my $biblionumber = shift;
872 my $dbh = C4::Context->dbh;
873 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
874 location, itemcallnumber, cn_sort
875 FROM items, branches as a, branches as b
876 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
878 ORDER BY cn_sort ASC";
879 my $sth = $dbh->prepare($query);
880 $sth->execute($biblionumber);
882 while ( my $data = $sth->fetchrow_hashref ) {
883 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
884 $av = $av->count ? $av->next : undef;
885 $data->{location_intranet} = $av ? $av->lib : '';
886 $data->{location_opac} = $av ? $av->opac_description : '';
887 push @results, $data;
892 =head2 GetHostItemsInfo
894 $hostiteminfo = GetHostItemsInfo($hostfield);
895 Returns the iteminfo for items linked to records via a host field
899 sub GetHostItemsInfo {
903 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
904 return @returnitemsInfo;
908 if( C4::Context->preference('marcflavour') eq 'MARC21' ) {
909 @fields = $record->field('773');
910 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
911 @fields = $record->field('461');
914 foreach my $hostfield ( @fields ) {
915 my $hostbiblionumber = $hostfield->subfield("0");
916 my $linkeditemnumber = $hostfield->subfield("9");
917 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
918 foreach my $hostitemInfo (@hostitemInfos) {
919 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
920 push @returnitemsInfo, $hostitemInfo;
925 return @returnitemsInfo;
928 =head2 get_hostitemnumbers_of
930 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
932 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
934 Return a reference on a hash where key is a biblionumber and values are
935 references on array of itemnumbers.
940 sub get_hostitemnumbers_of {
941 my ($biblionumber) = @_;
943 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
947 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
948 return unless $marcrecord;
950 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
952 my $marcflavor = C4::Context->preference('marcflavour');
953 if ( $marcflavor eq 'MARC21' ) {
958 elsif ( $marcflavor eq 'UNIMARC' ) {
964 foreach my $hostfield ( $marcrecord->field($tag) ) {
965 my $hostbiblionumber = $hostfield->subfield($biblio_s);
966 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
967 my $linkeditemnumber = $hostfield->subfield($item_s);
968 if ( ! $linkeditemnumber ) {
969 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
972 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
973 push @returnhostitemnumbers, $linkeditemnumber
977 return @returnhostitemnumbers;
980 =head2 GetHiddenItemnumbers
982 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
984 Given a list of items it checks which should be hidden from the OPAC given
985 the current configuration. Returns a list of itemnumbers corresponding to
986 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
991 sub GetHiddenItemnumbers {
993 my $items = $params->{items};
994 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
995 foreach my $except (split(/\|/, $exceptions)){
996 if ($params->{'borcat'} eq $except){
997 return; # we don't hide anything for this borrower category
1003 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
1006 unless $hidingrules;
1008 my $dbh = C4::Context->dbh;
1011 foreach my $item (@$items) {
1013 # We check each rule
1014 foreach my $field (keys %$hidingrules) {
1016 if (exists $item->{$field}) {
1017 $val = $item->{$field};
1020 my $query = "SELECT $field from items where itemnumber = ?";
1021 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1023 $val = '' unless defined $val;
1025 # If the results matches the values in the yaml file
1026 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1028 # We add the itemnumber to the list
1029 push @resultitems, $item->{'itemnumber'};
1031 # If at least one rule matched for an item, no need to test the others
1036 return @resultitems;
1039 =head1 LIMITED USE FUNCTIONS
1041 The following functions, while part of the public API,
1042 are not exported. This is generally because they are
1043 meant to be used by only one script for a specific
1044 purpose, and should not be used in any other context
1045 without careful thought.
1051 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1053 Returns MARC::Record of the item passed in parameter.
1054 This function is meant for use only in C<cataloguing/additem.pl>,
1055 where it is needed to support that script's MARC-like
1061 my ( $biblionumber, $itemnumber ) = @_;
1063 # GetMarcItem has been revised so that it does the following:
1064 # 1. Gets the item information from the items table.
1065 # 2. Converts it to a MARC field for storage in the bib record.
1067 # The previous behavior was:
1068 # 1. Get the bib record.
1069 # 2. Return the MARC tag corresponding to the item record.
1071 # The difference is that one treats the items row as authoritative,
1072 # while the other treats the MARC representation as authoritative
1073 # under certain circumstances.
1075 my $item = Koha::Items->find($itemnumber) or return;
1077 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1078 # Also, don't emit a subfield if the underlying field is blank.
1080 return Item2Marc($item->unblessed, $biblionumber);
1084 my ($itemrecord,$biblionumber)=@_;
1087 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1088 } keys %{ $itemrecord }
1090 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1091 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1092 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1093 "items.itemnumber", $framework,
1096 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1097 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1098 foreach my $field ($itemmarc->field($itemtag)){
1099 $field->add_subfields(@$unlinked_item_subfields);
1105 =head1 PRIVATE FUNCTIONS AND VARIABLES
1107 The following functions are not meant to be called
1108 directly, but are documented in order to explain
1109 the inner workings of C<C4::Items>.
1113 =head2 _marc_from_item_hash
1115 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1117 Given an item hash representing a complete item record,
1118 create a C<MARC::Record> object containing an embedded
1119 tag representing that item.
1121 The third, optional parameter C<$unlinked_item_subfields> is
1122 an arrayref of subfields (not mapped to C<items> fields per the
1123 framework) to be added to the MARC representation
1128 sub _marc_from_item_hash {
1130 my $frameworkcode = shift;
1131 my $unlinked_item_subfields;
1133 $unlinked_item_subfields = shift;
1136 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1137 # Also, don't emit a subfield if the underlying field is blank.
1138 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1139 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1140 : () } keys %{ $item } };
1142 my $item_marc = MARC::Record->new();
1143 foreach my $item_field ( keys %{$mungeditem} ) {
1144 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1145 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1146 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1147 foreach my $value (@values){
1148 if ( my $field = $item_marc->field($tag) ) {
1149 $field->add_subfields( $subfield => $value );
1151 my $add_subfields = [];
1152 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1153 $add_subfields = $unlinked_item_subfields;
1155 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1163 =head2 _repack_item_errors
1165 Add an error message hash generated by C<CheckItemPreSave>
1166 to a list of errors.
1170 sub _repack_item_errors {
1171 my $item_sequence_num = shift;
1172 my $item_ref = shift;
1173 my $error_ref = shift;
1175 my @repacked_errors = ();
1177 foreach my $error_code (sort keys %{ $error_ref }) {
1178 my $repacked_error = {};
1179 $repacked_error->{'item_sequence'} = $item_sequence_num;
1180 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1181 $repacked_error->{'error_code'} = $error_code;
1182 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1183 push @repacked_errors, $repacked_error;
1186 return @repacked_errors;
1189 =head2 _get_unlinked_item_subfields
1191 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1195 sub _get_unlinked_item_subfields {
1196 my $original_item_marc = shift;
1197 my $frameworkcode = shift;
1199 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1201 # assume that this record has only one field, and that that
1202 # field contains only the item information
1204 my @fields = $original_item_marc->fields();
1205 if ($#fields > -1) {
1206 my $field = $fields[0];
1207 my $tag = $field->tag();
1208 foreach my $subfield ($field->subfields()) {
1209 if (defined $subfield->[1] and
1210 $subfield->[1] ne '' and
1211 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1212 push @$subfields, $subfield->[0] => $subfield->[1];
1219 =head2 _get_unlinked_subfields_xml
1221 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1225 sub _get_unlinked_subfields_xml {
1226 my $unlinked_item_subfields = shift;
1229 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1230 my $marc = MARC::Record->new();
1231 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1232 # used in the framework
1233 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1234 $marc->encoding("UTF-8");
1235 $xml = $marc->as_xml("USMARC");
1241 =head2 _parse_unlinked_item_subfields_from_xml
1243 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1247 sub _parse_unlinked_item_subfields_from_xml {
1249 require C4::Charset;
1250 return unless defined $xml and $xml ne "";
1251 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1252 my $unlinked_subfields = [];
1253 my @fields = $marc->fields();
1254 if ($#fields > -1) {
1255 foreach my $subfield ($fields[0]->subfields()) {
1256 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1259 return $unlinked_subfields;
1262 =head2 GetAnalyticsCount
1264 $count= &GetAnalyticsCount($itemnumber)
1266 counts Usage of itemnumber in Analytical bibliorecords.
1270 sub GetAnalyticsCount {
1271 my ($itemnumber) = @_;
1273 ### ZOOM search here
1275 $query= "hi=".$itemnumber;
1276 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1277 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1281 sub _SearchItems_build_where_fragment {
1284 my $dbh = C4::Context->dbh;
1287 if (exists($filter->{conjunction})) {
1288 my (@where_strs, @where_args);
1289 foreach my $f (@{ $filter->{filters} }) {
1290 my $fragment = _SearchItems_build_where_fragment($f);
1292 push @where_strs, $fragment->{str};
1293 push @where_args, @{ $fragment->{args} };
1298 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1301 args => \@where_args,
1305 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1306 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1307 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1308 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1309 my @operators = qw(= != > < >= <= is like);
1310 push @operators, 'not like';
1311 my $field = $filter->{field} // q{};
1312 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1313 my $op = $filter->{operator};
1314 my $query = $filter->{query};
1315 my $ifnull = $filter->{ifnull};
1317 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1318 $op = '='; # default operator
1322 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1324 my $marcsubfield = $2;
1325 my ($kohafield) = $dbh->selectrow_array(q|
1326 SELECT kohafield FROM marc_subfield_structure
1327 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1328 |, undef, $marcfield, $marcsubfield);
1331 $column = $kohafield;
1333 # MARC field is not linked to a DB field so we need to use
1334 # ExtractValue on marcxml from biblio_metadata or
1335 # items.more_subfields_xml, depending on the MARC field.
1338 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1339 if ($marcfield eq $itemfield) {
1340 $sqlfield = 'more_subfields_xml';
1341 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1343 $sqlfield = 'metadata'; # From biblio_metadata
1344 if ($marcfield < 10) {
1345 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1347 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1350 $column = "ExtractValue($sqlfield, '$xpath')";
1356 if ( defined $ifnull ) {
1357 $column = "COALESCE($column, ?)";
1360 if (ref $query eq 'ARRAY') {
1363 } elsif ($op eq '!=') {
1367 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1370 } elsif ( $op eq 'is' ) {
1372 str => "$column $op $query",
1377 str => "$column $op ?",
1382 if ( defined $ifnull ) {
1383 unshift @{ $where_fragment->{args} }, $ifnull;
1388 return $where_fragment;
1393 my ($items, $total) = SearchItems($filter, $params);
1395 Perform a search among items
1397 $filter is a reference to a hash which can be a filter, or a combination of filters.
1399 A filter has the following keys:
1403 =item * field: the name of a SQL column in table items
1405 =item * query: the value to search in this column
1407 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1411 A combination of filters hash the following keys:
1415 =item * conjunction: 'AND' or 'OR'
1417 =item * filters: array ref of filters
1421 $params is a reference to a hash that can contain the following parameters:
1425 =item * rows: Number of items to return. 0 returns everything (default: 0)
1427 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1430 =item * sortby: A SQL column name in items table to sort on
1432 =item * sortorder: 'ASC' or 'DESC'
1439 my ($filter, $params) = @_;
1443 return unless ref $filter eq 'HASH';
1444 return unless ref $params eq 'HASH';
1446 # Default parameters
1447 $params->{rows} ||= 0;
1448 $params->{page} ||= 1;
1449 $params->{sortby} ||= 'itemnumber';
1450 $params->{sortorder} ||= 'ASC';
1452 my ($where_str, @where_args);
1453 my $where_fragment = _SearchItems_build_where_fragment($filter);
1454 if ($where_fragment) {
1455 $where_str = $where_fragment->{str};
1456 @where_args = @{ $where_fragment->{args} };
1459 my $dbh = C4::Context->dbh;
1461 SELECT SQL_CALC_FOUND_ROWS items.*
1463 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1464 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1465 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1466 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1469 if (defined $where_str and $where_str ne '') {
1470 $query .= qq{ AND $where_str };
1473 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1474 push @where_args, C4::Context->preference('marcflavour');
1476 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1477 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1478 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1479 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1481 if ( $params->{sortby} eq 'availability' ) {
1482 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1483 $query .= qq{ ORDER BY onloan $sortorder };
1485 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1486 ? $params->{sortby} : 'itemnumber';
1487 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1488 $query .= qq{ ORDER BY $sortby $sortorder };
1491 my $rows = $params->{rows};
1494 my $offset = $rows * ($params->{page}-1);
1495 $query .= qq { LIMIT ?, ? };
1496 push @limit_args, $offset, $rows;
1499 my $sth = $dbh->prepare($query);
1500 my $rv = $sth->execute(@where_args, @limit_args);
1502 return unless ($rv);
1503 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1505 return ($sth->fetchall_arrayref({}), $total_rows);
1509 =head1 OTHER FUNCTIONS
1513 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1515 Find the given $subfield in the given $tag in the given
1516 MARC::Record $record. If the subfield is found, returns
1517 the (indicators, value) pair; otherwise, (undef, undef) is
1521 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1522 I suggest we export it from this module.
1527 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1530 if ( $tagfield < 10 ) {
1531 if ( $record->field($tagfield) ) {
1532 push @result, $record->field($tagfield)->data();
1537 foreach my $field ( $record->field($tagfield) ) {
1538 my @subfields = $field->subfields();
1539 foreach my $subfield (@subfields) {
1540 if ( @$subfield[0] eq $insubfield ) {
1541 push @result, @$subfield[1];
1542 $indicator = $field->indicator(1) . $field->indicator(2);
1547 return ( $indicator, @result );
1551 =head2 PrepareItemrecordDisplay
1553 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1555 Returns a hash with all the fields for Display a given item data in a template
1557 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1559 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1563 sub PrepareItemrecordDisplay {
1565 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1567 my $dbh = C4::Context->dbh;
1568 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1569 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1571 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1572 # a shared data structure. No plugin (including custom ones) should change
1573 # its contents. See also GetMarcStructure.
1574 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1576 # Pick the default location from NewItemsDefaultLocation
1577 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1578 $defaultvalues //= {};
1579 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1582 # return nothing if we don't have found an existing framework.
1583 return q{} unless $tagslib;
1586 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1590 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1592 SELECT authorised_value,lib FROM authorised_values
1595 LEFT JOIN authorised_values_branches ON ( id = av_id )
1600 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1601 $query .= qq{ ORDER BY lib};
1602 my $authorised_values_sth = $dbh->prepare( $query );
1603 foreach my $tag ( sort keys %{$tagslib} ) {
1606 # loop through each subfield
1608 foreach my $subfield (
1609 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1610 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1611 values %{ $tagslib->{$tag} } )
1613 next unless ( $subfield->{'tab'} );
1614 next if ( $subfield->{'tab'} ne "10" );
1616 $subfield_data{tag} = $tag;
1617 $subfield_data{subfield} = $subfield->{subfield};
1618 $subfield_data{countsubfield} = $cntsubf++;
1619 $subfield_data{kohafield} = $subfield->{kohafield};
1620 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1622 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1623 $subfield_data{marc_lib} = $subfield->{lib};
1624 $subfield_data{mandatory} = $subfield->{mandatory};
1625 $subfield_data{repeatable} = $subfield->{repeatable};
1626 $subfield_data{hidden} = "display:none"
1627 if ( ( $subfield->{hidden} > 4 )
1628 || ( $subfield->{hidden} < -4 ) );
1629 my ( $x, $defaultvalue );
1631 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1633 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1634 if ( !defined $defaultvalue ) {
1635 $defaultvalue = q||;
1637 $defaultvalue =~ s/"/"/g;
1638 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1639 my $today_dt = dt_from_string;
1640 my $year = $today_dt->strftime('%Y');
1641 my $shortyear = $today_dt->strftime('%y');
1642 my $month = $today_dt->strftime('%m');
1643 my $day = $today_dt->strftime('%d');
1644 $defaultvalue =~ s/<<YYYY>>/$year/g;
1645 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1646 $defaultvalue =~ s/<<MM>>/$month/g;
1647 $defaultvalue =~ s/<<DD>>/$day/g;
1649 # And <<USER>> with surname (?)
1651 ( C4::Context->userenv
1652 ? C4::Context->userenv->{'surname'}
1653 : "superlibrarian" );
1654 $defaultvalue =~ s/<<USER>>/$username/g;
1657 my $maxlength = $subfield->{maxlength};
1659 # search for itemcallnumber if applicable
1660 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1661 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1662 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1663 my $CNtag = substr( $itemcn_pref, 0, 3 );
1664 next unless my $field = $itemrecord->field($CNtag);
1665 my $CNsubfields = substr( $itemcn_pref, 3 );
1666 $CNsubfields = undef if $CNsubfields eq '';
1667 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1668 last if $defaultvalue;
1671 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1673 && $defaultvalues->{'callnumber'} ) {
1674 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1675 # if the item record exists, only use default value if the item has no callnumber
1676 $defaultvalue = $defaultvalues->{callnumber};
1677 } elsif ( !$itemrecord and $defaultvalues ) {
1678 # if the item record *doesn't* exists, always use the default value
1679 $defaultvalue = $defaultvalues->{callnumber};
1682 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1684 && $defaultvalues->{'branchcode'} ) {
1685 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1686 $defaultvalue = $defaultvalues->{branchcode};
1689 if ( ( $subfield->{kohafield} eq 'items.location' )
1691 && $defaultvalues->{'location'} ) {
1693 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1694 # if the item record exists, only use default value if the item has no locationr
1695 $defaultvalue = $defaultvalues->{location};
1696 } elsif ( !$itemrecord and $defaultvalues ) {
1697 # if the item record *doesn't* exists, always use the default value
1698 $defaultvalue = $defaultvalues->{location};
1701 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1703 && $defaultvalues->{'ccode'} ) {
1705 if ( !$itemrecord and $defaultvalues ) {
1706 # if the item record *doesn't* exists, always use the default value
1707 $defaultvalue = $defaultvalues->{ccode};
1710 if ( $subfield->{authorised_value} ) {
1711 my @authorised_values;
1714 # builds list, depending on authorised value...
1716 if ( $subfield->{'authorised_value'} eq "branches" ) {
1717 if ( ( C4::Context->preference("IndependentBranches") )
1718 && !C4::Context->IsSuperLibrarian() ) {
1719 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1720 $sth->execute( C4::Context->userenv->{branch} );
1721 push @authorised_values, ""
1722 unless ( $subfield->{mandatory} );
1723 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1724 push @authorised_values, $branchcode;
1725 $authorised_lib{$branchcode} = $branchname;
1728 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1730 push @authorised_values, ""
1731 unless ( $subfield->{mandatory} );
1732 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1733 push @authorised_values, $branchcode;
1734 $authorised_lib{$branchcode} = $branchname;
1738 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1739 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1740 $defaultvalue = $defaultvalues->{branchcode};
1744 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1745 my $itemtypes = Koha::ItemTypes->search_with_localization;
1746 push @authorised_values, "";
1747 while ( my $itemtype = $itemtypes->next ) {
1748 push @authorised_values, $itemtype->itemtype;
1749 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1751 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1752 $defaultvalue = $defaultvalues->{'itemtype'};
1756 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1757 push @authorised_values, "";
1759 my $class_sources = GetClassSources();
1760 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1762 foreach my $class_source (sort keys %$class_sources) {
1763 next unless $class_sources->{$class_source}->{'used'} or
1764 ($class_source eq $default_source);
1765 push @authorised_values, $class_source;
1766 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1769 $defaultvalue = $default_source;
1771 #---- "true" authorised value
1773 $authorised_values_sth->execute(
1774 $subfield->{authorised_value},
1775 $branch_limit ? $branch_limit : ()
1777 push @authorised_values, "";
1778 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1779 push @authorised_values, $value;
1780 $authorised_lib{$value} = $lib;
1783 $subfield_data{marc_value} = {
1785 values => \@authorised_values,
1786 default => $defaultvalue // q{},
1787 labels => \%authorised_lib,
1789 } elsif ( $subfield->{value_builder} ) {
1791 require Koha::FrameworkPlugin;
1792 my $plugin = Koha::FrameworkPlugin->new({
1793 name => $subfield->{value_builder},
1796 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1797 $plugin->build( $pars );
1798 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1799 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1801 if( !$plugin->errstr ) {
1802 #TODO Move html to template; see report 12176/13397
1803 my $tab= $plugin->noclick? '-1': '';
1804 my $class= $plugin->noclick? ' disabled': '';
1805 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1806 $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;
1808 warn $plugin->errstr;
1809 $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
1812 elsif ( $tag eq '' ) { # it's an hidden field
1813 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1815 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
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" />);
1818 elsif ( length($defaultvalue) > 100
1819 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1820 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1821 or (C4::Context->preference("marcflavour") eq "MARC21" and
1822 500 <= $tag && $tag < 600 )
1824 # oversize field (textarea)
1825 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1827 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1829 push( @loop_data, \%subfield_data );
1834 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1835 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1838 'itemtagfield' => $itemtagfield,
1839 'itemtagsubfield' => $itemtagsubfield,
1840 'itemnumber' => $itemnumber,
1841 'iteminformation' => \@loop_data
1845 sub ToggleNewStatus {
1846 my ( $params ) = @_;
1847 my @rules = @{ $params->{rules} };
1848 my $report_only = $params->{report_only};
1850 my $dbh = C4::Context->dbh;
1852 my @item_columns = map { "items.$_" } Koha::Items->columns;
1853 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1855 for my $rule ( @rules ) {
1856 my $age = $rule->{age};
1857 # Default to using items.dateaccessioned if there's an old item modification rule
1858 # missing an agefield value
1859 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1860 my $conditions = $rule->{conditions};
1861 my $substitutions = $rule->{substitutions};
1862 foreach ( @$substitutions ) {
1863 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1870 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1873 for my $condition ( @$conditions ) {
1874 next unless $condition->{field};
1876 grep { $_ eq $condition->{field} } @item_columns
1877 or grep { $_ eq $condition->{field} } @biblioitem_columns
1879 if ( $condition->{value} =~ /\|/ ) {
1880 my @values = split /\|/, $condition->{value};
1881 $query .= qq| AND $condition->{field} IN (|
1882 . join( ',', ('?') x scalar @values )
1884 push @params, @values;
1886 $query .= qq| AND $condition->{field} = ?|;
1887 push @params, $condition->{value};
1891 if ( defined $age ) {
1892 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1895 my $sth = $dbh->prepare($query);
1896 $sth->execute( @params );
1897 while ( my $values = $sth->fetchrow_hashref ) {
1898 my $biblionumber = $values->{biblionumber};
1899 my $itemnumber = $values->{itemnumber};
1900 my $item = Koha::Items->find($itemnumber);
1901 for my $substitution ( @$substitutions ) {
1902 my $field = $substitution->{item_field};
1903 my $value = $substitution->{value};
1904 next unless $substitution->{field};
1905 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1906 $item->$field($value);
1907 push @{ $report->{$itemnumber} }, $substitution;
1909 $item->store unless $report_only;