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 ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
308 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
310 # Retrieving the values for the fields that are not linked
311 my @mapped_fields = Koha::MarcSubfieldStructures->search(
313 frameworkcode => $frameworkcode,
314 kohafield => { -like => "items.%" }
316 )->get_column('kohafield');
317 for my $c ( $item_object->_result->result_source->columns ) {
318 next if grep { "items.$c" eq $_ } @mapped_fields;
319 $item->{$c} = $item_object->$c;
322 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
323 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
324 $item->{itemnumber} = $itemnumber;
325 $item->{biblionumber} = $biblionumber;
327 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
328 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
329 $item_object = $item_object->set_or_blank($item);
330 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
332 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
334 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
335 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
336 $item_object->store({ skip_record_index => $params->{skip_record_index} });
338 return $item_object->unblessed;
341 =head2 ModItemTransfer
343 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
345 Marks an item as being transferred from one branch to another and records the trigger.
347 The last optional parameter allows for passing skip_record_index through to the items store call.
351 sub ModItemTransfer {
352 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
354 my $dbh = C4::Context->dbh;
355 my $item = Koha::Items->find( $itemnumber );
357 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
358 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
359 # will have been preceded by a check of branch transfer limits)
360 my $to_library = Koha::Libraries->find($tobranch);
361 my $transfer = $item->request_transfer(
370 # Immediately set the item to in transit if it is checked in
371 if ( !$item->checkout ) {
372 $item->holdingbranch($frombranch)->store(
375 skip_record_index => $params->{skip_record_index}
384 =head2 ModDateLastSeen
386 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
388 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
389 C<$itemnumber> is the item number
390 C<$leave_item_lost> determines if a lost item will be found or remain lost
392 The last optional parameter allows for passing skip_record_index through to the items store call.
396 sub ModDateLastSeen {
397 my ( $itemnumber, $leave_item_lost, $params ) = @_;
399 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
401 my $item = Koha::Items->find($itemnumber);
402 $item->datelastseen($today);
403 $item->itemlost(0) unless $leave_item_lost;
404 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
407 =head2 CheckItemPreSave
409 my $item_ref = TransformMarcToKoha($marc, 'items');
411 my %errors = CheckItemPreSave($item_ref);
412 if (exists $errors{'duplicate_barcode'}) {
413 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
414 } elsif (exists $errors{'invalid_homebranch'}) {
415 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
416 } elsif (exists $errors{'invalid_holdingbranch'}) {
417 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
422 Given a hashref containing item fields, determine if it can be
423 inserted or updated in the database. Specifically, checks for
424 database integrity issues, and returns a hash containing any
425 of the following keys, if applicable.
429 =item duplicate_barcode
431 Barcode, if it duplicates one already found in the database.
433 =item invalid_homebranch
435 Home branch, if not defined in branches table.
437 =item invalid_holdingbranch
439 Holding branch, if not defined in branches table.
443 This function does NOT implement any policy-related checks,
444 e.g., whether current operator is allowed to save an
445 item that has a given branch code.
449 sub CheckItemPreSave {
450 my $item_ref = shift;
454 # check for duplicate barcode
455 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
456 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
457 if ($existing_item) {
458 if (!exists $item_ref->{'itemnumber'} # new item
459 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
460 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
465 # check for valid home branch
466 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
467 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
468 unless (defined $home_library) {
469 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
473 # check for valid holding branch
474 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
475 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
476 unless (defined $holding_library) {
477 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
485 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
487 The following functions provide various ways of
488 getting an item record, a set of item records, or
489 lists of authorized values for certain item fields.
493 =head2 GetItemsForInventory
495 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
496 minlocation => $minlocation,
497 maxlocation => $maxlocation,
498 location => $location,
499 ignoreissued => $ignoreissued,
500 datelastseen => $datelastseen,
501 branchcode => $branchcode,
505 statushash => $statushash,
506 itemtypes => \@itemsarray,
509 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
511 The sub returns a reference to a list of hashes, each containing
512 itemnumber, author, title, barcode, item callnumber, and date last
513 seen. It is ordered by callnumber then title.
515 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
516 the datelastseen can be used to specify that you want to see items not seen since a past date only.
517 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
518 $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.
520 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
524 sub GetItemsForInventory {
525 my ( $parameters ) = @_;
526 my $minlocation = $parameters->{'minlocation'} // '';
527 my $maxlocation = $parameters->{'maxlocation'} // '';
528 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
529 my $location = $parameters->{'location'} // '';
530 my $itemtype = $parameters->{'itemtype'} // '';
531 my $ignoreissued = $parameters->{'ignoreissued'} // '';
532 my $datelastseen = $parameters->{'datelastseen'} // '';
533 my $branchcode = $parameters->{'branchcode'} // '';
534 my $branch = $parameters->{'branch'} // '';
535 my $offset = $parameters->{'offset'} // '';
536 my $size = $parameters->{'size'} // '';
537 my $statushash = $parameters->{'statushash'} // '';
538 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
539 my $itemtypes = $parameters->{'itemtypes'} || [];
541 my $dbh = C4::Context->dbh;
542 my ( @bind_params, @where_strings );
544 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
545 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
547 my $select_columns = q{
548 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
550 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
553 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
554 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
557 for my $authvfield (keys %$statushash){
558 if ( scalar @{$statushash->{$authvfield}} > 0 ){
559 my $joinedvals = join ',', @{$statushash->{$authvfield}};
560 push @where_strings, "$authvfield in (" . $joinedvals . ")";
566 push @where_strings, 'items.cn_sort >= ?';
567 push @bind_params, $min_cnsort;
571 push @where_strings, 'items.cn_sort <= ?';
572 push @bind_params, $max_cnsort;
576 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
577 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
578 push @bind_params, $datelastseen;
582 push @where_strings, 'items.location = ?';
583 push @bind_params, $location;
587 if($branch eq "homebranch"){
588 push @where_strings, 'items.homebranch = ?';
590 push @where_strings, 'items.holdingbranch = ?';
592 push @bind_params, $branchcode;
596 push @where_strings, 'biblioitems.itemtype = ?';
597 push @bind_params, $itemtype;
599 if ( $ignoreissued) {
600 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
601 push @where_strings, 'issues.date_due IS NULL';
604 if ( $ignore_waiting_holds ) {
605 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
606 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
610 my $itemtypes_str = join ', ', @$itemtypes;
611 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
614 if ( @where_strings ) {
616 $query .= join ' AND ', @where_strings;
618 my $count_query = $select_count . $query;
619 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
620 $query .= " LIMIT $offset, $size" if ($offset and $size);
621 $query = $select_columns . $query;
622 my $sth = $dbh->prepare($query);
623 $sth->execute( @bind_params );
626 my $tmpresults = $sth->fetchall_arrayref({});
627 $sth = $dbh->prepare( $count_query );
628 $sth->execute( @bind_params );
629 my ($iTotalRecords) = $sth->fetchrow_array();
631 my @avs = Koha::AuthorisedValues->search(
632 { 'marc_subfield_structures.kohafield' => { '>' => '' },
633 'me.authorised_value' => { '>' => '' },
635 { join => { category => 'marc_subfield_structures' },
636 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
637 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
638 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
642 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
644 foreach my $row (@$tmpresults) {
647 foreach (keys %$row) {
650 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
653 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
659 return (\@results, $iTotalRecords);
664 @results = GetItemsInfo($biblionumber);
666 Returns information about items with the given biblionumber.
668 C<GetItemsInfo> returns a list of references-to-hash. Each element
669 contains a number of keys. Most of them are attributes from the
670 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
671 Koha database. Other keys include:
675 =item C<$data-E<gt>{branchname}>
677 The name (not the code) of the branch to which the book belongs.
679 =item C<$data-E<gt>{datelastseen}>
681 This is simply C<items.datelastseen>, except that while the date is
682 stored in YYYY-MM-DD format in the database, here it is converted to
683 DD/MM/YYYY format. A NULL date is returned as C<//>.
685 =item C<$data-E<gt>{datedue}>
687 =item C<$data-E<gt>{class}>
689 This is the concatenation of C<biblioitems.classification>, the book's
690 Dewey code, and C<biblioitems.subclass>.
692 =item C<$data-E<gt>{ocount}>
694 I think this is the number of copies of the book available.
696 =item C<$data-E<gt>{order}>
698 If this is set, it is set to C<One Order>.
705 my ( $biblionumber ) = @_;
706 my $dbh = C4::Context->dbh;
707 require C4::Languages;
708 my $language = C4::Languages::getlanguage();
714 biblioitems.itemtype,
717 biblioitems.publicationyear,
718 biblioitems.publishercode,
719 biblioitems.volumedate,
720 biblioitems.volumedesc,
723 items.notforloan as itemnotforloan,
724 issues.borrowernumber,
725 issues.date_due as datedue,
726 issues.onsite_checkout,
727 borrowers.cardnumber,
730 borrowers.branchcode as bcode,
732 serial.publisheddate,
733 itemtypes.description,
734 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
735 itemtypes.notforloan as notforloan_per_itemtype,
739 holding.opac_info as holding_branch_opac_info,
740 home.opac_info as home_branch_opac_info,
741 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
743 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
744 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
745 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
746 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
747 LEFT JOIN issues USING (itemnumber)
748 LEFT JOIN borrowers USING (borrowernumber)
749 LEFT JOIN serialitems USING (itemnumber)
750 LEFT JOIN serial USING (serialid)
751 LEFT JOIN itemtypes ON itemtypes.itemtype = "
752 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
754 LEFT JOIN tmp_holdsqueue USING (itemnumber)
755 LEFT JOIN localization ON itemtypes.itemtype = localization.code
756 AND localization.entity = 'itemtypes'
757 AND localization.lang = ?
760 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
761 my $sth = $dbh->prepare($query);
762 $sth->execute($language, $biblionumber);
767 my $userenv = C4::Context->userenv;
768 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
769 while ( my $data = $sth->fetchrow_hashref ) {
770 if ( $data->{borrowernumber} && $want_not_same_branch) {
771 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
774 $serial ||= $data->{'serial'};
777 # get notforloan complete status if applicable
778 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
779 $data->{notforloanvalue} = $descriptions->{lib} // '';
780 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
782 # get restricted status and description if applicable
783 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
784 $data->{restrictedvalue} = $descriptions->{lib} // '';
785 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
787 # my stack procedures
788 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
789 $data->{stack} = $descriptions->{lib} // '';
791 # Find the last 3 people who borrowed this item.
792 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
794 AND old_issues.borrowernumber = borrowers.borrowernumber
795 ORDER BY returndate DESC
797 $sth2->execute($data->{'itemnumber'});
799 while (my $data2 = $sth2->fetchrow_hashref()) {
800 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
801 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
802 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
806 $results[$i] = $data;
811 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
815 =head2 GetItemsLocationInfo
817 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
819 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
821 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
825 =item C<$data-E<gt>{homebranch}>
827 Branch Name of the item's homebranch
829 =item C<$data-E<gt>{holdingbranch}>
831 Branch Name of the item's holdingbranch
833 =item C<$data-E<gt>{location}>
835 Item's shelving location code
837 =item C<$data-E<gt>{location_intranet}>
839 The intranet description for the Shelving Location as set in authorised_values 'LOC'
841 =item C<$data-E<gt>{location_opac}>
843 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
846 =item C<$data-E<gt>{itemcallnumber}>
848 Item's itemcallnumber
850 =item C<$data-E<gt>{cn_sort}>
852 Item's call number normalized for sorting
858 sub GetItemsLocationInfo {
859 my $biblionumber = shift;
862 my $dbh = C4::Context->dbh;
863 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
864 location, itemcallnumber, cn_sort
865 FROM items, branches as a, branches as b
866 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
868 ORDER BY cn_sort ASC";
869 my $sth = $dbh->prepare($query);
870 $sth->execute($biblionumber);
872 while ( my $data = $sth->fetchrow_hashref ) {
873 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
874 $av = $av->count ? $av->next : undef;
875 $data->{location_intranet} = $av ? $av->lib : '';
876 $data->{location_opac} = $av ? $av->opac_description : '';
877 push @results, $data;
882 =head2 GetHostItemsInfo
884 $hostiteminfo = GetHostItemsInfo($hostfield);
885 Returns the iteminfo for items linked to records via a host field
889 sub GetHostItemsInfo {
893 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
894 return @returnitemsInfo;
898 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
899 C4::Context->preference('marcflavour') eq 'NORMARC') {
900 @fields = $record->field('773');
901 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
902 @fields = $record->field('461');
905 foreach my $hostfield ( @fields ) {
906 my $hostbiblionumber = $hostfield->subfield("0");
907 my $linkeditemnumber = $hostfield->subfield("9");
908 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
909 foreach my $hostitemInfo (@hostitemInfos) {
910 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
911 push @returnitemsInfo, $hostitemInfo;
916 return @returnitemsInfo;
919 =head2 get_hostitemnumbers_of
921 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
923 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
925 Return a reference on a hash where key is a biblionumber and values are
926 references on array of itemnumbers.
931 sub get_hostitemnumbers_of {
932 my ($biblionumber) = @_;
934 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
938 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
939 return unless $marcrecord;
941 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
943 my $marcflavor = C4::Context->preference('marcflavour');
944 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
949 elsif ( $marcflavor eq 'UNIMARC' ) {
955 foreach my $hostfield ( $marcrecord->field($tag) ) {
956 my $hostbiblionumber = $hostfield->subfield($biblio_s);
957 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
958 my $linkeditemnumber = $hostfield->subfield($item_s);
959 if ( ! $linkeditemnumber ) {
960 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
963 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
964 push @returnhostitemnumbers, $linkeditemnumber
968 return @returnhostitemnumbers;
971 =head2 GetHiddenItemnumbers
973 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
975 Given a list of items it checks which should be hidden from the OPAC given
976 the current configuration. Returns a list of itemnumbers corresponding to
977 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
982 sub GetHiddenItemnumbers {
984 my $items = $params->{items};
985 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
986 foreach my $except (split(/\|/, $exceptions)){
987 if ($params->{'borcat'} eq $except){
988 return; # we don't hide anything for this borrower category
994 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
999 my $dbh = C4::Context->dbh;
1002 foreach my $item (@$items) {
1004 # We check each rule
1005 foreach my $field (keys %$hidingrules) {
1007 if (exists $item->{$field}) {
1008 $val = $item->{$field};
1011 my $query = "SELECT $field from items where itemnumber = ?";
1012 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1014 $val = '' unless defined $val;
1016 # If the results matches the values in the yaml file
1017 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1019 # We add the itemnumber to the list
1020 push @resultitems, $item->{'itemnumber'};
1022 # If at least one rule matched for an item, no need to test the others
1027 return @resultitems;
1030 =head1 LIMITED USE FUNCTIONS
1032 The following functions, while part of the public API,
1033 are not exported. This is generally because they are
1034 meant to be used by only one script for a specific
1035 purpose, and should not be used in any other context
1036 without careful thought.
1042 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1044 Returns MARC::Record of the item passed in parameter.
1045 This function is meant for use only in C<cataloguing/additem.pl>,
1046 where it is needed to support that script's MARC-like
1052 my ( $biblionumber, $itemnumber ) = @_;
1054 # GetMarcItem has been revised so that it does the following:
1055 # 1. Gets the item information from the items table.
1056 # 2. Converts it to a MARC field for storage in the bib record.
1058 # The previous behavior was:
1059 # 1. Get the bib record.
1060 # 2. Return the MARC tag corresponding to the item record.
1062 # The difference is that one treats the items row as authoritative,
1063 # while the other treats the MARC representation as authoritative
1064 # under certain circumstances.
1066 my $item = Koha::Items->find($itemnumber) or return;
1068 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1069 # Also, don't emit a subfield if the underlying field is blank.
1071 return Item2Marc($item->unblessed, $biblionumber);
1075 my ($itemrecord,$biblionumber)=@_;
1078 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1079 } keys %{ $itemrecord }
1081 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1082 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1083 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1084 "items.itemnumber", $framework,
1087 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1088 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1089 foreach my $field ($itemmarc->field($itemtag)){
1090 $field->add_subfields(@$unlinked_item_subfields);
1096 =head1 PRIVATE FUNCTIONS AND VARIABLES
1098 The following functions are not meant to be called
1099 directly, but are documented in order to explain
1100 the inner workings of C<C4::Items>.
1104 =head2 MoveItemFromBiblio
1106 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1108 Moves an item from a biblio to another
1110 Returns undef if the move failed or the biblionumber of the destination record otherwise
1114 sub MoveItemFromBiblio {
1115 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1116 my $dbh = C4::Context->dbh;
1117 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1118 SELECT biblioitemnumber
1120 WHERE biblionumber = ?
1121 |, undef, $tobiblio );
1122 my $return = $dbh->do(q|
1124 SET biblioitemnumber = ?,
1126 WHERE itemnumber = ?
1127 AND biblionumber = ?
1128 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1130 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1131 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1132 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1133 # Checking if the item we want to move is in an order
1134 require C4::Acquisition;
1135 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1137 # Replacing the biblionumber within the order if necessary
1138 $order->{'biblionumber'} = $tobiblio;
1139 C4::Acquisition::ModOrder($order);
1142 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1143 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1146 SET biblionumber = ?
1147 WHERE itemnumber = ?
1148 |, undef, $tobiblio, $itemnumber );
1155 =head2 _marc_from_item_hash
1157 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1159 Given an item hash representing a complete item record,
1160 create a C<MARC::Record> object containing an embedded
1161 tag representing that item.
1163 The third, optional parameter C<$unlinked_item_subfields> is
1164 an arrayref of subfields (not mapped to C<items> fields per the
1165 framework) to be added to the MARC representation
1170 sub _marc_from_item_hash {
1172 my $frameworkcode = shift;
1173 my $unlinked_item_subfields;
1175 $unlinked_item_subfields = shift;
1178 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1179 # Also, don't emit a subfield if the underlying field is blank.
1180 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1181 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1182 : () } keys %{ $item } };
1184 my $item_marc = MARC::Record->new();
1185 foreach my $item_field ( keys %{$mungeditem} ) {
1186 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1187 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1188 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1189 foreach my $value (@values){
1190 if ( my $field = $item_marc->field($tag) ) {
1191 $field->add_subfields( $subfield => $value );
1193 my $add_subfields = [];
1194 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1195 $add_subfields = $unlinked_item_subfields;
1197 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1205 =head2 _repack_item_errors
1207 Add an error message hash generated by C<CheckItemPreSave>
1208 to a list of errors.
1212 sub _repack_item_errors {
1213 my $item_sequence_num = shift;
1214 my $item_ref = shift;
1215 my $error_ref = shift;
1217 my @repacked_errors = ();
1219 foreach my $error_code (sort keys %{ $error_ref }) {
1220 my $repacked_error = {};
1221 $repacked_error->{'item_sequence'} = $item_sequence_num;
1222 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1223 $repacked_error->{'error_code'} = $error_code;
1224 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1225 push @repacked_errors, $repacked_error;
1228 return @repacked_errors;
1231 =head2 _get_unlinked_item_subfields
1233 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1237 sub _get_unlinked_item_subfields {
1238 my $original_item_marc = shift;
1239 my $frameworkcode = shift;
1241 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1243 # assume that this record has only one field, and that that
1244 # field contains only the item information
1246 my @fields = $original_item_marc->fields();
1247 if ($#fields > -1) {
1248 my $field = $fields[0];
1249 my $tag = $field->tag();
1250 foreach my $subfield ($field->subfields()) {
1251 if (defined $subfield->[1] and
1252 $subfield->[1] ne '' and
1253 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1254 push @$subfields, $subfield->[0] => $subfield->[1];
1261 =head2 _get_unlinked_subfields_xml
1263 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1267 sub _get_unlinked_subfields_xml {
1268 my $unlinked_item_subfields = shift;
1271 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1272 my $marc = MARC::Record->new();
1273 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1274 # used in the framework
1275 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1276 $marc->encoding("UTF-8");
1277 $xml = $marc->as_xml("USMARC");
1283 =head2 _parse_unlinked_item_subfields_from_xml
1285 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1289 sub _parse_unlinked_item_subfields_from_xml {
1291 require C4::Charset;
1292 return unless defined $xml and $xml ne "";
1293 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1294 my $unlinked_subfields = [];
1295 my @fields = $marc->fields();
1296 if ($#fields > -1) {
1297 foreach my $subfield ($fields[0]->subfields()) {
1298 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1301 return $unlinked_subfields;
1304 =head2 GetAnalyticsCount
1306 $count= &GetAnalyticsCount($itemnumber)
1308 counts Usage of itemnumber in Analytical bibliorecords.
1312 sub GetAnalyticsCount {
1313 my ($itemnumber) = @_;
1315 ### ZOOM search here
1317 $query= "hi=".$itemnumber;
1318 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1319 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1323 sub _SearchItems_build_where_fragment {
1326 my $dbh = C4::Context->dbh;
1329 if (exists($filter->{conjunction})) {
1330 my (@where_strs, @where_args);
1331 foreach my $f (@{ $filter->{filters} }) {
1332 my $fragment = _SearchItems_build_where_fragment($f);
1334 push @where_strs, $fragment->{str};
1335 push @where_args, @{ $fragment->{args} };
1340 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1343 args => \@where_args,
1347 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1348 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1349 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1350 my @operators = qw(= != > < >= <= like);
1351 push @operators, 'not like';
1352 my $field = $filter->{field} // q{};
1353 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1354 my $op = $filter->{operator};
1355 my $query = $filter->{query};
1356 my $ifnull = $filter->{ifnull};
1358 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1359 $op = '='; # default operator
1363 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1365 my $marcsubfield = $2;
1366 my ($kohafield) = $dbh->selectrow_array(q|
1367 SELECT kohafield FROM marc_subfield_structure
1368 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1369 |, undef, $marcfield, $marcsubfield);
1372 $column = $kohafield;
1374 # MARC field is not linked to a DB field so we need to use
1375 # ExtractValue on marcxml from biblio_metadata or
1376 # items.more_subfields_xml, depending on the MARC field.
1379 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1380 if ($marcfield eq $itemfield) {
1381 $sqlfield = 'more_subfields_xml';
1382 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1384 $sqlfield = 'metadata'; # From biblio_metadata
1385 if ($marcfield < 10) {
1386 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1388 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1391 $column = "ExtractValue($sqlfield, '$xpath')";
1397 if ( defined $ifnull ) {
1398 $column = "COALESCE($column, ?)";
1401 if (ref $query eq 'ARRAY') {
1404 } elsif ($op eq '!=') {
1408 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1413 str => "$column $op ?",
1418 if ( defined $ifnull ) {
1419 unshift @{ $where_fragment->{args} }, $ifnull;
1424 return $where_fragment;
1429 my ($items, $total) = SearchItems($filter, $params);
1431 Perform a search among items
1433 $filter is a reference to a hash which can be a filter, or a combination of filters.
1435 A filter has the following keys:
1439 =item * field: the name of a SQL column in table items
1441 =item * query: the value to search in this column
1443 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1447 A combination of filters hash the following keys:
1451 =item * conjunction: 'AND' or 'OR'
1453 =item * filters: array ref of filters
1457 $params is a reference to a hash that can contain the following parameters:
1461 =item * rows: Number of items to return. 0 returns everything (default: 0)
1463 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1466 =item * sortby: A SQL column name in items table to sort on
1468 =item * sortorder: 'ASC' or 'DESC'
1475 my ($filter, $params) = @_;
1479 return unless ref $filter eq 'HASH';
1480 return unless ref $params eq 'HASH';
1482 # Default parameters
1483 $params->{rows} ||= 0;
1484 $params->{page} ||= 1;
1485 $params->{sortby} ||= 'itemnumber';
1486 $params->{sortorder} ||= 'ASC';
1488 my ($where_str, @where_args);
1489 my $where_fragment = _SearchItems_build_where_fragment($filter);
1490 if ($where_fragment) {
1491 $where_str = $where_fragment->{str};
1492 @where_args = @{ $where_fragment->{args} };
1495 my $dbh = C4::Context->dbh;
1497 SELECT SQL_CALC_FOUND_ROWS items.*
1499 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1500 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1501 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1504 if (defined $where_str and $where_str ne '') {
1505 $query .= qq{ AND $where_str };
1508 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1509 push @where_args, C4::Context->preference('marcflavour');
1511 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1512 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1513 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1514 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1515 ? $params->{sortby} : 'itemnumber';
1516 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1517 $query .= qq{ ORDER BY $sortby $sortorder };
1519 my $rows = $params->{rows};
1522 my $offset = $rows * ($params->{page}-1);
1523 $query .= qq { LIMIT ?, ? };
1524 push @limit_args, $offset, $rows;
1527 my $sth = $dbh->prepare($query);
1528 my $rv = $sth->execute(@where_args, @limit_args);
1530 return unless ($rv);
1531 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1533 return ($sth->fetchall_arrayref({}), $total_rows);
1537 =head1 OTHER FUNCTIONS
1541 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1543 Find the given $subfield in the given $tag in the given
1544 MARC::Record $record. If the subfield is found, returns
1545 the (indicators, value) pair; otherwise, (undef, undef) is
1549 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1550 I suggest we export it from this module.
1555 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1558 if ( $tagfield < 10 ) {
1559 if ( $record->field($tagfield) ) {
1560 push @result, $record->field($tagfield)->data();
1565 foreach my $field ( $record->field($tagfield) ) {
1566 my @subfields = $field->subfields();
1567 foreach my $subfield (@subfields) {
1568 if ( @$subfield[0] eq $insubfield ) {
1569 push @result, @$subfield[1];
1570 $indicator = $field->indicator(1) . $field->indicator(2);
1575 return ( $indicator, @result );
1579 =head2 PrepareItemrecordDisplay
1581 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1583 Returns a hash with all the fields for Display a given item data in a template
1585 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1589 sub PrepareItemrecordDisplay {
1591 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1593 my $dbh = C4::Context->dbh;
1594 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1595 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1597 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1598 # a shared data structure. No plugin (including custom ones) should change
1599 # its contents. See also GetMarcStructure.
1600 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1602 # Pick the default location from NewItemsDefaultLocation
1603 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1604 $defaultvalues //= {};
1605 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1608 # return nothing if we don't have found an existing framework.
1609 return q{} unless $tagslib;
1612 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1616 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1618 SELECT authorised_value,lib FROM authorised_values
1621 LEFT JOIN authorised_values_branches ON ( id = av_id )
1626 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1627 $query .= qq{ ORDER BY lib};
1628 my $authorised_values_sth = $dbh->prepare( $query );
1629 foreach my $tag ( sort keys %{$tagslib} ) {
1632 # loop through each subfield
1634 foreach my $subfield (
1635 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1636 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1637 values %{ $tagslib->{$tag} } )
1639 next unless ( $subfield->{'tab'} );
1640 next if ( $subfield->{'tab'} ne "10" );
1642 $subfield_data{tag} = $tag;
1643 $subfield_data{subfield} = $subfield->{subfield};
1644 $subfield_data{countsubfield} = $cntsubf++;
1645 $subfield_data{kohafield} = $subfield->{kohafield};
1646 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1648 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1649 $subfield_data{marc_lib} = $subfield->{lib};
1650 $subfield_data{mandatory} = $subfield->{mandatory};
1651 $subfield_data{repeatable} = $subfield->{repeatable};
1652 $subfield_data{hidden} = "display:none"
1653 if ( ( $subfield->{hidden} > 4 )
1654 || ( $subfield->{hidden} < -4 ) );
1655 my ( $x, $defaultvalue );
1657 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1659 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1660 if ( !defined $defaultvalue ) {
1661 $defaultvalue = q||;
1663 $defaultvalue =~ s/"/"/g;
1664 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1665 my $today_dt = dt_from_string;
1666 my $year = $today_dt->strftime('%Y');
1667 my $shortyear = $today_dt->strftime('%y');
1668 my $month = $today_dt->strftime('%m');
1669 my $day = $today_dt->strftime('%d');
1670 $defaultvalue =~ s/<<YYYY>>/$year/g;
1671 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1672 $defaultvalue =~ s/<<MM>>/$month/g;
1673 $defaultvalue =~ s/<<DD>>/$day/g;
1675 # And <<USER>> with surname (?)
1677 ( C4::Context->userenv
1678 ? C4::Context->userenv->{'surname'}
1679 : "superlibrarian" );
1680 $defaultvalue =~ s/<<USER>>/$username/g;
1683 my $maxlength = $subfield->{maxlength};
1685 # search for itemcallnumber if applicable
1686 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1687 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1688 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1689 my $CNtag = substr( $itemcn_pref, 0, 3 );
1690 next unless my $field = $itemrecord->field($CNtag);
1691 my $CNsubfields = substr( $itemcn_pref, 3 );
1692 $CNsubfields = undef if $CNsubfields eq '';
1693 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1694 last if $defaultvalue;
1697 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1699 && $defaultvalues->{'callnumber'} ) {
1700 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1701 # if the item record exists, only use default value if the item has no callnumber
1702 $defaultvalue = $defaultvalues->{callnumber};
1703 } elsif ( !$itemrecord and $defaultvalues ) {
1704 # if the item record *doesn't* exists, always use the default value
1705 $defaultvalue = $defaultvalues->{callnumber};
1708 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1710 && $defaultvalues->{'branchcode'} ) {
1711 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1712 $defaultvalue = $defaultvalues->{branchcode};
1715 if ( ( $subfield->{kohafield} eq 'items.location' )
1717 && $defaultvalues->{'location'} ) {
1719 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1720 # if the item record exists, only use default value if the item has no locationr
1721 $defaultvalue = $defaultvalues->{location};
1722 } elsif ( !$itemrecord and $defaultvalues ) {
1723 # if the item record *doesn't* exists, always use the default value
1724 $defaultvalue = $defaultvalues->{location};
1727 if ( $subfield->{authorised_value} ) {
1728 my @authorised_values;
1731 # builds list, depending on authorised value...
1733 if ( $subfield->{'authorised_value'} eq "branches" ) {
1734 if ( ( C4::Context->preference("IndependentBranches") )
1735 && !C4::Context->IsSuperLibrarian() ) {
1736 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1737 $sth->execute( C4::Context->userenv->{branch} );
1738 push @authorised_values, ""
1739 unless ( $subfield->{mandatory} );
1740 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1741 push @authorised_values, $branchcode;
1742 $authorised_lib{$branchcode} = $branchname;
1745 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1747 push @authorised_values, ""
1748 unless ( $subfield->{mandatory} );
1749 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1750 push @authorised_values, $branchcode;
1751 $authorised_lib{$branchcode} = $branchname;
1755 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1756 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1757 $defaultvalue = $defaultvalues->{branchcode};
1761 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1762 my $itemtypes = Koha::ItemTypes->search_with_localization;
1763 push @authorised_values, "";
1764 while ( my $itemtype = $itemtypes->next ) {
1765 push @authorised_values, $itemtype->itemtype;
1766 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1768 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1769 $defaultvalue = $defaultvalues->{'itemtype'};
1773 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1774 push @authorised_values, "";
1776 my $class_sources = GetClassSources();
1777 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1779 foreach my $class_source (sort keys %$class_sources) {
1780 next unless $class_sources->{$class_source}->{'used'} or
1781 ($class_source eq $default_source);
1782 push @authorised_values, $class_source;
1783 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1786 $defaultvalue = $default_source;
1788 #---- "true" authorised value
1790 $authorised_values_sth->execute(
1791 $subfield->{authorised_value},
1792 $branch_limit ? $branch_limit : ()
1794 push @authorised_values, "";
1795 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1796 push @authorised_values, $value;
1797 $authorised_lib{$value} = $lib;
1800 $subfield_data{marc_value} = {
1802 values => \@authorised_values,
1803 default => $defaultvalue // q{},
1804 labels => \%authorised_lib,
1806 } elsif ( $subfield->{value_builder} ) {
1808 require Koha::FrameworkPlugin;
1809 my $plugin = Koha::FrameworkPlugin->new({
1810 name => $subfield->{value_builder},
1813 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1814 $plugin->build( $pars );
1815 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1816 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1818 if( !$plugin->errstr ) {
1819 #TODO Move html to template; see report 12176/13397
1820 my $tab= $plugin->noclick? '-1': '';
1821 my $class= $plugin->noclick? ' disabled': '';
1822 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1823 $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;
1825 warn $plugin->errstr;
1826 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
1829 elsif ( $tag eq '' ) { # it's an hidden field
1830 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1832 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1833 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1835 elsif ( length($defaultvalue) > 100
1836 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1837 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1838 or (C4::Context->preference("marcflavour") eq "MARC21" and
1839 500 <= $tag && $tag < 600 )
1841 # oversize field (textarea)
1842 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1844 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1846 push( @loop_data, \%subfield_data );
1851 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1852 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1855 'itemtagfield' => $itemtagfield,
1856 'itemtagsubfield' => $itemtagsubfield,
1857 'itemnumber' => $itemnumber,
1858 'iteminformation' => \@loop_data
1862 sub ToggleNewStatus {
1863 my ( $params ) = @_;
1864 my @rules = @{ $params->{rules} };
1865 my $report_only = $params->{report_only};
1867 my $dbh = C4::Context->dbh;
1869 my @item_columns = map { "items.$_" } Koha::Items->columns;
1870 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1872 for my $rule ( @rules ) {
1873 my $age = $rule->{age};
1874 my $conditions = $rule->{conditions};
1875 my $substitutions = $rule->{substitutions};
1876 foreach ( @$substitutions ) {
1877 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1884 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1887 for my $condition ( @$conditions ) {
1889 grep { $_ eq $condition->{field} } @item_columns
1890 or grep { $_ eq $condition->{field} } @biblioitem_columns
1892 if ( $condition->{value} =~ /\|/ ) {
1893 my @values = split /\|/, $condition->{value};
1894 $query .= qq| AND $condition->{field} IN (|
1895 . join( ',', ('?') x scalar @values )
1897 push @params, @values;
1899 $query .= qq| AND $condition->{field} = ?|;
1900 push @params, $condition->{value};
1904 if ( defined $age ) {
1905 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1908 my $sth = $dbh->prepare($query);
1909 $sth->execute( @params );
1910 while ( my $values = $sth->fetchrow_hashref ) {
1911 my $biblionumber = $values->{biblionumber};
1912 my $itemnumber = $values->{itemnumber};
1913 my $item = Koha::Items->find($itemnumber);
1914 for my $substitution ( @$substitutions ) {
1915 my $field = $substitution->{item_field};
1916 my $value = $substitution->{value};
1917 next unless $substitution->{field};
1918 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1919 $item->$field($value);
1920 push @{ $report->{$itemnumber} }, $substitution;
1922 $item->store unless $report_only;