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
60 use List::MoreUtils qw(any);
62 use DateTime::Format::MySQL;
63 use Data::Dumper; # used as part of logging item record changes, not just for
64 # debugging; so please don't remove this
66 use Koha::AuthorisedValues;
67 use Koha::DateUtils qw(dt_from_string);
70 use Koha::Biblioitems;
73 use Koha::SearchEngine;
74 use Koha::SearchEngine::Indexer;
75 use Koha::SearchEngine::Search;
80 C4::Items - item management functions
84 This module contains an API for manipulating item
85 records in Koha, and is used by cataloguing, circulation,
86 acquisitions, and serials management.
88 # FIXME This POD is not up-to-date
89 A Koha item record is stored in two places: the
90 items table and embedded in a MARC tag in the XML
91 version of the associated bib record in C<biblioitems.marcxml>.
92 This is done to allow the item information to be readily
93 indexed (e.g., by Zebra), but means that each item
94 modification transaction must keep the items table
95 and the MARC XML in sync at all times.
97 The items table will be considered authoritative. In other
98 words, if there is ever a discrepancy between the items
99 table and the MARC XML, the items table should be considered
102 =head1 HISTORICAL NOTE
104 Most of the functions in C<C4::Items> were originally in
105 the C<C4::Biblio> module.
107 =head1 CORE EXPORTED FUNCTIONS
109 The following functions are meant for use by users
116 CartToShelf($itemnumber);
118 Set the current shelving location of the item record
119 to its stored permanent shelving location. This is
120 primarily used to indicate when an item whose current
121 location is a special processing ('PROC') or shelving cart
122 ('CART') location is back in the stacks.
127 my ( $itemnumber ) = @_;
129 unless ( $itemnumber ) {
130 croak "FAILED CartToShelf() - no itemnumber supplied";
133 my $item = Koha::Items->find($itemnumber);
134 if ( $item->location eq 'CART' ) {
135 $item->location($item->permanent_location)->store;
139 =head2 AddItemFromMarc
141 my ($biblionumber, $biblioitemnumber, $itemnumber)
142 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
144 Given a MARC::Record object containing an embedded item
145 record and a biblionumber, create a new item record.
147 The final optional parameter, C<$params>, expected to contain
148 'skip_record_index' key, which relayed down to Koha::Item/store,
149 there it prevents calling of index_records,
150 which takes most of the time in batch adds/deletes: index_records
151 to be called later in C<additem.pl> after the whole loop.
154 skip_record_index => 1|0
158 sub AddItemFromMarc {
159 my $source_item_marc = shift;
160 my $biblionumber = shift;
161 my $params = @_ ? shift : {};
163 my $dbh = C4::Context->dbh;
165 # parse item hash from MARC
166 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
167 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
169 my $localitemmarc = MARC::Record->new;
170 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
172 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
173 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
174 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
175 $item_values->{biblionumber} = $biblionumber;
176 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
177 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
178 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
179 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
182 =head2 AddItemBatchFromMarc
184 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
185 $biblionumber, $biblioitemnumber, $frameworkcode);
187 Efficiently create item records from a MARC biblio record with
188 embedded item fields. This routine is suitable for batch jobs.
190 This API assumes that the bib record has already been
191 saved to the C<biblio> and C<biblioitems> tables. It does
192 not expect that C<biblio_metadata.metadata> is populated, but it
193 will do so via a call to ModBibiloMarc.
195 The goal of this API is to have a similar effect to using AddBiblio
196 and AddItems in succession, but without inefficient repeated
197 parsing of the MARC XML bib record.
199 This function returns an arrayref of new itemsnumbers and an arrayref of item
200 errors encountered during the processing. Each entry in the errors
201 list is a hashref containing the following keys:
207 Sequence number of original item tag in the MARC record.
211 Item barcode, provide to assist in the construction of
212 useful error messages.
216 Code representing the error condition. Can be 'duplicate_barcode',
217 'invalid_homebranch', or 'invalid_holdingbranch'.
219 =item error_information
221 Additional information appropriate to the error condition.
227 sub AddItemBatchFromMarc {
228 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
229 my @itemnumbers = ();
231 my $dbh = C4::Context->dbh;
233 # We modify the record, so lets work on a clone so we don't change the
235 $record = $record->clone();
236 # loop through the item tags and start creating items
237 my @bad_item_fields = ();
238 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
239 my $item_sequence_num = 0;
240 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
241 $item_sequence_num++;
242 # we take the item field and stick it into a new
243 # MARC record -- this is required so far because (FIXME)
244 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
245 # and there is no TransformMarcFieldToKoha
246 my $temp_item_marc = MARC::Record->new();
247 $temp_item_marc->append_fields($item_field);
249 # add biblionumber and biblioitemnumber
250 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
251 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
252 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
253 $item->{'biblionumber'} = $biblionumber;
254 $item->{'biblioitemnumber'} = $biblioitemnumber;
255 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
256 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
258 # check for duplicate barcode
259 my %item_errors = CheckItemPreSave($item);
261 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
262 push @bad_item_fields, $item_field;
266 my $item_object = Koha::Item->new($item)->store;
267 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
269 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
271 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
272 $item_field->replace_with($new_item_marc->field($itemtag));
275 # remove any MARC item fields for rejected items
276 foreach my $item_field (@bad_item_fields) {
277 $record->delete_field($item_field);
280 return (\@itemnumbers, \@errors);
283 =head2 ModItemFromMarc
285 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
287 The final optional parameter, C<$params>, expected to contain
288 'skip_record_index' key, which relayed down to Koha::Item/store,
289 there it prevents calling of index_records,
290 which takes most of the time in batch adds/deletes: index_records better
291 to be called later in C<additem.pl> after the whole loop.
294 skip_record_index => 1|0
298 sub ModItemFromMarc {
299 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
301 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
302 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
304 my $localitemmarc = MARC::Record->new;
305 $localitemmarc->append_fields( $item_marc->field($itemtag) );
306 my $item_object = Koha::Items->find($itemnumber);
307 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
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 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
332 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
333 $item_object->store({ skip_record_index => $params->{skip_record_index} });
335 return $item_object->unblessed;
338 =head2 ModItemTransfer
340 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
342 Marks an item as being transferred from one branch to another and records the trigger.
344 The last optional parameter allows for passing skip_record_index through to the items store call.
348 sub ModItemTransfer {
349 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
351 my $dbh = C4::Context->dbh;
352 my $item = Koha::Items->find( $itemnumber );
354 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
355 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
356 # will have been preceded by a check of branch transfer limits)
357 my $to_library = Koha::Libraries->find($tobranch);
358 my $transfer = $item->request_transfer(
367 # Immediately set the item to in transit if it is checked in
368 if ( !$item->checkout ) {
369 $item->holdingbranch($frombranch)->store(
372 skip_record_index => $params->{skip_record_index}
381 =head2 ModDateLastSeen
383 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
385 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
386 C<$itemnumber> is the item number
387 C<$leave_item_lost> determines if a lost item will be found or remain lost
389 The last optional parameter allows for passing skip_record_index through to the items store call.
393 sub ModDateLastSeen {
394 my ( $itemnumber, $leave_item_lost, $params ) = @_;
396 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
398 my $item = Koha::Items->find($itemnumber);
399 $item->datelastseen($today);
400 $item->itemlost(0) unless $leave_item_lost;
401 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
404 =head2 CheckItemPreSave
406 my $item_ref = TransformMarcToKoha($marc, 'items');
408 my %errors = CheckItemPreSave($item_ref);
409 if (exists $errors{'duplicate_barcode'}) {
410 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
411 } elsif (exists $errors{'invalid_homebranch'}) {
412 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
413 } elsif (exists $errors{'invalid_holdingbranch'}) {
414 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
419 Given a hashref containing item fields, determine if it can be
420 inserted or updated in the database. Specifically, checks for
421 database integrity issues, and returns a hash containing any
422 of the following keys, if applicable.
426 =item duplicate_barcode
428 Barcode, if it duplicates one already found in the database.
430 =item invalid_homebranch
432 Home branch, if not defined in branches table.
434 =item invalid_holdingbranch
436 Holding branch, if not defined in branches table.
440 This function does NOT implement any policy-related checks,
441 e.g., whether current operator is allowed to save an
442 item that has a given branch code.
446 sub CheckItemPreSave {
447 my $item_ref = shift;
451 # check for duplicate barcode
452 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
453 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
454 if ($existing_item) {
455 if (!exists $item_ref->{'itemnumber'} # new item
456 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
457 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
462 # check for valid home branch
463 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
464 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
465 unless (defined $home_library) {
466 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
470 # check for valid holding branch
471 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
472 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
473 unless (defined $holding_library) {
474 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
482 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
484 The following functions provide various ways of
485 getting an item record, a set of item records, or
486 lists of authorized values for certain item fields.
490 =head2 GetItemsForInventory
492 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
493 minlocation => $minlocation,
494 maxlocation => $maxlocation,
495 location => $location,
496 ignoreissued => $ignoreissued,
497 datelastseen => $datelastseen,
498 branchcode => $branchcode,
502 statushash => $statushash,
503 itemtypes => \@itemsarray,
506 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
508 The sub returns a reference to a list of hashes, each containing
509 itemnumber, author, title, barcode, item callnumber, and date last
510 seen. It is ordered by callnumber then title.
512 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
513 the datelastseen can be used to specify that you want to see items not seen since a past date only.
514 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
515 $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.
517 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
521 sub GetItemsForInventory {
522 my ( $parameters ) = @_;
523 my $minlocation = $parameters->{'minlocation'} // '';
524 my $maxlocation = $parameters->{'maxlocation'} // '';
525 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
526 my $location = $parameters->{'location'} // '';
527 my $itemtype = $parameters->{'itemtype'} // '';
528 my $ignoreissued = $parameters->{'ignoreissued'} // '';
529 my $datelastseen = $parameters->{'datelastseen'} // '';
530 my $branchcode = $parameters->{'branchcode'} // '';
531 my $branch = $parameters->{'branch'} // '';
532 my $offset = $parameters->{'offset'} // '';
533 my $size = $parameters->{'size'} // '';
534 my $statushash = $parameters->{'statushash'} // '';
535 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
536 my $itemtypes = $parameters->{'itemtypes'} || [];
538 my $dbh = C4::Context->dbh;
539 my ( @bind_params, @where_strings );
541 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
542 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
544 my $select_columns = q{
545 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
547 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
550 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
551 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
554 for my $authvfield (keys %$statushash){
555 if ( scalar @{$statushash->{$authvfield}} > 0 ){
556 my $joinedvals = join ',', @{$statushash->{$authvfield}};
557 push @where_strings, "$authvfield in (" . $joinedvals . ")";
563 push @where_strings, 'items.cn_sort >= ?';
564 push @bind_params, $min_cnsort;
568 push @where_strings, 'items.cn_sort <= ?';
569 push @bind_params, $max_cnsort;
573 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
574 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
575 push @bind_params, $datelastseen;
579 push @where_strings, 'items.location = ?';
580 push @bind_params, $location;
584 if($branch eq "homebranch"){
585 push @where_strings, 'items.homebranch = ?';
587 push @where_strings, 'items.holdingbranch = ?';
589 push @bind_params, $branchcode;
593 push @where_strings, 'biblioitems.itemtype = ?';
594 push @bind_params, $itemtype;
596 if ( $ignoreissued) {
597 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
598 push @where_strings, 'issues.date_due IS NULL';
601 if ( $ignore_waiting_holds ) {
602 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
603 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
607 my $itemtypes_str = join ', ', @$itemtypes;
608 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
611 if ( @where_strings ) {
613 $query .= join ' AND ', @where_strings;
615 my $count_query = $select_count . $query;
616 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
617 $query .= " LIMIT $offset, $size" if ($offset and $size);
618 $query = $select_columns . $query;
619 my $sth = $dbh->prepare($query);
620 $sth->execute( @bind_params );
623 my $tmpresults = $sth->fetchall_arrayref({});
624 $sth = $dbh->prepare( $count_query );
625 $sth->execute( @bind_params );
626 my ($iTotalRecords) = $sth->fetchrow_array();
628 my @avs = Koha::AuthorisedValues->search(
629 { 'marc_subfield_structures.kohafield' => { '>' => '' },
630 'me.authorised_value' => { '>' => '' },
632 { join => { category => 'marc_subfield_structures' },
633 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
634 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
635 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
639 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
641 foreach my $row (@$tmpresults) {
644 foreach (keys %$row) {
647 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
650 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
656 return (\@results, $iTotalRecords);
661 @results = GetItemsInfo($biblionumber);
663 Returns information about items with the given biblionumber.
665 C<GetItemsInfo> returns a list of references-to-hash. Each element
666 contains a number of keys. Most of them are attributes from the
667 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
668 Koha database. Other keys include:
672 =item C<$data-E<gt>{branchname}>
674 The name (not the code) of the branch to which the book belongs.
676 =item C<$data-E<gt>{datelastseen}>
678 This is simply C<items.datelastseen>, except that while the date is
679 stored in YYYY-MM-DD format in the database, here it is converted to
680 DD/MM/YYYY format. A NULL date is returned as C<//>.
682 =item C<$data-E<gt>{datedue}>
684 =item C<$data-E<gt>{class}>
686 This is the concatenation of C<biblioitems.classification>, the book's
687 Dewey code, and C<biblioitems.subclass>.
689 =item C<$data-E<gt>{ocount}>
691 I think this is the number of copies of the book available.
693 =item C<$data-E<gt>{order}>
695 If this is set, it is set to C<One Order>.
702 my ( $biblionumber ) = @_;
703 my $dbh = C4::Context->dbh;
704 require C4::Languages;
705 my $language = C4::Languages::getlanguage();
711 biblioitems.itemtype,
714 biblioitems.publicationyear,
715 biblioitems.publishercode,
716 biblioitems.volumedate,
717 biblioitems.volumedesc,
720 items.notforloan as itemnotforloan,
721 issues.borrowernumber,
722 issues.date_due as datedue,
723 issues.onsite_checkout,
724 borrowers.cardnumber,
727 borrowers.branchcode as bcode,
729 serial.publisheddate,
730 itemtypes.description,
731 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
732 itemtypes.notforloan as notforloan_per_itemtype,
736 holding.opac_info as holding_branch_opac_info,
737 home.opac_info as home_branch_opac_info,
738 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
740 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
741 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
742 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
743 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
744 LEFT JOIN issues USING (itemnumber)
745 LEFT JOIN borrowers USING (borrowernumber)
746 LEFT JOIN serialitems USING (itemnumber)
747 LEFT JOIN serial USING (serialid)
748 LEFT JOIN itemtypes ON itemtypes.itemtype = "
749 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
751 LEFT JOIN tmp_holdsqueue USING (itemnumber)
752 LEFT JOIN localization ON itemtypes.itemtype = localization.code
753 AND localization.entity = 'itemtypes'
754 AND localization.lang = ?
757 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
758 my $sth = $dbh->prepare($query);
759 $sth->execute($language, $biblionumber);
764 my $userenv = C4::Context->userenv;
765 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
766 while ( my $data = $sth->fetchrow_hashref ) {
767 if ( $data->{borrowernumber} && $want_not_same_branch) {
768 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
771 $serial ||= $data->{'serial'};
774 # get notforloan complete status if applicable
775 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
776 $data->{notforloanvalue} = $descriptions->{lib} // '';
777 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
779 # get restricted status and description if applicable
780 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
781 $data->{restrictedvalue} = $descriptions->{lib} // '';
782 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
784 # my stack procedures
785 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
786 $data->{stack} = $descriptions->{lib} // '';
788 # Find the last 3 people who borrowed this item.
789 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
791 AND old_issues.borrowernumber = borrowers.borrowernumber
792 ORDER BY returndate DESC
794 $sth2->execute($data->{'itemnumber'});
796 while (my $data2 = $sth2->fetchrow_hashref()) {
797 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
798 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
799 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
803 $results[$i] = $data;
808 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
812 =head2 GetItemsLocationInfo
814 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
816 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
818 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
822 =item C<$data-E<gt>{homebranch}>
824 Branch Name of the item's homebranch
826 =item C<$data-E<gt>{holdingbranch}>
828 Branch Name of the item's holdingbranch
830 =item C<$data-E<gt>{location}>
832 Item's shelving location code
834 =item C<$data-E<gt>{location_intranet}>
836 The intranet description for the Shelving Location as set in authorised_values 'LOC'
838 =item C<$data-E<gt>{location_opac}>
840 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
843 =item C<$data-E<gt>{itemcallnumber}>
845 Item's itemcallnumber
847 =item C<$data-E<gt>{cn_sort}>
849 Item's call number normalized for sorting
855 sub GetItemsLocationInfo {
856 my $biblionumber = shift;
859 my $dbh = C4::Context->dbh;
860 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
861 location, itemcallnumber, cn_sort
862 FROM items, branches as a, branches as b
863 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
865 ORDER BY cn_sort ASC";
866 my $sth = $dbh->prepare($query);
867 $sth->execute($biblionumber);
869 while ( my $data = $sth->fetchrow_hashref ) {
870 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
871 $av = $av->count ? $av->next : undef;
872 $data->{location_intranet} = $av ? $av->lib : '';
873 $data->{location_opac} = $av ? $av->opac_description : '';
874 push @results, $data;
879 =head2 GetHostItemsInfo
881 $hostiteminfo = GetHostItemsInfo($hostfield);
882 Returns the iteminfo for items linked to records via a host field
886 sub GetHostItemsInfo {
890 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
891 return @returnitemsInfo;
895 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
896 C4::Context->preference('marcflavour') eq 'NORMARC') {
897 @fields = $record->field('773');
898 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
899 @fields = $record->field('461');
902 foreach my $hostfield ( @fields ) {
903 my $hostbiblionumber = $hostfield->subfield("0");
904 my $linkeditemnumber = $hostfield->subfield("9");
905 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
906 foreach my $hostitemInfo (@hostitemInfos) {
907 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
908 push @returnitemsInfo, $hostitemInfo;
913 return @returnitemsInfo;
916 =head2 get_hostitemnumbers_of
918 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
920 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
922 Return a reference on a hash where key is a biblionumber and values are
923 references on array of itemnumbers.
928 sub get_hostitemnumbers_of {
929 my ($biblionumber) = @_;
931 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
935 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
936 return unless $marcrecord;
938 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
940 my $marcflavor = C4::Context->preference('marcflavour');
941 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
946 elsif ( $marcflavor eq 'UNIMARC' ) {
952 foreach my $hostfield ( $marcrecord->field($tag) ) {
953 my $hostbiblionumber = $hostfield->subfield($biblio_s);
954 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
955 my $linkeditemnumber = $hostfield->subfield($item_s);
956 if ( ! $linkeditemnumber ) {
957 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
960 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
961 push @returnhostitemnumbers, $linkeditemnumber
965 return @returnhostitemnumbers;
968 =head2 GetHiddenItemnumbers
970 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
972 Given a list of items it checks which should be hidden from the OPAC given
973 the current configuration. Returns a list of itemnumbers corresponding to
974 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
979 sub GetHiddenItemnumbers {
981 my $items = $params->{items};
982 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
983 foreach my $except (split(/\|/, $exceptions)){
984 if ($params->{'borcat'} eq $except){
985 return; # we don't hide anything for this borrower category
991 my $yaml = C4::Context->preference('OpacHiddenItems');
992 return () if (! $yaml =~ /\S/ );
993 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
996 $hidingrules = YAML::XS::Load(Encode::encode_utf8($yaml));
999 warn "Unable to parse OpacHiddenItems syspref : $@";
1002 my $dbh = C4::Context->dbh;
1005 foreach my $item (@$items) {
1007 # We check each rule
1008 foreach my $field (keys %$hidingrules) {
1010 if (exists $item->{$field}) {
1011 $val = $item->{$field};
1014 my $query = "SELECT $field from items where itemnumber = ?";
1015 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1017 $val = '' unless defined $val;
1019 # If the results matches the values in the yaml file
1020 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1022 # We add the itemnumber to the list
1023 push @resultitems, $item->{'itemnumber'};
1025 # If at least one rule matched for an item, no need to test the others
1030 return @resultitems;
1033 =head1 LIMITED USE FUNCTIONS
1035 The following functions, while part of the public API,
1036 are not exported. This is generally because they are
1037 meant to be used by only one script for a specific
1038 purpose, and should not be used in any other context
1039 without careful thought.
1045 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1047 Returns MARC::Record of the item passed in parameter.
1048 This function is meant for use only in C<cataloguing/additem.pl>,
1049 where it is needed to support that script's MARC-like
1055 my ( $biblionumber, $itemnumber ) = @_;
1057 # GetMarcItem has been revised so that it does the following:
1058 # 1. Gets the item information from the items table.
1059 # 2. Converts it to a MARC field for storage in the bib record.
1061 # The previous behavior was:
1062 # 1. Get the bib record.
1063 # 2. Return the MARC tag corresponding to the item record.
1065 # The difference is that one treats the items row as authoritative,
1066 # while the other treats the MARC representation as authoritative
1067 # under certain circumstances.
1069 my $item = Koha::Items->find($itemnumber) or return;
1071 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1072 # Also, don't emit a subfield if the underlying field is blank.
1074 return Item2Marc($item->unblessed, $biblionumber);
1078 my ($itemrecord,$biblionumber)=@_;
1081 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1082 } keys %{ $itemrecord }
1084 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1085 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1086 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1087 "items.itemnumber", $framework,
1090 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1091 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1092 foreach my $field ($itemmarc->field($itemtag)){
1093 $field->add_subfields(@$unlinked_item_subfields);
1099 =head1 PRIVATE FUNCTIONS AND VARIABLES
1101 The following functions are not meant to be called
1102 directly, but are documented in order to explain
1103 the inner workings of C<C4::Items>.
1107 =head2 MoveItemFromBiblio
1109 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1111 Moves an item from a biblio to another
1113 Returns undef if the move failed or the biblionumber of the destination record otherwise
1117 sub MoveItemFromBiblio {
1118 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1119 my $dbh = C4::Context->dbh;
1120 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1121 SELECT biblioitemnumber
1123 WHERE biblionumber = ?
1124 |, undef, $tobiblio );
1125 my $return = $dbh->do(q|
1127 SET biblioitemnumber = ?,
1129 WHERE itemnumber = ?
1130 AND biblionumber = ?
1131 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1133 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1134 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1135 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1136 # Checking if the item we want to move is in an order
1137 require C4::Acquisition;
1138 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1140 # Replacing the biblionumber within the order if necessary
1141 $order->{'biblionumber'} = $tobiblio;
1142 C4::Acquisition::ModOrder($order);
1145 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1146 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1149 SET biblionumber = ?
1150 WHERE itemnumber = ?
1151 |, undef, $tobiblio, $itemnumber );
1158 =head2 _marc_from_item_hash
1160 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1162 Given an item hash representing a complete item record,
1163 create a C<MARC::Record> object containing an embedded
1164 tag representing that item.
1166 The third, optional parameter C<$unlinked_item_subfields> is
1167 an arrayref of subfields (not mapped to C<items> fields per the
1168 framework) to be added to the MARC representation
1173 sub _marc_from_item_hash {
1175 my $frameworkcode = shift;
1176 my $unlinked_item_subfields;
1178 $unlinked_item_subfields = shift;
1181 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1182 # Also, don't emit a subfield if the underlying field is blank.
1183 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1184 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1185 : () } keys %{ $item } };
1187 my $item_marc = MARC::Record->new();
1188 foreach my $item_field ( keys %{$mungeditem} ) {
1189 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1190 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1191 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1192 foreach my $value (@values){
1193 if ( my $field = $item_marc->field($tag) ) {
1194 $field->add_subfields( $subfield => $value );
1196 my $add_subfields = [];
1197 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1198 $add_subfields = $unlinked_item_subfields;
1200 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1208 =head2 _repack_item_errors
1210 Add an error message hash generated by C<CheckItemPreSave>
1211 to a list of errors.
1215 sub _repack_item_errors {
1216 my $item_sequence_num = shift;
1217 my $item_ref = shift;
1218 my $error_ref = shift;
1220 my @repacked_errors = ();
1222 foreach my $error_code (sort keys %{ $error_ref }) {
1223 my $repacked_error = {};
1224 $repacked_error->{'item_sequence'} = $item_sequence_num;
1225 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1226 $repacked_error->{'error_code'} = $error_code;
1227 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1228 push @repacked_errors, $repacked_error;
1231 return @repacked_errors;
1234 =head2 _get_unlinked_item_subfields
1236 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1240 sub _get_unlinked_item_subfields {
1241 my $original_item_marc = shift;
1242 my $frameworkcode = shift;
1244 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1246 # assume that this record has only one field, and that that
1247 # field contains only the item information
1249 my @fields = $original_item_marc->fields();
1250 if ($#fields > -1) {
1251 my $field = $fields[0];
1252 my $tag = $field->tag();
1253 foreach my $subfield ($field->subfields()) {
1254 if (defined $subfield->[1] and
1255 $subfield->[1] ne '' and
1256 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1257 push @$subfields, $subfield->[0] => $subfield->[1];
1264 =head2 _get_unlinked_subfields_xml
1266 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1270 sub _get_unlinked_subfields_xml {
1271 my $unlinked_item_subfields = shift;
1274 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1275 my $marc = MARC::Record->new();
1276 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1277 # used in the framework
1278 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1279 $marc->encoding("UTF-8");
1280 $xml = $marc->as_xml("USMARC");
1286 =head2 _parse_unlinked_item_subfields_from_xml
1288 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1292 sub _parse_unlinked_item_subfields_from_xml {
1294 require C4::Charset;
1295 return unless defined $xml and $xml ne "";
1296 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1297 my $unlinked_subfields = [];
1298 my @fields = $marc->fields();
1299 if ($#fields > -1) {
1300 foreach my $subfield ($fields[0]->subfields()) {
1301 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1304 return $unlinked_subfields;
1307 =head2 GetAnalyticsCount
1309 $count= &GetAnalyticsCount($itemnumber)
1311 counts Usage of itemnumber in Analytical bibliorecords.
1315 sub GetAnalyticsCount {
1316 my ($itemnumber) = @_;
1318 ### ZOOM search here
1320 $query= "hi=".$itemnumber;
1321 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1322 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1326 sub _SearchItems_build_where_fragment {
1329 my $dbh = C4::Context->dbh;
1332 if (exists($filter->{conjunction})) {
1333 my (@where_strs, @where_args);
1334 foreach my $f (@{ $filter->{filters} }) {
1335 my $fragment = _SearchItems_build_where_fragment($f);
1337 push @where_strs, $fragment->{str};
1338 push @where_args, @{ $fragment->{args} };
1343 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1346 args => \@where_args,
1350 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1351 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1352 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1353 my @operators = qw(= != > < >= <= like);
1354 push @operators, 'not like';
1355 my $field = $filter->{field} // q{};
1356 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1357 my $op = $filter->{operator};
1358 my $query = $filter->{query};
1359 my $ifnull = $filter->{ifnull};
1361 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1362 $op = '='; # default operator
1366 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1368 my $marcsubfield = $2;
1369 my ($kohafield) = $dbh->selectrow_array(q|
1370 SELECT kohafield FROM marc_subfield_structure
1371 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1372 |, undef, $marcfield, $marcsubfield);
1375 $column = $kohafield;
1377 # MARC field is not linked to a DB field so we need to use
1378 # ExtractValue on marcxml from biblio_metadata or
1379 # items.more_subfields_xml, depending on the MARC field.
1382 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1383 if ($marcfield eq $itemfield) {
1384 $sqlfield = 'more_subfields_xml';
1385 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1387 $sqlfield = 'metadata'; # From biblio_metadata
1388 if ($marcfield < 10) {
1389 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1391 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1394 $column = "ExtractValue($sqlfield, '$xpath')";
1400 if ( defined $ifnull ) {
1401 $column = "COALESCE($column, ?)";
1404 if (ref $query eq 'ARRAY') {
1407 } elsif ($op eq '!=') {
1411 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1416 str => "$column $op ?",
1421 if ( defined $ifnull ) {
1422 unshift @{ $where_fragment->{args} }, $ifnull;
1427 return $where_fragment;
1432 my ($items, $total) = SearchItems($filter, $params);
1434 Perform a search among items
1436 $filter is a reference to a hash which can be a filter, or a combination of filters.
1438 A filter has the following keys:
1442 =item * field: the name of a SQL column in table items
1444 =item * query: the value to search in this column
1446 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1450 A combination of filters hash the following keys:
1454 =item * conjunction: 'AND' or 'OR'
1456 =item * filters: array ref of filters
1460 $params is a reference to a hash that can contain the following parameters:
1464 =item * rows: Number of items to return. 0 returns everything (default: 0)
1466 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1469 =item * sortby: A SQL column name in items table to sort on
1471 =item * sortorder: 'ASC' or 'DESC'
1478 my ($filter, $params) = @_;
1482 return unless ref $filter eq 'HASH';
1483 return unless ref $params eq 'HASH';
1485 # Default parameters
1486 $params->{rows} ||= 0;
1487 $params->{page} ||= 1;
1488 $params->{sortby} ||= 'itemnumber';
1489 $params->{sortorder} ||= 'ASC';
1491 my ($where_str, @where_args);
1492 my $where_fragment = _SearchItems_build_where_fragment($filter);
1493 if ($where_fragment) {
1494 $where_str = $where_fragment->{str};
1495 @where_args = @{ $where_fragment->{args} };
1498 my $dbh = C4::Context->dbh;
1500 SELECT SQL_CALC_FOUND_ROWS items.*
1502 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1503 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1504 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1507 if (defined $where_str and $where_str ne '') {
1508 $query .= qq{ AND $where_str };
1511 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1512 push @where_args, C4::Context->preference('marcflavour');
1514 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1515 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1516 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1517 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1518 ? $params->{sortby} : 'itemnumber';
1519 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1520 $query .= qq{ ORDER BY $sortby $sortorder };
1522 my $rows = $params->{rows};
1525 my $offset = $rows * ($params->{page}-1);
1526 $query .= qq { LIMIT ?, ? };
1527 push @limit_args, $offset, $rows;
1530 my $sth = $dbh->prepare($query);
1531 my $rv = $sth->execute(@where_args, @limit_args);
1533 return unless ($rv);
1534 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1536 return ($sth->fetchall_arrayref({}), $total_rows);
1540 =head1 OTHER FUNCTIONS
1544 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1546 Find the given $subfield in the given $tag in the given
1547 MARC::Record $record. If the subfield is found, returns
1548 the (indicators, value) pair; otherwise, (undef, undef) is
1552 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1553 I suggest we export it from this module.
1558 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1561 if ( $tagfield < 10 ) {
1562 if ( $record->field($tagfield) ) {
1563 push @result, $record->field($tagfield)->data();
1568 foreach my $field ( $record->field($tagfield) ) {
1569 my @subfields = $field->subfields();
1570 foreach my $subfield (@subfields) {
1571 if ( @$subfield[0] eq $insubfield ) {
1572 push @result, @$subfield[1];
1573 $indicator = $field->indicator(1) . $field->indicator(2);
1578 return ( $indicator, @result );
1582 =head2 PrepareItemrecordDisplay
1584 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1586 Returns a hash with all the fields for Display a given item data in a template
1588 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1592 sub PrepareItemrecordDisplay {
1594 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1596 my $dbh = C4::Context->dbh;
1597 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1598 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1600 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1601 # a shared data structure. No plugin (including custom ones) should change
1602 # its contents. See also GetMarcStructure.
1603 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1605 # return nothing if we don't have found an existing framework.
1606 return q{} unless $tagslib;
1609 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1613 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1615 SELECT authorised_value,lib FROM authorised_values
1618 LEFT JOIN authorised_values_branches ON ( id = av_id )
1623 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1624 $query .= qq{ ORDER BY lib};
1625 my $authorised_values_sth = $dbh->prepare( $query );
1626 foreach my $tag ( sort keys %{$tagslib} ) {
1629 # loop through each subfield
1631 foreach my $subfield (
1632 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1633 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1634 values %{ $tagslib->{$tag} } )
1636 next unless ( $subfield->{'tab'} );
1637 next if ( $subfield->{'tab'} ne "10" );
1639 $subfield_data{tag} = $tag;
1640 $subfield_data{subfield} = $subfield->{subfield};
1641 $subfield_data{countsubfield} = $cntsubf++;
1642 $subfield_data{kohafield} = $subfield->{kohafield};
1643 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1645 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1646 $subfield_data{marc_lib} = $subfield->{lib};
1647 $subfield_data{mandatory} = $subfield->{mandatory};
1648 $subfield_data{repeatable} = $subfield->{repeatable};
1649 $subfield_data{hidden} = "display:none"
1650 if ( ( $subfield->{hidden} > 4 )
1651 || ( $subfield->{hidden} < -4 ) );
1652 my ( $x, $defaultvalue );
1654 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1656 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1657 if ( !defined $defaultvalue ) {
1658 $defaultvalue = q||;
1660 $defaultvalue =~ s/"/"/g;
1661 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1662 my $today_dt = dt_from_string;
1663 my $year = $today_dt->strftime('%Y');
1664 my $shortyear = $today_dt->strftime('%y');
1665 my $month = $today_dt->strftime('%m');
1666 my $day = $today_dt->strftime('%d');
1667 $defaultvalue =~ s/<<YYYY>>/$year/g;
1668 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1669 $defaultvalue =~ s/<<MM>>/$month/g;
1670 $defaultvalue =~ s/<<DD>>/$day/g;
1672 # And <<USER>> with surname (?)
1674 ( C4::Context->userenv
1675 ? C4::Context->userenv->{'surname'}
1676 : "superlibrarian" );
1677 $defaultvalue =~ s/<<USER>>/$username/g;
1680 my $maxlength = $subfield->{maxlength};
1682 # search for itemcallnumber if applicable
1683 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1684 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1685 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1686 my $CNtag = substr( $itemcn_pref, 0, 3 );
1687 next unless my $field = $itemrecord->field($CNtag);
1688 my $CNsubfields = substr( $itemcn_pref, 3 );
1689 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1690 last if $defaultvalue;
1693 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1695 && $defaultvalues->{'callnumber'} ) {
1696 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1697 # if the item record exists, only use default value if the item has no callnumber
1698 $defaultvalue = $defaultvalues->{callnumber};
1699 } elsif ( !$itemrecord and $defaultvalues ) {
1700 # if the item record *doesn't* exists, always use the default value
1701 $defaultvalue = $defaultvalues->{callnumber};
1704 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1706 && $defaultvalues->{'branchcode'} ) {
1707 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1708 $defaultvalue = $defaultvalues->{branchcode};
1711 if ( ( $subfield->{kohafield} eq 'items.location' )
1713 && $defaultvalues->{'location'} ) {
1715 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1716 # if the item record exists, only use default value if the item has no locationr
1717 $defaultvalue = $defaultvalues->{location};
1718 } elsif ( !$itemrecord and $defaultvalues ) {
1719 # if the item record *doesn't* exists, always use the default value
1720 $defaultvalue = $defaultvalues->{location};
1723 if ( $subfield->{authorised_value} ) {
1724 my @authorised_values;
1727 # builds list, depending on authorised value...
1729 if ( $subfield->{'authorised_value'} eq "branches" ) {
1730 if ( ( C4::Context->preference("IndependentBranches") )
1731 && !C4::Context->IsSuperLibrarian() ) {
1732 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1733 $sth->execute( C4::Context->userenv->{branch} );
1734 push @authorised_values, ""
1735 unless ( $subfield->{mandatory} );
1736 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1737 push @authorised_values, $branchcode;
1738 $authorised_lib{$branchcode} = $branchname;
1741 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1743 push @authorised_values, ""
1744 unless ( $subfield->{mandatory} );
1745 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1746 push @authorised_values, $branchcode;
1747 $authorised_lib{$branchcode} = $branchname;
1751 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1752 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1753 $defaultvalue = $defaultvalues->{branchcode};
1757 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1758 my $itemtypes = Koha::ItemTypes->search_with_localization;
1759 push @authorised_values, "";
1760 while ( my $itemtype = $itemtypes->next ) {
1761 push @authorised_values, $itemtype->itemtype;
1762 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1764 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1765 $defaultvalue = $defaultvalues->{'itemtype'};
1769 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1770 push @authorised_values, "";
1772 my $class_sources = GetClassSources();
1773 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1775 foreach my $class_source (sort keys %$class_sources) {
1776 next unless $class_sources->{$class_source}->{'used'} or
1777 ($class_source eq $default_source);
1778 push @authorised_values, $class_source;
1779 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1782 $defaultvalue = $default_source;
1784 #---- "true" authorised value
1786 $authorised_values_sth->execute(
1787 $subfield->{authorised_value},
1788 $branch_limit ? $branch_limit : ()
1790 push @authorised_values, "";
1791 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1792 push @authorised_values, $value;
1793 $authorised_lib{$value} = $lib;
1796 $subfield_data{marc_value} = {
1798 values => \@authorised_values,
1799 default => $defaultvalue // q{},
1800 labels => \%authorised_lib,
1802 } elsif ( $subfield->{value_builder} ) {
1804 require Koha::FrameworkPlugin;
1805 my $plugin = Koha::FrameworkPlugin->new({
1806 name => $subfield->{value_builder},
1809 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1810 $plugin->build( $pars );
1811 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1812 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1814 if( !$plugin->errstr ) {
1815 #TODO Move html to template; see report 12176/13397
1816 my $tab= $plugin->noclick? '-1': '';
1817 my $class= $plugin->noclick? ' disabled': '';
1818 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1819 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1821 warn $plugin->errstr;
1822 $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
1825 elsif ( $tag eq '' ) { # it's an hidden field
1826 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1828 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1829 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1831 elsif ( length($defaultvalue) > 100
1832 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1833 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1834 or (C4::Context->preference("marcflavour") eq "MARC21" and
1835 500 <= $tag && $tag < 600 )
1837 # oversize field (textarea)
1838 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1840 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1842 push( @loop_data, \%subfield_data );
1847 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1848 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1851 'itemtagfield' => $itemtagfield,
1852 'itemtagsubfield' => $itemtagsubfield,
1853 'itemnumber' => $itemnumber,
1854 'iteminformation' => \@loop_data
1858 sub ToggleNewStatus {
1859 my ( $params ) = @_;
1860 my @rules = @{ $params->{rules} };
1861 my $report_only = $params->{report_only};
1863 my $dbh = C4::Context->dbh;
1865 my @item_columns = map { "items.$_" } Koha::Items->columns;
1866 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1868 for my $rule ( @rules ) {
1869 my $age = $rule->{age};
1870 my $conditions = $rule->{conditions};
1871 my $substitutions = $rule->{substitutions};
1872 foreach ( @$substitutions ) {
1873 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1880 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1883 for my $condition ( @$conditions ) {
1885 grep { $_ eq $condition->{field} } @item_columns
1886 or grep { $_ eq $condition->{field} } @biblioitem_columns
1888 if ( $condition->{value} =~ /\|/ ) {
1889 my @values = split /\|/, $condition->{value};
1890 $query .= qq| AND $condition->{field} IN (|
1891 . join( ',', ('?') x scalar @values )
1893 push @params, @values;
1895 $query .= qq| AND $condition->{field} = ?|;
1896 push @params, $condition->{value};
1900 if ( defined $age ) {
1901 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1904 my $sth = $dbh->prepare($query);
1905 $sth->execute( @params );
1906 while ( my $values = $sth->fetchrow_hashref ) {
1907 my $biblionumber = $values->{biblionumber};
1908 my $itemnumber = $values->{itemnumber};
1909 my $item = Koha::Items->find($itemnumber);
1910 for my $substitution ( @$substitutions ) {
1911 my $field = $substitution->{item_field};
1912 my $value = $substitution->{value};
1913 next unless $substitution->{field};
1914 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1915 $item->$field($value);
1916 push @{ $report->{$itemnumber} }, $substitution;
1918 $item->store unless $report_only;