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 my $has_permanent_location = $item->{permanent_location};
309 # Retrieving the values for the fields that are not linked
310 my @mapped_fields = Koha::MarcSubfieldStructures->search(
312 frameworkcode => $frameworkcode,
313 kohafield => { -like => "items.%" }
315 )->get_column('kohafield');
316 for my $c ( $item_object->_result->result_source->columns ) {
317 next if grep { "items.$c" eq $_ } @mapped_fields;
318 $item->{$c} = $item_object->$c;
321 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
322 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
323 $item->{itemnumber} = $itemnumber;
324 $item->{biblionumber} = $biblionumber;
326 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
327 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
328 $item_object = $item_object->set_or_blank($item);
329 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
331 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
333 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
334 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
335 $item_object->store({ skip_record_index => $params->{skip_record_index} });
337 return $item_object->unblessed;
340 =head2 ModItemTransfer
342 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
344 Marks an item as being transferred from one branch to another and records the trigger.
346 The last optional parameter allows for passing skip_record_index through to the items store call.
350 sub ModItemTransfer {
351 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
353 my $dbh = C4::Context->dbh;
354 my $item = Koha::Items->find( $itemnumber );
356 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
357 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
358 # will have been preceded by a check of branch transfer limits)
359 my $to_library = Koha::Libraries->find($tobranch);
360 my $transfer = $item->request_transfer(
369 # Immediately set the item to in transit if it is checked in
370 if ( !$item->checkout ) {
371 $item->holdingbranch($frombranch)->store(
374 skip_record_index => $params->{skip_record_index}
383 =head2 ModDateLastSeen
385 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
387 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
388 C<$itemnumber> is the item number
389 C<$leave_item_lost> determines if a lost item will be found or remain lost
391 The last optional parameter allows for passing skip_record_index through to the items store call.
395 sub ModDateLastSeen {
396 my ( $itemnumber, $leave_item_lost, $params ) = @_;
398 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
400 my $item = Koha::Items->find($itemnumber);
401 $item->datelastseen($today);
402 $item->itemlost(0) unless $leave_item_lost;
403 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
406 =head2 CheckItemPreSave
408 my $item_ref = TransformMarcToKoha($marc, 'items');
410 my %errors = CheckItemPreSave($item_ref);
411 if (exists $errors{'duplicate_barcode'}) {
412 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
413 } elsif (exists $errors{'invalid_homebranch'}) {
414 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
415 } elsif (exists $errors{'invalid_holdingbranch'}) {
416 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
421 Given a hashref containing item fields, determine if it can be
422 inserted or updated in the database. Specifically, checks for
423 database integrity issues, and returns a hash containing any
424 of the following keys, if applicable.
428 =item duplicate_barcode
430 Barcode, if it duplicates one already found in the database.
432 =item invalid_homebranch
434 Home branch, if not defined in branches table.
436 =item invalid_holdingbranch
438 Holding branch, if not defined in branches table.
442 This function does NOT implement any policy-related checks,
443 e.g., whether current operator is allowed to save an
444 item that has a given branch code.
448 sub CheckItemPreSave {
449 my $item_ref = shift;
453 # check for duplicate barcode
454 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
455 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
456 if ($existing_item) {
457 if (!exists $item_ref->{'itemnumber'} # new item
458 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
459 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
464 # check for valid home branch
465 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
466 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
467 unless (defined $home_library) {
468 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
472 # check for valid holding branch
473 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
474 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
475 unless (defined $holding_library) {
476 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
484 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
486 The following functions provide various ways of
487 getting an item record, a set of item records, or
488 lists of authorized values for certain item fields.
492 =head2 GetItemsForInventory
494 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
495 minlocation => $minlocation,
496 maxlocation => $maxlocation,
497 location => $location,
498 ignoreissued => $ignoreissued,
499 datelastseen => $datelastseen,
500 branchcode => $branchcode,
504 statushash => $statushash,
505 itemtypes => \@itemsarray,
508 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
510 The sub returns a reference to a list of hashes, each containing
511 itemnumber, author, title, barcode, item callnumber, and date last
512 seen. It is ordered by callnumber then title.
514 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
515 the datelastseen can be used to specify that you want to see items not seen since a past date only.
516 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
517 $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.
519 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
523 sub GetItemsForInventory {
524 my ( $parameters ) = @_;
525 my $minlocation = $parameters->{'minlocation'} // '';
526 my $maxlocation = $parameters->{'maxlocation'} // '';
527 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
528 my $location = $parameters->{'location'} // '';
529 my $itemtype = $parameters->{'itemtype'} // '';
530 my $ignoreissued = $parameters->{'ignoreissued'} // '';
531 my $datelastseen = $parameters->{'datelastseen'} // '';
532 my $branchcode = $parameters->{'branchcode'} // '';
533 my $branch = $parameters->{'branch'} // '';
534 my $offset = $parameters->{'offset'} // '';
535 my $size = $parameters->{'size'} // '';
536 my $statushash = $parameters->{'statushash'} // '';
537 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
538 my $itemtypes = $parameters->{'itemtypes'} || [];
540 my $dbh = C4::Context->dbh;
541 my ( @bind_params, @where_strings );
543 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
544 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
546 my $select_columns = q{
547 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
549 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
552 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
553 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
556 for my $authvfield (keys %$statushash){
557 if ( scalar @{$statushash->{$authvfield}} > 0 ){
558 my $joinedvals = join ',', @{$statushash->{$authvfield}};
559 push @where_strings, "$authvfield in (" . $joinedvals . ")";
565 push @where_strings, 'items.cn_sort >= ?';
566 push @bind_params, $min_cnsort;
570 push @where_strings, 'items.cn_sort <= ?';
571 push @bind_params, $max_cnsort;
575 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
576 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
577 push @bind_params, $datelastseen;
581 push @where_strings, 'items.location = ?';
582 push @bind_params, $location;
586 if($branch eq "homebranch"){
587 push @where_strings, 'items.homebranch = ?';
589 push @where_strings, 'items.holdingbranch = ?';
591 push @bind_params, $branchcode;
595 push @where_strings, 'biblioitems.itemtype = ?';
596 push @bind_params, $itemtype;
598 if ( $ignoreissued) {
599 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
600 push @where_strings, 'issues.date_due IS NULL';
603 if ( $ignore_waiting_holds ) {
604 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
605 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
609 my $itemtypes_str = join ', ', @$itemtypes;
610 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
613 if ( @where_strings ) {
615 $query .= join ' AND ', @where_strings;
617 my $count_query = $select_count . $query;
618 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
619 $query .= " LIMIT $offset, $size" if ($offset and $size);
620 $query = $select_columns . $query;
621 my $sth = $dbh->prepare($query);
622 $sth->execute( @bind_params );
625 my $tmpresults = $sth->fetchall_arrayref({});
626 $sth = $dbh->prepare( $count_query );
627 $sth->execute( @bind_params );
628 my ($iTotalRecords) = $sth->fetchrow_array();
630 my @avs = Koha::AuthorisedValues->search(
631 { 'marc_subfield_structures.kohafield' => { '>' => '' },
632 'me.authorised_value' => { '>' => '' },
634 { join => { category => 'marc_subfield_structures' },
635 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
636 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
637 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
641 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
643 foreach my $row (@$tmpresults) {
646 foreach (keys %$row) {
649 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
652 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
658 return (\@results, $iTotalRecords);
663 @results = GetItemsInfo($biblionumber);
665 Returns information about items with the given biblionumber.
667 C<GetItemsInfo> returns a list of references-to-hash. Each element
668 contains a number of keys. Most of them are attributes from the
669 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
670 Koha database. Other keys include:
674 =item C<$data-E<gt>{branchname}>
676 The name (not the code) of the branch to which the book belongs.
678 =item C<$data-E<gt>{datelastseen}>
680 This is simply C<items.datelastseen>, except that while the date is
681 stored in YYYY-MM-DD format in the database, here it is converted to
682 DD/MM/YYYY format. A NULL date is returned as C<//>.
684 =item C<$data-E<gt>{datedue}>
686 =item C<$data-E<gt>{class}>
688 This is the concatenation of C<biblioitems.classification>, the book's
689 Dewey code, and C<biblioitems.subclass>.
691 =item C<$data-E<gt>{ocount}>
693 I think this is the number of copies of the book available.
695 =item C<$data-E<gt>{order}>
697 If this is set, it is set to C<One Order>.
704 my ( $biblionumber ) = @_;
705 my $dbh = C4::Context->dbh;
706 require C4::Languages;
707 my $language = C4::Languages::getlanguage();
713 biblioitems.itemtype,
716 biblioitems.publicationyear,
717 biblioitems.publishercode,
718 biblioitems.volumedate,
719 biblioitems.volumedesc,
722 items.notforloan as itemnotforloan,
723 issues.borrowernumber,
724 issues.date_due as datedue,
725 issues.onsite_checkout,
726 borrowers.cardnumber,
729 borrowers.branchcode as bcode,
731 serial.publisheddate,
732 itemtypes.description,
733 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
734 itemtypes.notforloan as notforloan_per_itemtype,
738 holding.opac_info as holding_branch_opac_info,
739 home.opac_info as home_branch_opac_info,
740 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
742 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
743 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
744 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
745 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
746 LEFT JOIN issues USING (itemnumber)
747 LEFT JOIN borrowers USING (borrowernumber)
748 LEFT JOIN serialitems USING (itemnumber)
749 LEFT JOIN serial USING (serialid)
750 LEFT JOIN itemtypes ON itemtypes.itemtype = "
751 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
753 LEFT JOIN tmp_holdsqueue USING (itemnumber)
754 LEFT JOIN localization ON itemtypes.itemtype = localization.code
755 AND localization.entity = 'itemtypes'
756 AND localization.lang = ?
759 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
760 my $sth = $dbh->prepare($query);
761 $sth->execute($language, $biblionumber);
766 my $userenv = C4::Context->userenv;
767 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
768 while ( my $data = $sth->fetchrow_hashref ) {
769 if ( $data->{borrowernumber} && $want_not_same_branch) {
770 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
773 $serial ||= $data->{'serial'};
776 # get notforloan complete status if applicable
777 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
778 $data->{notforloanvalue} = $descriptions->{lib} // '';
779 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
781 # get restricted status and description if applicable
782 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
783 $data->{restrictedvalue} = $descriptions->{lib} // '';
784 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
786 # my stack procedures
787 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
788 $data->{stack} = $descriptions->{lib} // '';
790 # Find the last 3 people who borrowed this item.
791 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
793 AND old_issues.borrowernumber = borrowers.borrowernumber
794 ORDER BY returndate DESC
796 $sth2->execute($data->{'itemnumber'});
798 while (my $data2 = $sth2->fetchrow_hashref()) {
799 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
800 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
801 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
805 $results[$i] = $data;
810 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
814 =head2 GetItemsLocationInfo
816 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
818 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
820 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
824 =item C<$data-E<gt>{homebranch}>
826 Branch Name of the item's homebranch
828 =item C<$data-E<gt>{holdingbranch}>
830 Branch Name of the item's holdingbranch
832 =item C<$data-E<gt>{location}>
834 Item's shelving location code
836 =item C<$data-E<gt>{location_intranet}>
838 The intranet description for the Shelving Location as set in authorised_values 'LOC'
840 =item C<$data-E<gt>{location_opac}>
842 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
845 =item C<$data-E<gt>{itemcallnumber}>
847 Item's itemcallnumber
849 =item C<$data-E<gt>{cn_sort}>
851 Item's call number normalized for sorting
857 sub GetItemsLocationInfo {
858 my $biblionumber = shift;
861 my $dbh = C4::Context->dbh;
862 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
863 location, itemcallnumber, cn_sort
864 FROM items, branches as a, branches as b
865 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
867 ORDER BY cn_sort ASC";
868 my $sth = $dbh->prepare($query);
869 $sth->execute($biblionumber);
871 while ( my $data = $sth->fetchrow_hashref ) {
872 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
873 $av = $av->count ? $av->next : undef;
874 $data->{location_intranet} = $av ? $av->lib : '';
875 $data->{location_opac} = $av ? $av->opac_description : '';
876 push @results, $data;
881 =head2 GetHostItemsInfo
883 $hostiteminfo = GetHostItemsInfo($hostfield);
884 Returns the iteminfo for items linked to records via a host field
888 sub GetHostItemsInfo {
892 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
893 return @returnitemsInfo;
897 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
898 C4::Context->preference('marcflavour') eq 'NORMARC') {
899 @fields = $record->field('773');
900 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
901 @fields = $record->field('461');
904 foreach my $hostfield ( @fields ) {
905 my $hostbiblionumber = $hostfield->subfield("0");
906 my $linkeditemnumber = $hostfield->subfield("9");
907 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
908 foreach my $hostitemInfo (@hostitemInfos) {
909 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
910 push @returnitemsInfo, $hostitemInfo;
915 return @returnitemsInfo;
918 =head2 get_hostitemnumbers_of
920 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
922 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
924 Return a reference on a hash where key is a biblionumber and values are
925 references on array of itemnumbers.
930 sub get_hostitemnumbers_of {
931 my ($biblionumber) = @_;
933 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
937 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
938 return unless $marcrecord;
940 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
942 my $marcflavor = C4::Context->preference('marcflavour');
943 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
948 elsif ( $marcflavor eq 'UNIMARC' ) {
954 foreach my $hostfield ( $marcrecord->field($tag) ) {
955 my $hostbiblionumber = $hostfield->subfield($biblio_s);
956 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
957 my $linkeditemnumber = $hostfield->subfield($item_s);
958 if ( ! $linkeditemnumber ) {
959 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
962 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
963 push @returnhostitemnumbers, $linkeditemnumber
967 return @returnhostitemnumbers;
970 =head2 GetHiddenItemnumbers
972 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
974 Given a list of items it checks which should be hidden from the OPAC given
975 the current configuration. Returns a list of itemnumbers corresponding to
976 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
981 sub GetHiddenItemnumbers {
983 my $items = $params->{items};
984 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
985 foreach my $except (split(/\|/, $exceptions)){
986 if ($params->{'borcat'} eq $except){
987 return; # we don't hide anything for this borrower category
993 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
998 my $dbh = C4::Context->dbh;
1001 foreach my $item (@$items) {
1003 # We check each rule
1004 foreach my $field (keys %$hidingrules) {
1006 if (exists $item->{$field}) {
1007 $val = $item->{$field};
1010 my $query = "SELECT $field from items where itemnumber = ?";
1011 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1013 $val = '' unless defined $val;
1015 # If the results matches the values in the yaml file
1016 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1018 # We add the itemnumber to the list
1019 push @resultitems, $item->{'itemnumber'};
1021 # If at least one rule matched for an item, no need to test the others
1026 return @resultitems;
1029 =head1 LIMITED USE FUNCTIONS
1031 The following functions, while part of the public API,
1032 are not exported. This is generally because they are
1033 meant to be used by only one script for a specific
1034 purpose, and should not be used in any other context
1035 without careful thought.
1041 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1043 Returns MARC::Record of the item passed in parameter.
1044 This function is meant for use only in C<cataloguing/additem.pl>,
1045 where it is needed to support that script's MARC-like
1051 my ( $biblionumber, $itemnumber ) = @_;
1053 # GetMarcItem has been revised so that it does the following:
1054 # 1. Gets the item information from the items table.
1055 # 2. Converts it to a MARC field for storage in the bib record.
1057 # The previous behavior was:
1058 # 1. Get the bib record.
1059 # 2. Return the MARC tag corresponding to the item record.
1061 # The difference is that one treats the items row as authoritative,
1062 # while the other treats the MARC representation as authoritative
1063 # under certain circumstances.
1065 my $item = Koha::Items->find($itemnumber) or return;
1067 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1068 # Also, don't emit a subfield if the underlying field is blank.
1070 return Item2Marc($item->unblessed, $biblionumber);
1074 my ($itemrecord,$biblionumber)=@_;
1077 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1078 } keys %{ $itemrecord }
1080 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1081 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1082 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1083 "items.itemnumber", $framework,
1086 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1087 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1088 foreach my $field ($itemmarc->field($itemtag)){
1089 $field->add_subfields(@$unlinked_item_subfields);
1095 =head1 PRIVATE FUNCTIONS AND VARIABLES
1097 The following functions are not meant to be called
1098 directly, but are documented in order to explain
1099 the inner workings of C<C4::Items>.
1103 =head2 MoveItemFromBiblio
1105 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1107 Moves an item from a biblio to another
1109 Returns undef if the move failed or the biblionumber of the destination record otherwise
1113 sub MoveItemFromBiblio {
1114 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1115 my $dbh = C4::Context->dbh;
1116 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1117 SELECT biblioitemnumber
1119 WHERE biblionumber = ?
1120 |, undef, $tobiblio );
1121 my $return = $dbh->do(q|
1123 SET biblioitemnumber = ?,
1125 WHERE itemnumber = ?
1126 AND biblionumber = ?
1127 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1129 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1130 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1131 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1132 # Checking if the item we want to move is in an order
1133 require C4::Acquisition;
1134 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1136 # Replacing the biblionumber within the order if necessary
1137 $order->{'biblionumber'} = $tobiblio;
1138 C4::Acquisition::ModOrder($order);
1141 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1142 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1145 SET biblionumber = ?
1146 WHERE itemnumber = ?
1147 |, undef, $tobiblio, $itemnumber );
1154 =head2 _marc_from_item_hash
1156 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1158 Given an item hash representing a complete item record,
1159 create a C<MARC::Record> object containing an embedded
1160 tag representing that item.
1162 The third, optional parameter C<$unlinked_item_subfields> is
1163 an arrayref of subfields (not mapped to C<items> fields per the
1164 framework) to be added to the MARC representation
1169 sub _marc_from_item_hash {
1171 my $frameworkcode = shift;
1172 my $unlinked_item_subfields;
1174 $unlinked_item_subfields = shift;
1177 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1178 # Also, don't emit a subfield if the underlying field is blank.
1179 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1180 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1181 : () } keys %{ $item } };
1183 my $item_marc = MARC::Record->new();
1184 foreach my $item_field ( keys %{$mungeditem} ) {
1185 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1186 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1187 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1188 foreach my $value (@values){
1189 if ( my $field = $item_marc->field($tag) ) {
1190 $field->add_subfields( $subfield => $value );
1192 my $add_subfields = [];
1193 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1194 $add_subfields = $unlinked_item_subfields;
1196 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1204 =head2 _repack_item_errors
1206 Add an error message hash generated by C<CheckItemPreSave>
1207 to a list of errors.
1211 sub _repack_item_errors {
1212 my $item_sequence_num = shift;
1213 my $item_ref = shift;
1214 my $error_ref = shift;
1216 my @repacked_errors = ();
1218 foreach my $error_code (sort keys %{ $error_ref }) {
1219 my $repacked_error = {};
1220 $repacked_error->{'item_sequence'} = $item_sequence_num;
1221 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1222 $repacked_error->{'error_code'} = $error_code;
1223 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1224 push @repacked_errors, $repacked_error;
1227 return @repacked_errors;
1230 =head2 _get_unlinked_item_subfields
1232 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1236 sub _get_unlinked_item_subfields {
1237 my $original_item_marc = shift;
1238 my $frameworkcode = shift;
1240 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1242 # assume that this record has only one field, and that that
1243 # field contains only the item information
1245 my @fields = $original_item_marc->fields();
1246 if ($#fields > -1) {
1247 my $field = $fields[0];
1248 my $tag = $field->tag();
1249 foreach my $subfield ($field->subfields()) {
1250 if (defined $subfield->[1] and
1251 $subfield->[1] ne '' and
1252 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1253 push @$subfields, $subfield->[0] => $subfield->[1];
1260 =head2 _get_unlinked_subfields_xml
1262 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1266 sub _get_unlinked_subfields_xml {
1267 my $unlinked_item_subfields = shift;
1270 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1271 my $marc = MARC::Record->new();
1272 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1273 # used in the framework
1274 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1275 $marc->encoding("UTF-8");
1276 $xml = $marc->as_xml("USMARC");
1282 =head2 _parse_unlinked_item_subfields_from_xml
1284 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1288 sub _parse_unlinked_item_subfields_from_xml {
1290 require C4::Charset;
1291 return unless defined $xml and $xml ne "";
1292 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1293 my $unlinked_subfields = [];
1294 my @fields = $marc->fields();
1295 if ($#fields > -1) {
1296 foreach my $subfield ($fields[0]->subfields()) {
1297 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1300 return $unlinked_subfields;
1303 =head2 GetAnalyticsCount
1305 $count= &GetAnalyticsCount($itemnumber)
1307 counts Usage of itemnumber in Analytical bibliorecords.
1311 sub GetAnalyticsCount {
1312 my ($itemnumber) = @_;
1314 ### ZOOM search here
1316 $query= "hi=".$itemnumber;
1317 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1318 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1322 sub _SearchItems_build_where_fragment {
1325 my $dbh = C4::Context->dbh;
1328 if (exists($filter->{conjunction})) {
1329 my (@where_strs, @where_args);
1330 foreach my $f (@{ $filter->{filters} }) {
1331 my $fragment = _SearchItems_build_where_fragment($f);
1333 push @where_strs, $fragment->{str};
1334 push @where_args, @{ $fragment->{args} };
1339 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1342 args => \@where_args,
1346 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1347 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1348 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1349 my @operators = qw(= != > < >= <= like);
1350 push @operators, 'not like';
1351 my $field = $filter->{field} // q{};
1352 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1353 my $op = $filter->{operator};
1354 my $query = $filter->{query};
1355 my $ifnull = $filter->{ifnull};
1357 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1358 $op = '='; # default operator
1362 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1364 my $marcsubfield = $2;
1365 my ($kohafield) = $dbh->selectrow_array(q|
1366 SELECT kohafield FROM marc_subfield_structure
1367 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1368 |, undef, $marcfield, $marcsubfield);
1371 $column = $kohafield;
1373 # MARC field is not linked to a DB field so we need to use
1374 # ExtractValue on marcxml from biblio_metadata or
1375 # items.more_subfields_xml, depending on the MARC field.
1378 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1379 if ($marcfield eq $itemfield) {
1380 $sqlfield = 'more_subfields_xml';
1381 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1383 $sqlfield = 'metadata'; # From biblio_metadata
1384 if ($marcfield < 10) {
1385 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1387 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1390 $column = "ExtractValue($sqlfield, '$xpath')";
1396 if ( defined $ifnull ) {
1397 $column = "COALESCE($column, ?)";
1400 if (ref $query eq 'ARRAY') {
1403 } elsif ($op eq '!=') {
1407 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1412 str => "$column $op ?",
1417 if ( defined $ifnull ) {
1418 unshift @{ $where_fragment->{args} }, $ifnull;
1423 return $where_fragment;
1428 my ($items, $total) = SearchItems($filter, $params);
1430 Perform a search among items
1432 $filter is a reference to a hash which can be a filter, or a combination of filters.
1434 A filter has the following keys:
1438 =item * field: the name of a SQL column in table items
1440 =item * query: the value to search in this column
1442 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1446 A combination of filters hash the following keys:
1450 =item * conjunction: 'AND' or 'OR'
1452 =item * filters: array ref of filters
1456 $params is a reference to a hash that can contain the following parameters:
1460 =item * rows: Number of items to return. 0 returns everything (default: 0)
1462 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1465 =item * sortby: A SQL column name in items table to sort on
1467 =item * sortorder: 'ASC' or 'DESC'
1474 my ($filter, $params) = @_;
1478 return unless ref $filter eq 'HASH';
1479 return unless ref $params eq 'HASH';
1481 # Default parameters
1482 $params->{rows} ||= 0;
1483 $params->{page} ||= 1;
1484 $params->{sortby} ||= 'itemnumber';
1485 $params->{sortorder} ||= 'ASC';
1487 my ($where_str, @where_args);
1488 my $where_fragment = _SearchItems_build_where_fragment($filter);
1489 if ($where_fragment) {
1490 $where_str = $where_fragment->{str};
1491 @where_args = @{ $where_fragment->{args} };
1494 my $dbh = C4::Context->dbh;
1496 SELECT SQL_CALC_FOUND_ROWS items.*
1498 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1499 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1500 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1503 if (defined $where_str and $where_str ne '') {
1504 $query .= qq{ AND $where_str };
1507 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1508 push @where_args, C4::Context->preference('marcflavour');
1510 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1511 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1512 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1513 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1514 ? $params->{sortby} : 'itemnumber';
1515 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1516 $query .= qq{ ORDER BY $sortby $sortorder };
1518 my $rows = $params->{rows};
1521 my $offset = $rows * ($params->{page}-1);
1522 $query .= qq { LIMIT ?, ? };
1523 push @limit_args, $offset, $rows;
1526 my $sth = $dbh->prepare($query);
1527 my $rv = $sth->execute(@where_args, @limit_args);
1529 return unless ($rv);
1530 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1532 return ($sth->fetchall_arrayref({}), $total_rows);
1536 =head1 OTHER FUNCTIONS
1540 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1542 Find the given $subfield in the given $tag in the given
1543 MARC::Record $record. If the subfield is found, returns
1544 the (indicators, value) pair; otherwise, (undef, undef) is
1548 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1549 I suggest we export it from this module.
1554 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1557 if ( $tagfield < 10 ) {
1558 if ( $record->field($tagfield) ) {
1559 push @result, $record->field($tagfield)->data();
1564 foreach my $field ( $record->field($tagfield) ) {
1565 my @subfields = $field->subfields();
1566 foreach my $subfield (@subfields) {
1567 if ( @$subfield[0] eq $insubfield ) {
1568 push @result, @$subfield[1];
1569 $indicator = $field->indicator(1) . $field->indicator(2);
1574 return ( $indicator, @result );
1578 =head2 PrepareItemrecordDisplay
1580 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1582 Returns a hash with all the fields for Display a given item data in a template
1584 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1588 sub PrepareItemrecordDisplay {
1590 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1592 my $dbh = C4::Context->dbh;
1593 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1594 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1596 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1597 # a shared data structure. No plugin (including custom ones) should change
1598 # its contents. See also GetMarcStructure.
1599 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1601 # Pick the default location from NewItemsDefaultLocation
1602 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1603 $defaultvalues //= {};
1604 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1607 # return nothing if we don't have found an existing framework.
1608 return q{} unless $tagslib;
1611 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1615 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1617 SELECT authorised_value,lib FROM authorised_values
1620 LEFT JOIN authorised_values_branches ON ( id = av_id )
1625 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1626 $query .= qq{ ORDER BY lib};
1627 my $authorised_values_sth = $dbh->prepare( $query );
1628 foreach my $tag ( sort keys %{$tagslib} ) {
1631 # loop through each subfield
1633 foreach my $subfield (
1634 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1635 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1636 values %{ $tagslib->{$tag} } )
1638 next unless ( $subfield->{'tab'} );
1639 next if ( $subfield->{'tab'} ne "10" );
1641 $subfield_data{tag} = $tag;
1642 $subfield_data{subfield} = $subfield->{subfield};
1643 $subfield_data{countsubfield} = $cntsubf++;
1644 $subfield_data{kohafield} = $subfield->{kohafield};
1645 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1647 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1648 $subfield_data{marc_lib} = $subfield->{lib};
1649 $subfield_data{mandatory} = $subfield->{mandatory};
1650 $subfield_data{repeatable} = $subfield->{repeatable};
1651 $subfield_data{hidden} = "display:none"
1652 if ( ( $subfield->{hidden} > 4 )
1653 || ( $subfield->{hidden} < -4 ) );
1654 my ( $x, $defaultvalue );
1656 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1658 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1659 if ( !defined $defaultvalue ) {
1660 $defaultvalue = q||;
1662 $defaultvalue =~ s/"/"/g;
1663 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1664 my $today_dt = dt_from_string;
1665 my $year = $today_dt->strftime('%Y');
1666 my $shortyear = $today_dt->strftime('%y');
1667 my $month = $today_dt->strftime('%m');
1668 my $day = $today_dt->strftime('%d');
1669 $defaultvalue =~ s/<<YYYY>>/$year/g;
1670 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1671 $defaultvalue =~ s/<<MM>>/$month/g;
1672 $defaultvalue =~ s/<<DD>>/$day/g;
1674 # And <<USER>> with surname (?)
1676 ( C4::Context->userenv
1677 ? C4::Context->userenv->{'surname'}
1678 : "superlibrarian" );
1679 $defaultvalue =~ s/<<USER>>/$username/g;
1682 my $maxlength = $subfield->{maxlength};
1684 # search for itemcallnumber if applicable
1685 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1686 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1687 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1688 my $CNtag = substr( $itemcn_pref, 0, 3 );
1689 next unless my $field = $itemrecord->field($CNtag);
1690 my $CNsubfields = substr( $itemcn_pref, 3 );
1691 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1692 last if $defaultvalue;
1695 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1697 && $defaultvalues->{'callnumber'} ) {
1698 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1699 # if the item record exists, only use default value if the item has no callnumber
1700 $defaultvalue = $defaultvalues->{callnumber};
1701 } elsif ( !$itemrecord and $defaultvalues ) {
1702 # if the item record *doesn't* exists, always use the default value
1703 $defaultvalue = $defaultvalues->{callnumber};
1706 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1708 && $defaultvalues->{'branchcode'} ) {
1709 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1710 $defaultvalue = $defaultvalues->{branchcode};
1713 if ( ( $subfield->{kohafield} eq 'items.location' )
1715 && $defaultvalues->{'location'} ) {
1717 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1718 # if the item record exists, only use default value if the item has no locationr
1719 $defaultvalue = $defaultvalues->{location};
1720 } elsif ( !$itemrecord and $defaultvalues ) {
1721 # if the item record *doesn't* exists, always use the default value
1722 $defaultvalue = $defaultvalues->{location};
1725 if ( $subfield->{authorised_value} ) {
1726 my @authorised_values;
1729 # builds list, depending on authorised value...
1731 if ( $subfield->{'authorised_value'} eq "branches" ) {
1732 if ( ( C4::Context->preference("IndependentBranches") )
1733 && !C4::Context->IsSuperLibrarian() ) {
1734 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1735 $sth->execute( C4::Context->userenv->{branch} );
1736 push @authorised_values, ""
1737 unless ( $subfield->{mandatory} );
1738 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1739 push @authorised_values, $branchcode;
1740 $authorised_lib{$branchcode} = $branchname;
1743 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1745 push @authorised_values, ""
1746 unless ( $subfield->{mandatory} );
1747 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1748 push @authorised_values, $branchcode;
1749 $authorised_lib{$branchcode} = $branchname;
1753 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1754 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1755 $defaultvalue = $defaultvalues->{branchcode};
1759 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1760 my $itemtypes = Koha::ItemTypes->search_with_localization;
1761 push @authorised_values, "";
1762 while ( my $itemtype = $itemtypes->next ) {
1763 push @authorised_values, $itemtype->itemtype;
1764 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1766 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1767 $defaultvalue = $defaultvalues->{'itemtype'};
1771 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1772 push @authorised_values, "";
1774 my $class_sources = GetClassSources();
1775 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1777 foreach my $class_source (sort keys %$class_sources) {
1778 next unless $class_sources->{$class_source}->{'used'} or
1779 ($class_source eq $default_source);
1780 push @authorised_values, $class_source;
1781 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1784 $defaultvalue = $default_source;
1786 #---- "true" authorised value
1788 $authorised_values_sth->execute(
1789 $subfield->{authorised_value},
1790 $branch_limit ? $branch_limit : ()
1792 push @authorised_values, "";
1793 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1794 push @authorised_values, $value;
1795 $authorised_lib{$value} = $lib;
1798 $subfield_data{marc_value} = {
1800 values => \@authorised_values,
1801 default => $defaultvalue // q{},
1802 labels => \%authorised_lib,
1804 } elsif ( $subfield->{value_builder} ) {
1806 require Koha::FrameworkPlugin;
1807 my $plugin = Koha::FrameworkPlugin->new({
1808 name => $subfield->{value_builder},
1811 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1812 $plugin->build( $pars );
1813 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1814 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1816 if( !$plugin->errstr ) {
1817 #TODO Move html to template; see report 12176/13397
1818 my $tab= $plugin->noclick? '-1': '';
1819 my $class= $plugin->noclick? ' disabled': '';
1820 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1821 $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;
1823 warn $plugin->errstr;
1824 $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
1827 elsif ( $tag eq '' ) { # it's an hidden field
1828 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1830 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1831 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1833 elsif ( length($defaultvalue) > 100
1834 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1835 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1836 or (C4::Context->preference("marcflavour") eq "MARC21" and
1837 500 <= $tag && $tag < 600 )
1839 # oversize field (textarea)
1840 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1842 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1844 push( @loop_data, \%subfield_data );
1849 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1850 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1853 'itemtagfield' => $itemtagfield,
1854 'itemtagsubfield' => $itemtagsubfield,
1855 'itemnumber' => $itemnumber,
1856 'iteminformation' => \@loop_data
1860 sub ToggleNewStatus {
1861 my ( $params ) = @_;
1862 my @rules = @{ $params->{rules} };
1863 my $report_only = $params->{report_only};
1865 my $dbh = C4::Context->dbh;
1867 my @item_columns = map { "items.$_" } Koha::Items->columns;
1868 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1870 for my $rule ( @rules ) {
1871 my $age = $rule->{age};
1872 my $conditions = $rule->{conditions};
1873 my $substitutions = $rule->{substitutions};
1874 foreach ( @$substitutions ) {
1875 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1882 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1885 for my $condition ( @$conditions ) {
1887 grep { $_ eq $condition->{field} } @item_columns
1888 or grep { $_ eq $condition->{field} } @biblioitem_columns
1890 if ( $condition->{value} =~ /\|/ ) {
1891 my @values = split /\|/, $condition->{value};
1892 $query .= qq| AND $condition->{field} IN (|
1893 . join( ',', ('?') x scalar @values )
1895 push @params, @values;
1897 $query .= qq| AND $condition->{field} = ?|;
1898 push @params, $condition->{value};
1902 if ( defined $age ) {
1903 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1906 my $sth = $dbh->prepare($query);
1907 $sth->execute( @params );
1908 while ( my $values = $sth->fetchrow_hashref ) {
1909 my $biblionumber = $values->{biblionumber};
1910 my $itemnumber = $values->{itemnumber};
1911 my $item = Koha::Items->find($itemnumber);
1912 for my $substitution ( @$substitutions ) {
1913 my $field = $substitution->{item_field};
1914 my $value = $substitution->{value};
1915 next unless $substitution->{field};
1916 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1917 $item->$field($value);
1918 push @{ $report->{$itemnumber} }, $substitution;
1920 $item->store unless $report_only;