3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use vars qw(@ISA @EXPORT);
40 get_hostitemnumbers_of
46 PrepareItemrecordDisplay
59 use List::MoreUtils qw(any);
60 use DateTime::Format::MySQL;
61 use Data::Dumper; # used as part of logging item record changes, not just for
62 # debugging; so please don't remove this
64 use Koha::AuthorisedValues;
65 use Koha::DateUtils qw(dt_from_string);
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>, expected to 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.
152 skip_record_index => 1|0
156 sub AddItemFromMarc {
157 my $source_item_marc = shift;
158 my $biblionumber = shift;
159 my $params = @_ ? shift : {};
161 my $dbh = C4::Context->dbh;
163 # parse item hash from MARC
164 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
165 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
167 my $localitemmarc = MARC::Record->new;
168 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
170 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
171 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
172 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
173 $item_values->{biblionumber} = $biblionumber;
174 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
175 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
176 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
177 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
180 =head2 AddItemBatchFromMarc
182 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
183 $biblionumber, $biblioitemnumber, $frameworkcode);
185 Efficiently create item records from a MARC biblio record with
186 embedded item fields. This routine is suitable for batch jobs.
188 This API assumes that the bib record has already been
189 saved to the C<biblio> and C<biblioitems> tables. It does
190 not expect that C<biblio_metadata.metadata> is populated, but it
191 will do so via a call to ModBibiloMarc.
193 The goal of this API is to have a similar effect to using AddBiblio
194 and AddItems in succession, but without inefficient repeated
195 parsing of the MARC XML bib record.
197 This function returns an arrayref of new itemsnumbers and an arrayref of item
198 errors encountered during the processing. Each entry in the errors
199 list is a hashref containing the following keys:
205 Sequence number of original item tag in the MARC record.
209 Item barcode, provide to assist in the construction of
210 useful error messages.
214 Code representing the error condition. Can be 'duplicate_barcode',
215 'invalid_homebranch', or 'invalid_holdingbranch'.
217 =item error_information
219 Additional information appropriate to the error condition.
225 sub AddItemBatchFromMarc {
226 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
227 my @itemnumbers = ();
229 my $dbh = C4::Context->dbh;
231 # We modify the record, so lets work on a clone so we don't change the
233 $record = $record->clone();
234 # loop through the item tags and start creating items
235 my @bad_item_fields = ();
236 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
237 my $item_sequence_num = 0;
238 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
239 $item_sequence_num++;
240 # we take the item field and stick it into a new
241 # MARC record -- this is required so far because (FIXME)
242 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
243 # and there is no TransformMarcFieldToKoha
244 my $temp_item_marc = MARC::Record->new();
245 $temp_item_marc->append_fields($item_field);
247 # add biblionumber and biblioitemnumber
248 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
249 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
250 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
251 $item->{'biblionumber'} = $biblionumber;
252 $item->{'biblioitemnumber'} = $biblioitemnumber;
253 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
254 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
256 # check for duplicate barcode
257 my %item_errors = CheckItemPreSave($item);
259 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
260 push @bad_item_fields, $item_field;
264 my $item_object = Koha::Item->new($item)->store;
265 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
267 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
269 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
270 $item_field->replace_with($new_item_marc->field($itemtag));
273 # remove any MARC item fields for rejected items
274 foreach my $item_field (@bad_item_fields) {
275 $record->delete_field($item_field);
278 return (\@itemnumbers, \@errors);
281 =head2 ModItemFromMarc
283 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
285 The final optional parameter, C<$params>, expected to contain
286 'skip_record_index' key, which relayed down to Koha::Item/store,
287 there it prevents calling of index_records,
288 which takes most of the time in batch adds/deletes: index_records better
289 to be called later in C<additem.pl> after the whole loop.
292 skip_record_index => 1|0
296 sub ModItemFromMarc {
297 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
299 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
300 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
302 my $localitemmarc = MARC::Record->new;
303 $localitemmarc->append_fields( $item_marc->field($itemtag) );
304 my $item_object = Koha::Items->find($itemnumber);
305 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
307 # Retrieving the values for the fields that are not linked
308 my @mapped_fields = Koha::MarcSubfieldStructures->search(
310 frameworkcode => $frameworkcode,
311 kohafield => { -like => "items.%" }
313 )->get_column('kohafield');
314 for my $c ( $item_object->_result->result_source->columns ) {
315 next if grep { "items.$c" eq $_ } @mapped_fields;
316 $item->{$c} = $item_object->$c;
319 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
320 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
321 $item->{itemnumber} = $itemnumber;
322 $item->{biblionumber} = $biblionumber;
324 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
325 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
326 $item_object = $item_object->set_or_blank($item);
327 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
329 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
330 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
331 $item_object->store({ skip_record_index => $params->{skip_record_index} });
333 return $item_object->unblessed;
336 =head2 ModItemTransfer
338 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
340 Marks an item as being transferred from one branch to another and records the trigger.
342 The last optional parameter allows for passing skip_record_index through to the items store call.
346 sub ModItemTransfer {
347 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
349 my $dbh = C4::Context->dbh;
350 my $item = Koha::Items->find( $itemnumber );
352 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
353 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
354 # will have been preceded by a check of branch transfer limits)
355 my $to_library = Koha::Libraries->find($tobranch);
356 my $transfer = $item->request_transfer(
365 # Immediately set the item to in transit if it is checked in
366 if ( !$item->checkout ) {
367 $item->holdingbranch($frombranch)->store(
370 skip_record_index => $params->{skip_record_index}
379 =head2 ModDateLastSeen
381 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
383 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
384 C<$itemnumber> is the item number
385 C<$leave_item_lost> determines if a lost item will be found or remain lost
387 The last optional parameter allows for passing skip_record_index through to the items store call.
391 sub ModDateLastSeen {
392 my ( $itemnumber, $leave_item_lost, $params ) = @_;
394 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
396 my $item = Koha::Items->find($itemnumber);
397 $item->datelastseen($today);
398 $item->itemlost(0) unless $leave_item_lost;
399 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
402 =head2 CheckItemPreSave
404 my $item_ref = TransformMarcToKoha($marc, 'items');
406 my %errors = CheckItemPreSave($item_ref);
407 if (exists $errors{'duplicate_barcode'}) {
408 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
409 } elsif (exists $errors{'invalid_homebranch'}) {
410 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
411 } elsif (exists $errors{'invalid_holdingbranch'}) {
412 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
417 Given a hashref containing item fields, determine if it can be
418 inserted or updated in the database. Specifically, checks for
419 database integrity issues, and returns a hash containing any
420 of the following keys, if applicable.
424 =item duplicate_barcode
426 Barcode, if it duplicates one already found in the database.
428 =item invalid_homebranch
430 Home branch, if not defined in branches table.
432 =item invalid_holdingbranch
434 Holding branch, if not defined in branches table.
438 This function does NOT implement any policy-related checks,
439 e.g., whether current operator is allowed to save an
440 item that has a given branch code.
444 sub CheckItemPreSave {
445 my $item_ref = shift;
449 # check for duplicate barcode
450 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
451 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
452 if ($existing_item) {
453 if (!exists $item_ref->{'itemnumber'} # new item
454 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
455 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
460 # check for valid home branch
461 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
462 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
463 unless (defined $home_library) {
464 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
468 # check for valid holding branch
469 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
470 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
471 unless (defined $holding_library) {
472 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
480 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
482 The following functions provide various ways of
483 getting an item record, a set of item records, or
484 lists of authorized values for certain item fields.
488 =head2 GetItemsForInventory
490 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
491 minlocation => $minlocation,
492 maxlocation => $maxlocation,
493 location => $location,
494 ignoreissued => $ignoreissued,
495 datelastseen => $datelastseen,
496 branchcode => $branchcode,
500 statushash => $statushash,
501 itemtypes => \@itemsarray,
504 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
506 The sub returns a reference to a list of hashes, each containing
507 itemnumber, author, title, barcode, item callnumber, and date last
508 seen. It is ordered by callnumber then title.
510 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
511 the datelastseen can be used to specify that you want to see items not seen since a past date only.
512 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
513 $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.
515 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
519 sub GetItemsForInventory {
520 my ( $parameters ) = @_;
521 my $minlocation = $parameters->{'minlocation'} // '';
522 my $maxlocation = $parameters->{'maxlocation'} // '';
523 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
524 my $location = $parameters->{'location'} // '';
525 my $itemtype = $parameters->{'itemtype'} // '';
526 my $ignoreissued = $parameters->{'ignoreissued'} // '';
527 my $datelastseen = $parameters->{'datelastseen'} // '';
528 my $branchcode = $parameters->{'branchcode'} // '';
529 my $branch = $parameters->{'branch'} // '';
530 my $offset = $parameters->{'offset'} // '';
531 my $size = $parameters->{'size'} // '';
532 my $statushash = $parameters->{'statushash'} // '';
533 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
534 my $itemtypes = $parameters->{'itemtypes'} || [];
536 my $dbh = C4::Context->dbh;
537 my ( @bind_params, @where_strings );
539 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
540 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
542 my $select_columns = q{
543 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
545 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
548 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
549 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
552 for my $authvfield (keys %$statushash){
553 if ( scalar @{$statushash->{$authvfield}} > 0 ){
554 my $joinedvals = join ',', @{$statushash->{$authvfield}};
555 push @where_strings, "$authvfield in (" . $joinedvals . ")";
561 push @where_strings, 'items.cn_sort >= ?';
562 push @bind_params, $min_cnsort;
566 push @where_strings, 'items.cn_sort <= ?';
567 push @bind_params, $max_cnsort;
571 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
572 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
573 push @bind_params, $datelastseen;
577 push @where_strings, 'items.location = ?';
578 push @bind_params, $location;
582 if($branch eq "homebranch"){
583 push @where_strings, 'items.homebranch = ?';
585 push @where_strings, 'items.holdingbranch = ?';
587 push @bind_params, $branchcode;
591 push @where_strings, 'biblioitems.itemtype = ?';
592 push @bind_params, $itemtype;
594 if ( $ignoreissued) {
595 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
596 push @where_strings, 'issues.date_due IS NULL';
599 if ( $ignore_waiting_holds ) {
600 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
601 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
605 my $itemtypes_str = join ', ', @$itemtypes;
606 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
609 if ( @where_strings ) {
611 $query .= join ' AND ', @where_strings;
613 my $count_query = $select_count . $query;
614 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
615 $query .= " LIMIT $offset, $size" if ($offset and $size);
616 $query = $select_columns . $query;
617 my $sth = $dbh->prepare($query);
618 $sth->execute( @bind_params );
621 my $tmpresults = $sth->fetchall_arrayref({});
622 $sth = $dbh->prepare( $count_query );
623 $sth->execute( @bind_params );
624 my ($iTotalRecords) = $sth->fetchrow_array();
626 my @avs = Koha::AuthorisedValues->search(
627 { 'marc_subfield_structures.kohafield' => { '>' => '' },
628 'me.authorised_value' => { '>' => '' },
630 { join => { category => 'marc_subfield_structures' },
631 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
632 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
633 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
637 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
639 foreach my $row (@$tmpresults) {
642 foreach (keys %$row) {
645 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
648 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
654 return (\@results, $iTotalRecords);
659 @results = GetItemsInfo($biblionumber);
661 Returns information about items with the given biblionumber.
663 C<GetItemsInfo> returns a list of references-to-hash. Each element
664 contains a number of keys. Most of them are attributes from the
665 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
666 Koha database. Other keys include:
670 =item C<$data-E<gt>{branchname}>
672 The name (not the code) of the branch to which the book belongs.
674 =item C<$data-E<gt>{datelastseen}>
676 This is simply C<items.datelastseen>, except that while the date is
677 stored in YYYY-MM-DD format in the database, here it is converted to
678 DD/MM/YYYY format. A NULL date is returned as C<//>.
680 =item C<$data-E<gt>{datedue}>
682 =item C<$data-E<gt>{class}>
684 This is the concatenation of C<biblioitems.classification>, the book's
685 Dewey code, and C<biblioitems.subclass>.
687 =item C<$data-E<gt>{ocount}>
689 I think this is the number of copies of the book available.
691 =item C<$data-E<gt>{order}>
693 If this is set, it is set to C<One Order>.
700 my ( $biblionumber ) = @_;
701 my $dbh = C4::Context->dbh;
702 require C4::Languages;
703 my $language = C4::Languages::getlanguage();
709 biblioitems.itemtype,
712 biblioitems.publicationyear,
713 biblioitems.publishercode,
714 biblioitems.volumedate,
715 biblioitems.volumedesc,
718 items.notforloan as itemnotforloan,
719 issues.borrowernumber,
720 issues.date_due as datedue,
721 issues.onsite_checkout,
722 borrowers.cardnumber,
725 borrowers.branchcode as bcode,
727 serial.publisheddate,
728 itemtypes.description,
729 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
730 itemtypes.notforloan as notforloan_per_itemtype,
734 holding.opac_info as holding_branch_opac_info,
735 home.opac_info as home_branch_opac_info,
736 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
738 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
739 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
740 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
741 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
742 LEFT JOIN issues USING (itemnumber)
743 LEFT JOIN borrowers USING (borrowernumber)
744 LEFT JOIN serialitems USING (itemnumber)
745 LEFT JOIN serial USING (serialid)
746 LEFT JOIN itemtypes ON itemtypes.itemtype = "
747 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
749 LEFT JOIN tmp_holdsqueue USING (itemnumber)
750 LEFT JOIN localization ON itemtypes.itemtype = localization.code
751 AND localization.entity = 'itemtypes'
752 AND localization.lang = ?
755 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
756 my $sth = $dbh->prepare($query);
757 $sth->execute($language, $biblionumber);
762 my $userenv = C4::Context->userenv;
763 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
764 while ( my $data = $sth->fetchrow_hashref ) {
765 if ( $data->{borrowernumber} && $want_not_same_branch) {
766 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
769 $serial ||= $data->{'serial'};
772 # get notforloan complete status if applicable
773 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
774 $data->{notforloanvalue} = $descriptions->{lib} // '';
775 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
777 # get restricted status and description if applicable
778 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
779 $data->{restrictedvalue} = $descriptions->{lib} // '';
780 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
782 # my stack procedures
783 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
784 $data->{stack} = $descriptions->{lib} // '';
786 # Find the last 3 people who borrowed this item.
787 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
789 AND old_issues.borrowernumber = borrowers.borrowernumber
790 ORDER BY returndate DESC
792 $sth2->execute($data->{'itemnumber'});
794 while (my $data2 = $sth2->fetchrow_hashref()) {
795 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
796 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
797 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
801 $results[$i] = $data;
806 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
810 =head2 GetItemsLocationInfo
812 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
814 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
816 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
820 =item C<$data-E<gt>{homebranch}>
822 Branch Name of the item's homebranch
824 =item C<$data-E<gt>{holdingbranch}>
826 Branch Name of the item's holdingbranch
828 =item C<$data-E<gt>{location}>
830 Item's shelving location code
832 =item C<$data-E<gt>{location_intranet}>
834 The intranet description for the Shelving Location as set in authorised_values 'LOC'
836 =item C<$data-E<gt>{location_opac}>
838 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
841 =item C<$data-E<gt>{itemcallnumber}>
843 Item's itemcallnumber
845 =item C<$data-E<gt>{cn_sort}>
847 Item's call number normalized for sorting
853 sub GetItemsLocationInfo {
854 my $biblionumber = shift;
857 my $dbh = C4::Context->dbh;
858 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
859 location, itemcallnumber, cn_sort
860 FROM items, branches as a, branches as b
861 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
863 ORDER BY cn_sort ASC";
864 my $sth = $dbh->prepare($query);
865 $sth->execute($biblionumber);
867 while ( my $data = $sth->fetchrow_hashref ) {
868 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
869 $av = $av->count ? $av->next : undef;
870 $data->{location_intranet} = $av ? $av->lib : '';
871 $data->{location_opac} = $av ? $av->opac_description : '';
872 push @results, $data;
877 =head2 GetHostItemsInfo
879 $hostiteminfo = GetHostItemsInfo($hostfield);
880 Returns the iteminfo for items linked to records via a host field
884 sub GetHostItemsInfo {
888 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
889 return @returnitemsInfo;
893 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
894 C4::Context->preference('marcflavour') eq 'NORMARC') {
895 @fields = $record->field('773');
896 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
897 @fields = $record->field('461');
900 foreach my $hostfield ( @fields ) {
901 my $hostbiblionumber = $hostfield->subfield("0");
902 my $linkeditemnumber = $hostfield->subfield("9");
903 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
904 foreach my $hostitemInfo (@hostitemInfos) {
905 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
906 push @returnitemsInfo, $hostitemInfo;
911 return @returnitemsInfo;
914 =head2 get_hostitemnumbers_of
916 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
918 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
920 Return a reference on a hash where key is a biblionumber and values are
921 references on array of itemnumbers.
926 sub get_hostitemnumbers_of {
927 my ($biblionumber) = @_;
929 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
933 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
934 return unless $marcrecord;
936 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
938 my $marcflavor = C4::Context->preference('marcflavour');
939 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
944 elsif ( $marcflavor eq 'UNIMARC' ) {
950 foreach my $hostfield ( $marcrecord->field($tag) ) {
951 my $hostbiblionumber = $hostfield->subfield($biblio_s);
952 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
953 my $linkeditemnumber = $hostfield->subfield($item_s);
954 if ( ! $linkeditemnumber ) {
955 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
958 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
959 push @returnhostitemnumbers, $linkeditemnumber
963 return @returnhostitemnumbers;
966 =head2 GetHiddenItemnumbers
968 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
970 Given a list of items it checks which should be hidden from the OPAC given
971 the current configuration. Returns a list of itemnumbers corresponding to
972 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
977 sub GetHiddenItemnumbers {
979 my $items = $params->{items};
980 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
981 foreach my $except (split(/\|/, $exceptions)){
982 if ($params->{'borcat'} eq $except){
983 return; # we don't hide anything for this borrower category
989 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
994 my $dbh = C4::Context->dbh;
997 foreach my $item (@$items) {
1000 foreach my $field (keys %$hidingrules) {
1002 if (exists $item->{$field}) {
1003 $val = $item->{$field};
1006 my $query = "SELECT $field from items where itemnumber = ?";
1007 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1009 $val = '' unless defined $val;
1011 # If the results matches the values in the yaml file
1012 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1014 # We add the itemnumber to the list
1015 push @resultitems, $item->{'itemnumber'};
1017 # If at least one rule matched for an item, no need to test the others
1022 return @resultitems;
1025 =head1 LIMITED USE FUNCTIONS
1027 The following functions, while part of the public API,
1028 are not exported. This is generally because they are
1029 meant to be used by only one script for a specific
1030 purpose, and should not be used in any other context
1031 without careful thought.
1037 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1039 Returns MARC::Record of the item passed in parameter.
1040 This function is meant for use only in C<cataloguing/additem.pl>,
1041 where it is needed to support that script's MARC-like
1047 my ( $biblionumber, $itemnumber ) = @_;
1049 # GetMarcItem has been revised so that it does the following:
1050 # 1. Gets the item information from the items table.
1051 # 2. Converts it to a MARC field for storage in the bib record.
1053 # The previous behavior was:
1054 # 1. Get the bib record.
1055 # 2. Return the MARC tag corresponding to the item record.
1057 # The difference is that one treats the items row as authoritative,
1058 # while the other treats the MARC representation as authoritative
1059 # under certain circumstances.
1061 my $item = Koha::Items->find($itemnumber) or return;
1063 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1064 # Also, don't emit a subfield if the underlying field is blank.
1066 return Item2Marc($item->unblessed, $biblionumber);
1070 my ($itemrecord,$biblionumber)=@_;
1073 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1074 } keys %{ $itemrecord }
1076 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1077 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1078 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1079 "items.itemnumber", $framework,
1082 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1083 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1084 foreach my $field ($itemmarc->field($itemtag)){
1085 $field->add_subfields(@$unlinked_item_subfields);
1091 =head1 PRIVATE FUNCTIONS AND VARIABLES
1093 The following functions are not meant to be called
1094 directly, but are documented in order to explain
1095 the inner workings of C<C4::Items>.
1099 =head2 MoveItemFromBiblio
1101 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1103 Moves an item from a biblio to another
1105 Returns undef if the move failed or the biblionumber of the destination record otherwise
1109 sub MoveItemFromBiblio {
1110 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1111 my $dbh = C4::Context->dbh;
1112 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1113 SELECT biblioitemnumber
1115 WHERE biblionumber = ?
1116 |, undef, $tobiblio );
1117 my $return = $dbh->do(q|
1119 SET biblioitemnumber = ?,
1121 WHERE itemnumber = ?
1122 AND biblionumber = ?
1123 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1125 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1126 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1127 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1128 # Checking if the item we want to move is in an order
1129 require C4::Acquisition;
1130 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1132 # Replacing the biblionumber within the order if necessary
1133 $order->{'biblionumber'} = $tobiblio;
1134 C4::Acquisition::ModOrder($order);
1137 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1138 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1141 SET biblionumber = ?
1142 WHERE itemnumber = ?
1143 |, undef, $tobiblio, $itemnumber );
1150 =head2 _marc_from_item_hash
1152 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1154 Given an item hash representing a complete item record,
1155 create a C<MARC::Record> object containing an embedded
1156 tag representing that item.
1158 The third, optional parameter C<$unlinked_item_subfields> is
1159 an arrayref of subfields (not mapped to C<items> fields per the
1160 framework) to be added to the MARC representation
1165 sub _marc_from_item_hash {
1167 my $frameworkcode = shift;
1168 my $unlinked_item_subfields;
1170 $unlinked_item_subfields = shift;
1173 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1174 # Also, don't emit a subfield if the underlying field is blank.
1175 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1176 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1177 : () } keys %{ $item } };
1179 my $item_marc = MARC::Record->new();
1180 foreach my $item_field ( keys %{$mungeditem} ) {
1181 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1182 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1183 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1184 foreach my $value (@values){
1185 if ( my $field = $item_marc->field($tag) ) {
1186 $field->add_subfields( $subfield => $value );
1188 my $add_subfields = [];
1189 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1190 $add_subfields = $unlinked_item_subfields;
1192 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1200 =head2 _repack_item_errors
1202 Add an error message hash generated by C<CheckItemPreSave>
1203 to a list of errors.
1207 sub _repack_item_errors {
1208 my $item_sequence_num = shift;
1209 my $item_ref = shift;
1210 my $error_ref = shift;
1212 my @repacked_errors = ();
1214 foreach my $error_code (sort keys %{ $error_ref }) {
1215 my $repacked_error = {};
1216 $repacked_error->{'item_sequence'} = $item_sequence_num;
1217 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1218 $repacked_error->{'error_code'} = $error_code;
1219 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1220 push @repacked_errors, $repacked_error;
1223 return @repacked_errors;
1226 =head2 _get_unlinked_item_subfields
1228 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1232 sub _get_unlinked_item_subfields {
1233 my $original_item_marc = shift;
1234 my $frameworkcode = shift;
1236 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1238 # assume that this record has only one field, and that that
1239 # field contains only the item information
1241 my @fields = $original_item_marc->fields();
1242 if ($#fields > -1) {
1243 my $field = $fields[0];
1244 my $tag = $field->tag();
1245 foreach my $subfield ($field->subfields()) {
1246 if (defined $subfield->[1] and
1247 $subfield->[1] ne '' and
1248 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1249 push @$subfields, $subfield->[0] => $subfield->[1];
1256 =head2 _get_unlinked_subfields_xml
1258 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1262 sub _get_unlinked_subfields_xml {
1263 my $unlinked_item_subfields = shift;
1266 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1267 my $marc = MARC::Record->new();
1268 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1269 # used in the framework
1270 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1271 $marc->encoding("UTF-8");
1272 $xml = $marc->as_xml("USMARC");
1278 =head2 _parse_unlinked_item_subfields_from_xml
1280 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1284 sub _parse_unlinked_item_subfields_from_xml {
1286 require C4::Charset;
1287 return unless defined $xml and $xml ne "";
1288 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1289 my $unlinked_subfields = [];
1290 my @fields = $marc->fields();
1291 if ($#fields > -1) {
1292 foreach my $subfield ($fields[0]->subfields()) {
1293 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1296 return $unlinked_subfields;
1299 =head2 GetAnalyticsCount
1301 $count= &GetAnalyticsCount($itemnumber)
1303 counts Usage of itemnumber in Analytical bibliorecords.
1307 sub GetAnalyticsCount {
1308 my ($itemnumber) = @_;
1310 ### ZOOM search here
1312 $query= "hi=".$itemnumber;
1313 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1314 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1318 sub _SearchItems_build_where_fragment {
1321 my $dbh = C4::Context->dbh;
1324 if (exists($filter->{conjunction})) {
1325 my (@where_strs, @where_args);
1326 foreach my $f (@{ $filter->{filters} }) {
1327 my $fragment = _SearchItems_build_where_fragment($f);
1329 push @where_strs, $fragment->{str};
1330 push @where_args, @{ $fragment->{args} };
1335 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1338 args => \@where_args,
1342 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1343 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1344 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1345 my @operators = qw(= != > < >= <= like);
1346 push @operators, 'not like';
1347 my $field = $filter->{field} // q{};
1348 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1349 my $op = $filter->{operator};
1350 my $query = $filter->{query};
1351 my $ifnull = $filter->{ifnull};
1353 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1354 $op = '='; # default operator
1358 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1360 my $marcsubfield = $2;
1361 my ($kohafield) = $dbh->selectrow_array(q|
1362 SELECT kohafield FROM marc_subfield_structure
1363 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1364 |, undef, $marcfield, $marcsubfield);
1367 $column = $kohafield;
1369 # MARC field is not linked to a DB field so we need to use
1370 # ExtractValue on marcxml from biblio_metadata or
1371 # items.more_subfields_xml, depending on the MARC field.
1374 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1375 if ($marcfield eq $itemfield) {
1376 $sqlfield = 'more_subfields_xml';
1377 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1379 $sqlfield = 'metadata'; # From biblio_metadata
1380 if ($marcfield < 10) {
1381 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1383 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1386 $column = "ExtractValue($sqlfield, '$xpath')";
1392 if ( defined $ifnull ) {
1393 $column = "COALESCE($column, ?)";
1396 if (ref $query eq 'ARRAY') {
1399 } elsif ($op eq '!=') {
1403 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1408 str => "$column $op ?",
1413 if ( defined $ifnull ) {
1414 unshift @{ $where_fragment->{args} }, $ifnull;
1419 return $where_fragment;
1424 my ($items, $total) = SearchItems($filter, $params);
1426 Perform a search among items
1428 $filter is a reference to a hash which can be a filter, or a combination of filters.
1430 A filter has the following keys:
1434 =item * field: the name of a SQL column in table items
1436 =item * query: the value to search in this column
1438 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1442 A combination of filters hash the following keys:
1446 =item * conjunction: 'AND' or 'OR'
1448 =item * filters: array ref of filters
1452 $params is a reference to a hash that can contain the following parameters:
1456 =item * rows: Number of items to return. 0 returns everything (default: 0)
1458 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1461 =item * sortby: A SQL column name in items table to sort on
1463 =item * sortorder: 'ASC' or 'DESC'
1470 my ($filter, $params) = @_;
1474 return unless ref $filter eq 'HASH';
1475 return unless ref $params eq 'HASH';
1477 # Default parameters
1478 $params->{rows} ||= 0;
1479 $params->{page} ||= 1;
1480 $params->{sortby} ||= 'itemnumber';
1481 $params->{sortorder} ||= 'ASC';
1483 my ($where_str, @where_args);
1484 my $where_fragment = _SearchItems_build_where_fragment($filter);
1485 if ($where_fragment) {
1486 $where_str = $where_fragment->{str};
1487 @where_args = @{ $where_fragment->{args} };
1490 my $dbh = C4::Context->dbh;
1492 SELECT SQL_CALC_FOUND_ROWS items.*
1494 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1495 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1496 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1499 if (defined $where_str and $where_str ne '') {
1500 $query .= qq{ AND $where_str };
1503 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1504 push @where_args, C4::Context->preference('marcflavour');
1506 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1507 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1508 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1509 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1510 ? $params->{sortby} : 'itemnumber';
1511 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1512 $query .= qq{ ORDER BY $sortby $sortorder };
1514 my $rows = $params->{rows};
1517 my $offset = $rows * ($params->{page}-1);
1518 $query .= qq { LIMIT ?, ? };
1519 push @limit_args, $offset, $rows;
1522 my $sth = $dbh->prepare($query);
1523 my $rv = $sth->execute(@where_args, @limit_args);
1525 return unless ($rv);
1526 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1528 return ($sth->fetchall_arrayref({}), $total_rows);
1532 =head1 OTHER FUNCTIONS
1536 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1538 Find the given $subfield in the given $tag in the given
1539 MARC::Record $record. If the subfield is found, returns
1540 the (indicators, value) pair; otherwise, (undef, undef) is
1544 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1545 I suggest we export it from this module.
1550 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1553 if ( $tagfield < 10 ) {
1554 if ( $record->field($tagfield) ) {
1555 push @result, $record->field($tagfield)->data();
1560 foreach my $field ( $record->field($tagfield) ) {
1561 my @subfields = $field->subfields();
1562 foreach my $subfield (@subfields) {
1563 if ( @$subfield[0] eq $insubfield ) {
1564 push @result, @$subfield[1];
1565 $indicator = $field->indicator(1) . $field->indicator(2);
1570 return ( $indicator, @result );
1574 =head2 PrepareItemrecordDisplay
1576 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1578 Returns a hash with all the fields for Display a given item data in a template
1580 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1584 sub PrepareItemrecordDisplay {
1586 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1588 my $dbh = C4::Context->dbh;
1589 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1590 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1592 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1593 # a shared data structure. No plugin (including custom ones) should change
1594 # its contents. See also GetMarcStructure.
1595 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1597 # Pick the default location from NewItemsDefaultLocation
1598 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1599 $defaultvalues //= {};
1600 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1603 # return nothing if we don't have found an existing framework.
1604 return q{} unless $tagslib;
1607 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1611 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1613 SELECT authorised_value,lib FROM authorised_values
1616 LEFT JOIN authorised_values_branches ON ( id = av_id )
1621 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1622 $query .= qq{ ORDER BY lib};
1623 my $authorised_values_sth = $dbh->prepare( $query );
1624 foreach my $tag ( sort keys %{$tagslib} ) {
1627 # loop through each subfield
1629 foreach my $subfield (
1630 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1631 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1632 values %{ $tagslib->{$tag} } )
1634 next unless ( $subfield->{'tab'} );
1635 next if ( $subfield->{'tab'} ne "10" );
1637 $subfield_data{tag} = $tag;
1638 $subfield_data{subfield} = $subfield->{subfield};
1639 $subfield_data{countsubfield} = $cntsubf++;
1640 $subfield_data{kohafield} = $subfield->{kohafield};
1641 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1643 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1644 $subfield_data{marc_lib} = $subfield->{lib};
1645 $subfield_data{mandatory} = $subfield->{mandatory};
1646 $subfield_data{repeatable} = $subfield->{repeatable};
1647 $subfield_data{hidden} = "display:none"
1648 if ( ( $subfield->{hidden} > 4 )
1649 || ( $subfield->{hidden} < -4 ) );
1650 my ( $x, $defaultvalue );
1652 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1654 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1655 if ( !defined $defaultvalue ) {
1656 $defaultvalue = q||;
1658 $defaultvalue =~ s/"/"/g;
1659 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1660 my $today_dt = dt_from_string;
1661 my $year = $today_dt->strftime('%Y');
1662 my $shortyear = $today_dt->strftime('%y');
1663 my $month = $today_dt->strftime('%m');
1664 my $day = $today_dt->strftime('%d');
1665 $defaultvalue =~ s/<<YYYY>>/$year/g;
1666 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1667 $defaultvalue =~ s/<<MM>>/$month/g;
1668 $defaultvalue =~ s/<<DD>>/$day/g;
1670 # And <<USER>> with surname (?)
1672 ( C4::Context->userenv
1673 ? C4::Context->userenv->{'surname'}
1674 : "superlibrarian" );
1675 $defaultvalue =~ s/<<USER>>/$username/g;
1678 my $maxlength = $subfield->{maxlength};
1680 # search for itemcallnumber if applicable
1681 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1682 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1683 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1684 my $CNtag = substr( $itemcn_pref, 0, 3 );
1685 next unless my $field = $itemrecord->field($CNtag);
1686 my $CNsubfields = substr( $itemcn_pref, 3 );
1687 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1688 last if $defaultvalue;
1691 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1693 && $defaultvalues->{'callnumber'} ) {
1694 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1695 # if the item record exists, only use default value if the item has no callnumber
1696 $defaultvalue = $defaultvalues->{callnumber};
1697 } elsif ( !$itemrecord and $defaultvalues ) {
1698 # if the item record *doesn't* exists, always use the default value
1699 $defaultvalue = $defaultvalues->{callnumber};
1702 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1704 && $defaultvalues->{'branchcode'} ) {
1705 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1706 $defaultvalue = $defaultvalues->{branchcode};
1709 if ( ( $subfield->{kohafield} eq 'items.location' )
1711 && $defaultvalues->{'location'} ) {
1713 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1714 # if the item record exists, only use default value if the item has no locationr
1715 $defaultvalue = $defaultvalues->{location};
1716 } elsif ( !$itemrecord and $defaultvalues ) {
1717 # if the item record *doesn't* exists, always use the default value
1718 $defaultvalue = $defaultvalues->{location};
1721 if ( $subfield->{authorised_value} ) {
1722 my @authorised_values;
1725 # builds list, depending on authorised value...
1727 if ( $subfield->{'authorised_value'} eq "branches" ) {
1728 if ( ( C4::Context->preference("IndependentBranches") )
1729 && !C4::Context->IsSuperLibrarian() ) {
1730 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1731 $sth->execute( C4::Context->userenv->{branch} );
1732 push @authorised_values, ""
1733 unless ( $subfield->{mandatory} );
1734 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1735 push @authorised_values, $branchcode;
1736 $authorised_lib{$branchcode} = $branchname;
1739 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1741 push @authorised_values, ""
1742 unless ( $subfield->{mandatory} );
1743 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1744 push @authorised_values, $branchcode;
1745 $authorised_lib{$branchcode} = $branchname;
1749 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1750 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1751 $defaultvalue = $defaultvalues->{branchcode};
1755 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1756 my $itemtypes = Koha::ItemTypes->search_with_localization;
1757 push @authorised_values, "";
1758 while ( my $itemtype = $itemtypes->next ) {
1759 push @authorised_values, $itemtype->itemtype;
1760 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1762 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1763 $defaultvalue = $defaultvalues->{'itemtype'};
1767 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1768 push @authorised_values, "";
1770 my $class_sources = GetClassSources();
1771 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1773 foreach my $class_source (sort keys %$class_sources) {
1774 next unless $class_sources->{$class_source}->{'used'} or
1775 ($class_source eq $default_source);
1776 push @authorised_values, $class_source;
1777 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1780 $defaultvalue = $default_source;
1782 #---- "true" authorised value
1784 $authorised_values_sth->execute(
1785 $subfield->{authorised_value},
1786 $branch_limit ? $branch_limit : ()
1788 push @authorised_values, "";
1789 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1790 push @authorised_values, $value;
1791 $authorised_lib{$value} = $lib;
1794 $subfield_data{marc_value} = {
1796 values => \@authorised_values,
1797 default => $defaultvalue // q{},
1798 labels => \%authorised_lib,
1800 } elsif ( $subfield->{value_builder} ) {
1802 require Koha::FrameworkPlugin;
1803 my $plugin = Koha::FrameworkPlugin->new({
1804 name => $subfield->{value_builder},
1807 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1808 $plugin->build( $pars );
1809 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1810 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1812 if( !$plugin->errstr ) {
1813 #TODO Move html to template; see report 12176/13397
1814 my $tab= $plugin->noclick? '-1': '';
1815 my $class= $plugin->noclick? ' disabled': '';
1816 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1817 $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;
1819 warn $plugin->errstr;
1820 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
1823 elsif ( $tag eq '' ) { # it's an hidden field
1824 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1826 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
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 elsif ( length($defaultvalue) > 100
1830 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1831 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1832 or (C4::Context->preference("marcflavour") eq "MARC21" and
1833 500 <= $tag && $tag < 600 )
1835 # oversize field (textarea)
1836 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1838 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1840 push( @loop_data, \%subfield_data );
1845 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1846 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1849 'itemtagfield' => $itemtagfield,
1850 'itemtagsubfield' => $itemtagsubfield,
1851 'itemnumber' => $itemnumber,
1852 'iteminformation' => \@loop_data
1856 sub ToggleNewStatus {
1857 my ( $params ) = @_;
1858 my @rules = @{ $params->{rules} };
1859 my $report_only = $params->{report_only};
1861 my $dbh = C4::Context->dbh;
1863 my @item_columns = map { "items.$_" } Koha::Items->columns;
1864 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1866 for my $rule ( @rules ) {
1867 my $age = $rule->{age};
1868 my $conditions = $rule->{conditions};
1869 my $substitutions = $rule->{substitutions};
1870 foreach ( @$substitutions ) {
1871 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1878 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1881 for my $condition ( @$conditions ) {
1883 grep { $_ eq $condition->{field} } @item_columns
1884 or grep { $_ eq $condition->{field} } @biblioitem_columns
1886 if ( $condition->{value} =~ /\|/ ) {
1887 my @values = split /\|/, $condition->{value};
1888 $query .= qq| AND $condition->{field} IN (|
1889 . join( ',', ('?') x scalar @values )
1891 push @params, @values;
1893 $query .= qq| AND $condition->{field} = ?|;
1894 push @params, $condition->{value};
1898 if ( defined $age ) {
1899 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1902 my $sth = $dbh->prepare($query);
1903 $sth->execute( @params );
1904 while ( my $values = $sth->fetchrow_hashref ) {
1905 my $biblionumber = $values->{biblionumber};
1906 my $itemnumber = $values->{itemnumber};
1907 my $item = Koha::Items->find($itemnumber);
1908 for my $substitution ( @$substitutions ) {
1909 my $field = $substitution->{item_field};
1910 my $value = $substitution->{value};
1911 next unless $substitution->{field};
1912 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1913 $item->$field($value);
1914 push @{ $report->{$itemnumber} }, $substitution;
1916 $item->store unless $report_only;