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);
61 use DateTime::Format::MySQL;
62 use Data::Dumper; # used as part of logging item record changes, not just for
63 # debugging; so please don't remove this
65 use Koha::AuthorisedValues;
66 use Koha::DateUtils qw(dt_from_string);
69 use Koha::Biblioitems;
72 use Koha::SearchEngine;
73 use Koha::SearchEngine::Indexer;
74 use Koha::SearchEngine::Search;
79 C4::Items - item management functions
83 This module contains an API for manipulating item
84 records in Koha, and is used by cataloguing, circulation,
85 acquisitions, and serials management.
87 # FIXME This POD is not up-to-date
88 A Koha item record is stored in two places: the
89 items table and embedded in a MARC tag in the XML
90 version of the associated bib record in C<biblioitems.marcxml>.
91 This is done to allow the item information to be readily
92 indexed (e.g., by Zebra), but means that each item
93 modification transaction must keep the items table
94 and the MARC XML in sync at all times.
96 The items table will be considered authoritative. In other
97 words, if there is ever a discrepancy between the items
98 table and the MARC XML, the items table should be considered
101 =head1 HISTORICAL NOTE
103 Most of the functions in C<C4::Items> were originally in
104 the C<C4::Biblio> module.
106 =head1 CORE EXPORTED FUNCTIONS
108 The following functions are meant for use by users
115 CartToShelf($itemnumber);
117 Set the current shelving location of the item record
118 to its stored permanent shelving location. This is
119 primarily used to indicate when an item whose current
120 location is a special processing ('PROC') or shelving cart
121 ('CART') location is back in the stacks.
126 my ( $itemnumber ) = @_;
128 unless ( $itemnumber ) {
129 croak "FAILED CartToShelf() - no itemnumber supplied";
132 my $item = Koha::Items->find($itemnumber);
133 if ( $item->location eq 'CART' ) {
134 $item->location($item->permanent_location)->store;
138 =head2 AddItemFromMarc
140 my ($biblionumber, $biblioitemnumber, $itemnumber)
141 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
143 Given a MARC::Record object containing an embedded item
144 record and a biblionumber, create a new item record.
146 The final optional parameter, C<$params>, expected to contain
147 'skip_record_index' key, which relayed down to Koha::Item/store,
148 there it prevents calling of index_records,
149 which takes most of the time in batch adds/deletes: index_records
150 to be called later in C<additem.pl> after the whole loop.
153 skip_record_index => 1|0
157 sub AddItemFromMarc {
158 my $source_item_marc = shift;
159 my $biblionumber = shift;
160 my $params = @_ ? shift : {};
162 my $dbh = C4::Context->dbh;
164 # parse item hash from MARC
165 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
166 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
168 my $localitemmarc = MARC::Record->new;
169 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
171 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
172 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
173 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
174 $item_values->{biblionumber} = $biblionumber;
175 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
176 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
177 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
178 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
181 =head2 AddItemBatchFromMarc
183 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
184 $biblionumber, $biblioitemnumber, $frameworkcode);
186 Efficiently create item records from a MARC biblio record with
187 embedded item fields. This routine is suitable for batch jobs.
189 This API assumes that the bib record has already been
190 saved to the C<biblio> and C<biblioitems> tables. It does
191 not expect that C<biblio_metadata.metadata> is populated, but it
192 will do so via a call to ModBibiloMarc.
194 The goal of this API is to have a similar effect to using AddBiblio
195 and AddItems in succession, but without inefficient repeated
196 parsing of the MARC XML bib record.
198 This function returns an arrayref of new itemsnumbers and an arrayref of item
199 errors encountered during the processing. Each entry in the errors
200 list is a hashref containing the following keys:
206 Sequence number of original item tag in the MARC record.
210 Item barcode, provide to assist in the construction of
211 useful error messages.
215 Code representing the error condition. Can be 'duplicate_barcode',
216 'invalid_homebranch', or 'invalid_holdingbranch'.
218 =item error_information
220 Additional information appropriate to the error condition.
226 sub AddItemBatchFromMarc {
227 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
228 my @itemnumbers = ();
230 my $dbh = C4::Context->dbh;
232 # We modify the record, so lets work on a clone so we don't change the
234 $record = $record->clone();
235 # loop through the item tags and start creating items
236 my @bad_item_fields = ();
237 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
238 my $item_sequence_num = 0;
239 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
240 $item_sequence_num++;
241 # we take the item field and stick it into a new
242 # MARC record -- this is required so far because (FIXME)
243 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
244 # and there is no TransformMarcFieldToKoha
245 my $temp_item_marc = MARC::Record->new();
246 $temp_item_marc->append_fields($item_field);
248 # add biblionumber and biblioitemnumber
249 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
250 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
251 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
252 $item->{'biblionumber'} = $biblionumber;
253 $item->{'biblioitemnumber'} = $biblioitemnumber;
254 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
255 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
257 # check for duplicate barcode
258 my %item_errors = CheckItemPreSave($item);
260 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
261 push @bad_item_fields, $item_field;
265 my $item_object = Koha::Item->new($item)->store;
266 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
268 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
270 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
271 $item_field->replace_with($new_item_marc->field($itemtag));
274 # remove any MARC item fields for rejected items
275 foreach my $item_field (@bad_item_fields) {
276 $record->delete_field($item_field);
279 return (\@itemnumbers, \@errors);
282 =head2 ModItemFromMarc
284 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
286 The final optional parameter, C<$params>, expected to contain
287 'skip_record_index' key, which relayed down to Koha::Item/store,
288 there it prevents calling of index_records,
289 which takes most of the time in batch adds/deletes: index_records better
290 to be called later in C<additem.pl> after the whole loop.
293 skip_record_index => 1|0
297 sub ModItemFromMarc {
298 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
300 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
301 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
303 my $localitemmarc = MARC::Record->new;
304 $localitemmarc->append_fields( $item_marc->field($itemtag) );
305 my $item_object = Koha::Items->find($itemnumber);
306 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
308 # Retrieving the values for the fields that are not linked
309 my @mapped_fields = Koha::MarcSubfieldStructures->search(
311 frameworkcode => $frameworkcode,
312 kohafield => { -like => "items.%" }
314 )->get_column('kohafield');
315 for my $c ( $item_object->_result->result_source->columns ) {
316 next if grep { "items.$c" eq $_ } @mapped_fields;
317 $item->{$c} = $item_object->$c;
320 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
321 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
322 $item->{itemnumber} = $itemnumber;
323 $item->{biblionumber} = $biblionumber;
325 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
326 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
327 $item_object = $item_object->set_or_blank($item);
328 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
330 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
331 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
332 $item_object->store({ skip_record_index => $params->{skip_record_index} });
334 return $item_object->unblessed;
337 =head2 ModItemTransfer
339 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
341 Marks an item as being transferred from one branch to another and records the trigger.
343 The last optional parameter allows for passing skip_record_index through to the items store call.
347 sub ModItemTransfer {
348 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
350 my $dbh = C4::Context->dbh;
351 my $item = Koha::Items->find( $itemnumber );
353 # Remove the 'shelving cart' location status if it is being used.
354 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
356 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
358 #new entry in branchtransfers....
359 my $sth = $dbh->prepare(
360 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
361 VALUES (?, ?, NOW(), ?, ?)");
362 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
364 # FIXME we are fetching the item twice in the 2 next statements!
365 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
366 ModDateLastSeen($itemnumber, undef, { skip_record_index => $params->{skip_record_index} });
370 =head2 ModDateLastSeen
372 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
374 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
375 C<$itemnumber> is the item number
376 C<$leave_item_lost> determines if a lost item will be found or remain lost
378 The last optional parameter allows for passing skip_record_index through to the items store call.
382 sub ModDateLastSeen {
383 my ( $itemnumber, $leave_item_lost, $params ) = @_;
385 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
387 my $item = Koha::Items->find($itemnumber);
388 $item->datelastseen($today);
389 $item->itemlost(0) unless $leave_item_lost;
390 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
393 =head2 CheckItemPreSave
395 my $item_ref = TransformMarcToKoha($marc, 'items');
397 my %errors = CheckItemPreSave($item_ref);
398 if (exists $errors{'duplicate_barcode'}) {
399 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
400 } elsif (exists $errors{'invalid_homebranch'}) {
401 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
402 } elsif (exists $errors{'invalid_holdingbranch'}) {
403 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
408 Given a hashref containing item fields, determine if it can be
409 inserted or updated in the database. Specifically, checks for
410 database integrity issues, and returns a hash containing any
411 of the following keys, if applicable.
415 =item duplicate_barcode
417 Barcode, if it duplicates one already found in the database.
419 =item invalid_homebranch
421 Home branch, if not defined in branches table.
423 =item invalid_holdingbranch
425 Holding branch, if not defined in branches table.
429 This function does NOT implement any policy-related checks,
430 e.g., whether current operator is allowed to save an
431 item that has a given branch code.
435 sub CheckItemPreSave {
436 my $item_ref = shift;
440 # check for duplicate barcode
441 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
442 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
443 if ($existing_item) {
444 if (!exists $item_ref->{'itemnumber'} # new item
445 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
446 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
451 # check for valid home branch
452 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
453 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
454 unless (defined $home_library) {
455 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
459 # check for valid holding branch
460 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
461 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
462 unless (defined $holding_library) {
463 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
471 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
473 The following functions provide various ways of
474 getting an item record, a set of item records, or
475 lists of authorized values for certain item fields.
479 =head2 GetItemsForInventory
481 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
482 minlocation => $minlocation,
483 maxlocation => $maxlocation,
484 location => $location,
485 itemtype => $itemtype,
486 ignoreissued => $ignoreissued,
487 datelastseen => $datelastseen,
488 branchcode => $branchcode,
492 statushash => $statushash,
495 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
497 The sub returns a reference to a list of hashes, each containing
498 itemnumber, author, title, barcode, item callnumber, and date last
499 seen. It is ordered by callnumber then title.
501 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
502 the datelastseen can be used to specify that you want to see items not seen since a past date only.
503 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
504 $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.
506 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
510 sub GetItemsForInventory {
511 my ( $parameters ) = @_;
512 my $minlocation = $parameters->{'minlocation'} // '';
513 my $maxlocation = $parameters->{'maxlocation'} // '';
514 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
515 my $location = $parameters->{'location'} // '';
516 my $itemtype = $parameters->{'itemtype'} // '';
517 my $ignoreissued = $parameters->{'ignoreissued'} // '';
518 my $datelastseen = $parameters->{'datelastseen'} // '';
519 my $branchcode = $parameters->{'branchcode'} // '';
520 my $branch = $parameters->{'branch'} // '';
521 my $offset = $parameters->{'offset'} // '';
522 my $size = $parameters->{'size'} // '';
523 my $statushash = $parameters->{'statushash'} // '';
524 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
526 my $dbh = C4::Context->dbh;
527 my ( @bind_params, @where_strings );
529 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
530 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
532 my $select_columns = q{
533 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
535 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
538 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
539 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
542 for my $authvfield (keys %$statushash){
543 if ( scalar @{$statushash->{$authvfield}} > 0 ){
544 my $joinedvals = join ',', @{$statushash->{$authvfield}};
545 push @where_strings, "$authvfield in (" . $joinedvals . ")";
551 push @where_strings, 'items.cn_sort >= ?';
552 push @bind_params, $min_cnsort;
556 push @where_strings, 'items.cn_sort <= ?';
557 push @bind_params, $max_cnsort;
561 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
562 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
563 push @bind_params, $datelastseen;
567 push @where_strings, 'items.location = ?';
568 push @bind_params, $location;
572 if($branch eq "homebranch"){
573 push @where_strings, 'items.homebranch = ?';
575 push @where_strings, 'items.holdingbranch = ?';
577 push @bind_params, $branchcode;
581 push @where_strings, 'biblioitems.itemtype = ?';
582 push @bind_params, $itemtype;
585 if ( $ignoreissued) {
586 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
587 push @where_strings, 'issues.date_due IS NULL';
590 if ( $ignore_waiting_holds ) {
591 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
592 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
595 if ( @where_strings ) {
597 $query .= join ' AND ', @where_strings;
599 my $count_query = $select_count . $query;
600 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
601 $query .= " LIMIT $offset, $size" if ($offset and $size);
602 $query = $select_columns . $query;
603 my $sth = $dbh->prepare($query);
604 $sth->execute( @bind_params );
607 my $tmpresults = $sth->fetchall_arrayref({});
608 $sth = $dbh->prepare( $count_query );
609 $sth->execute( @bind_params );
610 my ($iTotalRecords) = $sth->fetchrow_array();
612 my @avs = Koha::AuthorisedValues->search(
613 { 'marc_subfield_structures.kohafield' => { '>' => '' },
614 'me.authorised_value' => { '>' => '' },
616 { join => { category => 'marc_subfield_structures' },
617 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
618 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
619 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
623 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
625 foreach my $row (@$tmpresults) {
628 foreach (keys %$row) {
631 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
634 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
640 return (\@results, $iTotalRecords);
645 @results = GetItemsInfo($biblionumber);
647 Returns information about items with the given biblionumber.
649 C<GetItemsInfo> returns a list of references-to-hash. Each element
650 contains a number of keys. Most of them are attributes from the
651 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
652 Koha database. Other keys include:
656 =item C<$data-E<gt>{branchname}>
658 The name (not the code) of the branch to which the book belongs.
660 =item C<$data-E<gt>{datelastseen}>
662 This is simply C<items.datelastseen>, except that while the date is
663 stored in YYYY-MM-DD format in the database, here it is converted to
664 DD/MM/YYYY format. A NULL date is returned as C<//>.
666 =item C<$data-E<gt>{datedue}>
668 =item C<$data-E<gt>{class}>
670 This is the concatenation of C<biblioitems.classification>, the book's
671 Dewey code, and C<biblioitems.subclass>.
673 =item C<$data-E<gt>{ocount}>
675 I think this is the number of copies of the book available.
677 =item C<$data-E<gt>{order}>
679 If this is set, it is set to C<One Order>.
686 my ( $biblionumber ) = @_;
687 my $dbh = C4::Context->dbh;
688 require C4::Languages;
689 my $language = C4::Languages::getlanguage();
695 biblioitems.itemtype,
698 biblioitems.publicationyear,
699 biblioitems.publishercode,
700 biblioitems.volumedate,
701 biblioitems.volumedesc,
704 items.notforloan as itemnotforloan,
705 issues.borrowernumber,
706 issues.date_due as datedue,
707 issues.onsite_checkout,
708 borrowers.cardnumber,
711 borrowers.branchcode as bcode,
713 serial.publisheddate,
714 itemtypes.description,
715 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
716 itemtypes.notforloan as notforloan_per_itemtype,
720 holding.opac_info as holding_branch_opac_info,
721 home.opac_info as home_branch_opac_info,
722 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
724 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
725 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
726 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
727 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
728 LEFT JOIN issues USING (itemnumber)
729 LEFT JOIN borrowers USING (borrowernumber)
730 LEFT JOIN serialitems USING (itemnumber)
731 LEFT JOIN serial USING (serialid)
732 LEFT JOIN itemtypes ON itemtypes.itemtype = "
733 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
735 LEFT JOIN tmp_holdsqueue USING (itemnumber)
736 LEFT JOIN localization ON itemtypes.itemtype = localization.code
737 AND localization.entity = 'itemtypes'
738 AND localization.lang = ?
741 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
742 my $sth = $dbh->prepare($query);
743 $sth->execute($language, $biblionumber);
748 my $userenv = C4::Context->userenv;
749 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
750 while ( my $data = $sth->fetchrow_hashref ) {
751 if ( $data->{borrowernumber} && $want_not_same_branch) {
752 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
755 $serial ||= $data->{'serial'};
758 # get notforloan complete status if applicable
759 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
760 $data->{notforloanvalue} = $descriptions->{lib} // '';
761 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
763 # get restricted status and description if applicable
764 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
765 $data->{restrictedvalue} = $descriptions->{lib} // '';
766 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
768 # my stack procedures
769 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
770 $data->{stack} = $descriptions->{lib} // '';
772 # Find the last 3 people who borrowed this item.
773 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
775 AND old_issues.borrowernumber = borrowers.borrowernumber
776 ORDER BY returndate DESC
778 $sth2->execute($data->{'itemnumber'});
780 while (my $data2 = $sth2->fetchrow_hashref()) {
781 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
782 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
783 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
787 $results[$i] = $data;
792 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
796 =head2 GetItemsLocationInfo
798 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
800 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
802 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
806 =item C<$data-E<gt>{homebranch}>
808 Branch Name of the item's homebranch
810 =item C<$data-E<gt>{holdingbranch}>
812 Branch Name of the item's holdingbranch
814 =item C<$data-E<gt>{location}>
816 Item's shelving location code
818 =item C<$data-E<gt>{location_intranet}>
820 The intranet description for the Shelving Location as set in authorised_values 'LOC'
822 =item C<$data-E<gt>{location_opac}>
824 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
827 =item C<$data-E<gt>{itemcallnumber}>
829 Item's itemcallnumber
831 =item C<$data-E<gt>{cn_sort}>
833 Item's call number normalized for sorting
839 sub GetItemsLocationInfo {
840 my $biblionumber = shift;
843 my $dbh = C4::Context->dbh;
844 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
845 location, itemcallnumber, cn_sort
846 FROM items, branches as a, branches as b
847 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
849 ORDER BY cn_sort ASC";
850 my $sth = $dbh->prepare($query);
851 $sth->execute($biblionumber);
853 while ( my $data = $sth->fetchrow_hashref ) {
854 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
855 $av = $av->count ? $av->next : undef;
856 $data->{location_intranet} = $av ? $av->lib : '';
857 $data->{location_opac} = $av ? $av->opac_description : '';
858 push @results, $data;
863 =head2 GetHostItemsInfo
865 $hostiteminfo = GetHostItemsInfo($hostfield);
866 Returns the iteminfo for items linked to records via a host field
870 sub GetHostItemsInfo {
874 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
875 return @returnitemsInfo;
879 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
880 C4::Context->preference('marcflavour') eq 'NORMARC') {
881 @fields = $record->field('773');
882 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
883 @fields = $record->field('461');
886 foreach my $hostfield ( @fields ) {
887 my $hostbiblionumber = $hostfield->subfield("0");
888 my $linkeditemnumber = $hostfield->subfield("9");
889 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
890 foreach my $hostitemInfo (@hostitemInfos) {
891 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
892 push @returnitemsInfo, $hostitemInfo;
897 return @returnitemsInfo;
900 =head2 get_hostitemnumbers_of
902 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
904 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
906 Return a reference on a hash where key is a biblionumber and values are
907 references on array of itemnumbers.
912 sub get_hostitemnumbers_of {
913 my ($biblionumber) = @_;
915 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
919 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
920 return unless $marcrecord;
922 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
924 my $marcflavor = C4::Context->preference('marcflavour');
925 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
930 elsif ( $marcflavor eq 'UNIMARC' ) {
936 foreach my $hostfield ( $marcrecord->field($tag) ) {
937 my $hostbiblionumber = $hostfield->subfield($biblio_s);
938 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
939 my $linkeditemnumber = $hostfield->subfield($item_s);
940 if ( ! $linkeditemnumber ) {
941 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
944 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
945 push @returnhostitemnumbers, $linkeditemnumber
949 return @returnhostitemnumbers;
952 =head2 GetHiddenItemnumbers
954 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
956 Given a list of items it checks which should be hidden from the OPAC given
957 the current configuration. Returns a list of itemnumbers corresponding to
958 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
963 sub GetHiddenItemnumbers {
965 my $items = $params->{items};
966 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
967 foreach my $except (split(/\|/, $exceptions)){
968 if ($params->{'borcat'} eq $except){
969 return; # we don't hide anything for this borrower category
975 my $yaml = C4::Context->preference('OpacHiddenItems');
976 return () if (! $yaml =~ /\S/ );
977 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
980 $hidingrules = YAML::Load($yaml);
983 warn "Unable to parse OpacHiddenItems syspref : $@";
986 my $dbh = C4::Context->dbh;
989 foreach my $item (@$items) {
992 foreach my $field (keys %$hidingrules) {
994 if (exists $item->{$field}) {
995 $val = $item->{$field};
998 my $query = "SELECT $field from items where itemnumber = ?";
999 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1001 $val = '' unless defined $val;
1003 # If the results matches the values in the yaml file
1004 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1006 # We add the itemnumber to the list
1007 push @resultitems, $item->{'itemnumber'};
1009 # If at least one rule matched for an item, no need to test the others
1014 return @resultitems;
1017 =head1 LIMITED USE FUNCTIONS
1019 The following functions, while part of the public API,
1020 are not exported. This is generally because they are
1021 meant to be used by only one script for a specific
1022 purpose, and should not be used in any other context
1023 without careful thought.
1029 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1031 Returns MARC::Record of the item passed in parameter.
1032 This function is meant for use only in C<cataloguing/additem.pl>,
1033 where it is needed to support that script's MARC-like
1039 my ( $biblionumber, $itemnumber ) = @_;
1041 # GetMarcItem has been revised so that it does the following:
1042 # 1. Gets the item information from the items table.
1043 # 2. Converts it to a MARC field for storage in the bib record.
1045 # The previous behavior was:
1046 # 1. Get the bib record.
1047 # 2. Return the MARC tag corresponding to the item record.
1049 # The difference is that one treats the items row as authoritative,
1050 # while the other treats the MARC representation as authoritative
1051 # under certain circumstances.
1053 my $item = Koha::Items->find($itemnumber) or return;
1055 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1056 # Also, don't emit a subfield if the underlying field is blank.
1058 return Item2Marc($item->unblessed, $biblionumber);
1062 my ($itemrecord,$biblionumber)=@_;
1065 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1066 } keys %{ $itemrecord }
1068 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1069 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1070 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1071 "items.itemnumber", $framework,
1074 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1075 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1076 foreach my $field ($itemmarc->field($itemtag)){
1077 $field->add_subfields(@$unlinked_item_subfields);
1083 =head1 PRIVATE FUNCTIONS AND VARIABLES
1085 The following functions are not meant to be called
1086 directly, but are documented in order to explain
1087 the inner workings of C<C4::Items>.
1091 =head2 MoveItemFromBiblio
1093 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1095 Moves an item from a biblio to another
1097 Returns undef if the move failed or the biblionumber of the destination record otherwise
1101 sub MoveItemFromBiblio {
1102 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1103 my $dbh = C4::Context->dbh;
1104 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1105 SELECT biblioitemnumber
1107 WHERE biblionumber = ?
1108 |, undef, $tobiblio );
1109 my $return = $dbh->do(q|
1111 SET biblioitemnumber = ?,
1113 WHERE itemnumber = ?
1114 AND biblionumber = ?
1115 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1117 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1118 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1119 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1120 # Checking if the item we want to move is in an order
1121 require C4::Acquisition;
1122 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1124 # Replacing the biblionumber within the order if necessary
1125 $order->{'biblionumber'} = $tobiblio;
1126 C4::Acquisition::ModOrder($order);
1129 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1130 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1133 SET biblionumber = ?
1134 WHERE itemnumber = ?
1135 |, undef, $tobiblio, $itemnumber );
1142 =head2 _marc_from_item_hash
1144 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1146 Given an item hash representing a complete item record,
1147 create a C<MARC::Record> object containing an embedded
1148 tag representing that item.
1150 The third, optional parameter C<$unlinked_item_subfields> is
1151 an arrayref of subfields (not mapped to C<items> fields per the
1152 framework) to be added to the MARC representation
1157 sub _marc_from_item_hash {
1159 my $frameworkcode = shift;
1160 my $unlinked_item_subfields;
1162 $unlinked_item_subfields = shift;
1165 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1166 # Also, don't emit a subfield if the underlying field is blank.
1167 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1168 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1169 : () } keys %{ $item } };
1171 my $item_marc = MARC::Record->new();
1172 foreach my $item_field ( keys %{$mungeditem} ) {
1173 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1174 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1175 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1176 foreach my $value (@values){
1177 if ( my $field = $item_marc->field($tag) ) {
1178 $field->add_subfields( $subfield => $value );
1180 my $add_subfields = [];
1181 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1182 $add_subfields = $unlinked_item_subfields;
1184 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1192 =head2 _repack_item_errors
1194 Add an error message hash generated by C<CheckItemPreSave>
1195 to a list of errors.
1199 sub _repack_item_errors {
1200 my $item_sequence_num = shift;
1201 my $item_ref = shift;
1202 my $error_ref = shift;
1204 my @repacked_errors = ();
1206 foreach my $error_code (sort keys %{ $error_ref }) {
1207 my $repacked_error = {};
1208 $repacked_error->{'item_sequence'} = $item_sequence_num;
1209 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1210 $repacked_error->{'error_code'} = $error_code;
1211 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1212 push @repacked_errors, $repacked_error;
1215 return @repacked_errors;
1218 =head2 _get_unlinked_item_subfields
1220 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1224 sub _get_unlinked_item_subfields {
1225 my $original_item_marc = shift;
1226 my $frameworkcode = shift;
1228 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1230 # assume that this record has only one field, and that that
1231 # field contains only the item information
1233 my @fields = $original_item_marc->fields();
1234 if ($#fields > -1) {
1235 my $field = $fields[0];
1236 my $tag = $field->tag();
1237 foreach my $subfield ($field->subfields()) {
1238 if (defined $subfield->[1] and
1239 $subfield->[1] ne '' and
1240 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1241 push @$subfields, $subfield->[0] => $subfield->[1];
1248 =head2 _get_unlinked_subfields_xml
1250 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1254 sub _get_unlinked_subfields_xml {
1255 my $unlinked_item_subfields = shift;
1258 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1259 my $marc = MARC::Record->new();
1260 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1261 # used in the framework
1262 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1263 $marc->encoding("UTF-8");
1264 $xml = $marc->as_xml("USMARC");
1270 =head2 _parse_unlinked_item_subfields_from_xml
1272 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1276 sub _parse_unlinked_item_subfields_from_xml {
1278 require C4::Charset;
1279 return unless defined $xml and $xml ne "";
1280 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1281 my $unlinked_subfields = [];
1282 my @fields = $marc->fields();
1283 if ($#fields > -1) {
1284 foreach my $subfield ($fields[0]->subfields()) {
1285 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1288 return $unlinked_subfields;
1291 =head2 GetAnalyticsCount
1293 $count= &GetAnalyticsCount($itemnumber)
1295 counts Usage of itemnumber in Analytical bibliorecords.
1299 sub GetAnalyticsCount {
1300 my ($itemnumber) = @_;
1302 ### ZOOM search here
1304 $query= "hi=".$itemnumber;
1305 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1306 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1310 sub _SearchItems_build_where_fragment {
1313 my $dbh = C4::Context->dbh;
1316 if (exists($filter->{conjunction})) {
1317 my (@where_strs, @where_args);
1318 foreach my $f (@{ $filter->{filters} }) {
1319 my $fragment = _SearchItems_build_where_fragment($f);
1321 push @where_strs, $fragment->{str};
1322 push @where_args, @{ $fragment->{args} };
1327 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1330 args => \@where_args,
1334 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1335 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1336 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1337 my @operators = qw(= != > < >= <= like);
1338 push @operators, 'not like';
1339 my $field = $filter->{field} // q{};
1340 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1341 my $op = $filter->{operator};
1342 my $query = $filter->{query};
1343 my $ifnull = $filter->{ifnull};
1345 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1346 $op = '='; # default operator
1350 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1352 my $marcsubfield = $2;
1353 my ($kohafield) = $dbh->selectrow_array(q|
1354 SELECT kohafield FROM marc_subfield_structure
1355 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1356 |, undef, $marcfield, $marcsubfield);
1359 $column = $kohafield;
1361 # MARC field is not linked to a DB field so we need to use
1362 # ExtractValue on marcxml from biblio_metadata or
1363 # items.more_subfields_xml, depending on the MARC field.
1366 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1367 if ($marcfield eq $itemfield) {
1368 $sqlfield = 'more_subfields_xml';
1369 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1371 $sqlfield = 'metadata'; # From biblio_metadata
1372 if ($marcfield < 10) {
1373 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1375 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1378 $column = "ExtractValue($sqlfield, '$xpath')";
1384 if ( defined $ifnull ) {
1385 $column = "COALESCE($column, ?)";
1388 if (ref $query eq 'ARRAY') {
1391 } elsif ($op eq '!=') {
1395 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1400 str => "$column $op ?",
1405 if ( defined $ifnull ) {
1406 unshift @{ $where_fragment->{args} }, $ifnull;
1411 return $where_fragment;
1416 my ($items, $total) = SearchItems($filter, $params);
1418 Perform a search among items
1420 $filter is a reference to a hash which can be a filter, or a combination of filters.
1422 A filter has the following keys:
1426 =item * field: the name of a SQL column in table items
1428 =item * query: the value to search in this column
1430 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1434 A combination of filters hash the following keys:
1438 =item * conjunction: 'AND' or 'OR'
1440 =item * filters: array ref of filters
1444 $params is a reference to a hash that can contain the following parameters:
1448 =item * rows: Number of items to return. 0 returns everything (default: 0)
1450 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1453 =item * sortby: A SQL column name in items table to sort on
1455 =item * sortorder: 'ASC' or 'DESC'
1462 my ($filter, $params) = @_;
1466 return unless ref $filter eq 'HASH';
1467 return unless ref $params eq 'HASH';
1469 # Default parameters
1470 $params->{rows} ||= 0;
1471 $params->{page} ||= 1;
1472 $params->{sortby} ||= 'itemnumber';
1473 $params->{sortorder} ||= 'ASC';
1475 my ($where_str, @where_args);
1476 my $where_fragment = _SearchItems_build_where_fragment($filter);
1477 if ($where_fragment) {
1478 $where_str = $where_fragment->{str};
1479 @where_args = @{ $where_fragment->{args} };
1482 my $dbh = C4::Context->dbh;
1484 SELECT SQL_CALC_FOUND_ROWS items.*
1486 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1487 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1488 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1491 if (defined $where_str and $where_str ne '') {
1492 $query .= qq{ AND $where_str };
1495 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1496 push @where_args, C4::Context->preference('marcflavour');
1498 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1499 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1500 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1501 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1502 ? $params->{sortby} : 'itemnumber';
1503 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1504 $query .= qq{ ORDER BY $sortby $sortorder };
1506 my $rows = $params->{rows};
1509 my $offset = $rows * ($params->{page}-1);
1510 $query .= qq { LIMIT ?, ? };
1511 push @limit_args, $offset, $rows;
1514 my $sth = $dbh->prepare($query);
1515 my $rv = $sth->execute(@where_args, @limit_args);
1517 return unless ($rv);
1518 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1520 return ($sth->fetchall_arrayref({}), $total_rows);
1524 =head1 OTHER FUNCTIONS
1528 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1530 Find the given $subfield in the given $tag in the given
1531 MARC::Record $record. If the subfield is found, returns
1532 the (indicators, value) pair; otherwise, (undef, undef) is
1536 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1537 I suggest we export it from this module.
1542 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1545 if ( $tagfield < 10 ) {
1546 if ( $record->field($tagfield) ) {
1547 push @result, $record->field($tagfield)->data();
1552 foreach my $field ( $record->field($tagfield) ) {
1553 my @subfields = $field->subfields();
1554 foreach my $subfield (@subfields) {
1555 if ( @$subfield[0] eq $insubfield ) {
1556 push @result, @$subfield[1];
1557 $indicator = $field->indicator(1) . $field->indicator(2);
1562 return ( $indicator, @result );
1566 =head2 PrepareItemrecordDisplay
1568 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1570 Returns a hash with all the fields for Display a given item data in a template
1572 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1576 sub PrepareItemrecordDisplay {
1578 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1580 my $dbh = C4::Context->dbh;
1581 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1582 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1584 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1585 # a shared data structure. No plugin (including custom ones) should change
1586 # its contents. See also GetMarcStructure.
1587 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1589 # return nothing if we don't have found an existing framework.
1590 return q{} unless $tagslib;
1593 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1597 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1599 SELECT authorised_value,lib FROM authorised_values
1602 LEFT JOIN authorised_values_branches ON ( id = av_id )
1607 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1608 $query .= qq{ ORDER BY lib};
1609 my $authorised_values_sth = $dbh->prepare( $query );
1610 foreach my $tag ( sort keys %{$tagslib} ) {
1613 # loop through each subfield
1615 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1616 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1617 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1618 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1620 $subfield_data{tag} = $tag;
1621 $subfield_data{subfield} = $subfield;
1622 $subfield_data{countsubfield} = $cntsubf++;
1623 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1624 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1626 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1627 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1628 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1629 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1630 $subfield_data{hidden} = "display:none"
1631 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1632 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1633 my ( $x, $defaultvalue );
1635 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1637 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1638 if ( !defined $defaultvalue ) {
1639 $defaultvalue = q||;
1641 $defaultvalue =~ s/"/"/g;
1642 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1643 my $today_dt = dt_from_string;
1644 my $year = $today_dt->strftime('%Y');
1645 my $shortyear = $today_dt->strftime('%y');
1646 my $month = $today_dt->strftime('%m');
1647 my $day = $today_dt->strftime('%d');
1648 $defaultvalue =~ s/<<YYYY>>/$year/g;
1649 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1650 $defaultvalue =~ s/<<MM>>/$month/g;
1651 $defaultvalue =~ s/<<DD>>/$day/g;
1653 # And <<USER>> with surname (?)
1655 ( C4::Context->userenv
1656 ? C4::Context->userenv->{'surname'}
1657 : "superlibrarian" );
1658 $defaultvalue =~ s/<<USER>>/$username/g;
1661 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1663 # search for itemcallnumber if applicable
1664 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1665 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1666 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1667 my $CNtag = substr( $itemcn_pref, 0, 3 );
1668 next unless my $field = $itemrecord->field($CNtag);
1669 my $CNsubfields = substr( $itemcn_pref, 3 );
1670 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1671 last if $defaultvalue;
1674 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1676 && $defaultvalues->{'callnumber'} ) {
1677 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1678 # if the item record exists, only use default value if the item has no callnumber
1679 $defaultvalue = $defaultvalues->{callnumber};
1680 } elsif ( !$itemrecord and $defaultvalues ) {
1681 # if the item record *doesn't* exists, always use the default value
1682 $defaultvalue = $defaultvalues->{callnumber};
1685 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1687 && $defaultvalues->{'branchcode'} ) {
1688 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1689 $defaultvalue = $defaultvalues->{branchcode};
1692 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1694 && $defaultvalues->{'location'} ) {
1696 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1697 # if the item record exists, only use default value if the item has no locationr
1698 $defaultvalue = $defaultvalues->{location};
1699 } elsif ( !$itemrecord and $defaultvalues ) {
1700 # if the item record *doesn't* exists, always use the default value
1701 $defaultvalue = $defaultvalues->{location};
1704 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1705 my @authorised_values;
1708 # builds list, depending on authorised value...
1710 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1711 if ( ( C4::Context->preference("IndependentBranches") )
1712 && !C4::Context->IsSuperLibrarian() ) {
1713 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1714 $sth->execute( C4::Context->userenv->{branch} );
1715 push @authorised_values, ""
1716 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1717 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1718 push @authorised_values, $branchcode;
1719 $authorised_lib{$branchcode} = $branchname;
1722 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1724 push @authorised_values, ""
1725 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1726 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1727 push @authorised_values, $branchcode;
1728 $authorised_lib{$branchcode} = $branchname;
1732 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1733 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1734 $defaultvalue = $defaultvalues->{branchcode};
1738 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1739 my $itemtypes = Koha::ItemTypes->search_with_localization;
1740 push @authorised_values, "";
1741 while ( my $itemtype = $itemtypes->next ) {
1742 push @authorised_values, $itemtype->itemtype;
1743 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1745 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1746 $defaultvalue = $defaultvalues->{'itemtype'};
1750 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1751 push @authorised_values, "";
1753 my $class_sources = GetClassSources();
1754 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1756 foreach my $class_source (sort keys %$class_sources) {
1757 next unless $class_sources->{$class_source}->{'used'} or
1758 ($class_source eq $default_source);
1759 push @authorised_values, $class_source;
1760 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1763 $defaultvalue = $default_source;
1765 #---- "true" authorised value
1767 $authorised_values_sth->execute(
1768 $tagslib->{$tag}->{$subfield}->{authorised_value},
1769 $branch_limit ? $branch_limit : ()
1771 push @authorised_values, "";
1772 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1773 push @authorised_values, $value;
1774 $authorised_lib{$value} = $lib;
1777 $subfield_data{marc_value} = {
1779 values => \@authorised_values,
1780 default => $defaultvalue // q{},
1781 labels => \%authorised_lib,
1783 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1785 require Koha::FrameworkPlugin;
1786 my $plugin = Koha::FrameworkPlugin->new({
1787 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1790 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1791 $plugin->build( $pars );
1792 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1793 $defaultvalue = $field->subfield($subfield) || q{};
1795 if( !$plugin->errstr ) {
1796 #TODO Move html to template; see report 12176/13397
1797 my $tab= $plugin->noclick? '-1': '';
1798 my $class= $plugin->noclick? ' disabled': '';
1799 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1800 $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;
1802 warn $plugin->errstr;
1803 $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
1806 elsif ( $tag eq '' ) { # it's an hidden field
1807 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1809 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1810 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1812 elsif ( length($defaultvalue) > 100
1813 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1814 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1815 or (C4::Context->preference("marcflavour") eq "MARC21" and
1816 500 <= $tag && $tag < 600 )
1818 # oversize field (textarea)
1819 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1821 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1823 push( @loop_data, \%subfield_data );
1828 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1829 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1832 'itemtagfield' => $itemtagfield,
1833 'itemtagsubfield' => $itemtagsubfield,
1834 'itemnumber' => $itemnumber,
1835 'iteminformation' => \@loop_data
1839 sub ToggleNewStatus {
1840 my ( $params ) = @_;
1841 my @rules = @{ $params->{rules} };
1842 my $report_only = $params->{report_only};
1844 my $dbh = C4::Context->dbh;
1846 my @item_columns = map { "items.$_" } Koha::Items->columns;
1847 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1849 for my $rule ( @rules ) {
1850 my $age = $rule->{age};
1851 my $conditions = $rule->{conditions};
1852 my $substitutions = $rule->{substitutions};
1853 foreach ( @$substitutions ) {
1854 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1861 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1864 for my $condition ( @$conditions ) {
1866 grep { $_ eq $condition->{field} } @item_columns
1867 or grep { $_ eq $condition->{field} } @biblioitem_columns
1869 if ( $condition->{value} =~ /\|/ ) {
1870 my @values = split /\|/, $condition->{value};
1871 $query .= qq| AND $condition->{field} IN (|
1872 . join( ',', ('?') x scalar @values )
1874 push @params, @values;
1876 $query .= qq| AND $condition->{field} = ?|;
1877 push @params, $condition->{value};
1881 if ( defined $age ) {
1882 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1885 my $sth = $dbh->prepare($query);
1886 $sth->execute( @params );
1887 while ( my $values = $sth->fetchrow_hashref ) {
1888 my $biblionumber = $values->{biblionumber};
1889 my $itemnumber = $values->{itemnumber};
1890 my $item = Koha::Items->find($itemnumber);
1891 for my $substitution ( @$substitutions ) {
1892 my $field = $substitution->{item_field};
1893 my $value = $substitution->{value};
1894 next unless $substitution->{field};
1895 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1896 $item->$field($value);
1897 push @{ $report->{$itemnumber} }, $substitution;
1899 $item->store unless $report_only;