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 # update the MARC biblio
280 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
282 return (\@itemnumbers, \@errors);
285 =head2 ModItemFromMarc
287 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
289 The final optional parameter, C<$params>, expected to contain
290 'skip_record_index' key, which relayed down to Koha::Item/store,
291 there it prevents calling of index_records,
292 which takes most of the time in batch adds/deletes: index_records better
293 to be called later in C<additem.pl> after the whole loop.
296 skip_record_index => 1|0
300 sub ModItemFromMarc {
301 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
303 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
304 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
306 my $localitemmarc = MARC::Record->new;
307 $localitemmarc->append_fields( $item_marc->field($itemtag) );
308 my $item_object = Koha::Items->find($itemnumber);
309 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
311 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
312 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
314 # Retrieving the values for the fields that are not linked
315 my @mapped_fields = Koha::MarcSubfieldStructures->search(
317 frameworkcode => $frameworkcode,
318 kohafield => { -like => "items.%" }
320 )->get_column('kohafield');
321 for my $c ( $item_object->_result->result_source->columns ) {
322 next if grep { "items.$c" eq $_ } @mapped_fields;
323 $item->{$c} = $item_object->$c;
326 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
327 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
328 $item->{itemnumber} = $itemnumber;
329 $item->{biblionumber} = $biblionumber;
331 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
332 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
333 $item_object = $item_object->set_or_blank($item);
334 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
336 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
338 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
339 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
340 $item_object->store({ skip_record_index => $params->{skip_record_index} });
342 return $item_object->unblessed;
345 =head2 ModItemTransfer
347 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
349 Marks an item as being transferred from one branch to another and records the trigger.
351 The last optional parameter allows for passing skip_record_index through to the items store call.
355 sub ModItemTransfer {
356 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
358 my $dbh = C4::Context->dbh;
359 my $item = Koha::Items->find( $itemnumber );
361 # Remove the 'shelving cart' location status if it is being used.
362 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
364 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
366 #new entry in branchtransfers....
367 my $sth = $dbh->prepare(
368 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
369 VALUES (?, ?, NOW(), ?, ?)");
370 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
372 # FIXME we are fetching the item twice in the 2 next statements!
373 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
374 ModDateLastSeen($itemnumber, undef, { skip_record_index => $params->{skip_record_index} });
378 =head2 ModDateLastSeen
380 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
382 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
383 C<$itemnumber> is the item number
384 C<$leave_item_lost> determines if a lost item will be found or remain lost
386 The last optional parameter allows for passing skip_record_index through to the items store call.
390 sub ModDateLastSeen {
391 my ( $itemnumber, $leave_item_lost, $params ) = @_;
393 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
395 my $item = Koha::Items->find($itemnumber);
396 $item->datelastseen($today);
397 $item->itemlost(0) unless $leave_item_lost;
398 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
401 =head2 CheckItemPreSave
403 my $item_ref = TransformMarcToKoha($marc, 'items');
405 my %errors = CheckItemPreSave($item_ref);
406 if (exists $errors{'duplicate_barcode'}) {
407 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
408 } elsif (exists $errors{'invalid_homebranch'}) {
409 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
410 } elsif (exists $errors{'invalid_holdingbranch'}) {
411 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
416 Given a hashref containing item fields, determine if it can be
417 inserted or updated in the database. Specifically, checks for
418 database integrity issues, and returns a hash containing any
419 of the following keys, if applicable.
423 =item duplicate_barcode
425 Barcode, if it duplicates one already found in the database.
427 =item invalid_homebranch
429 Home branch, if not defined in branches table.
431 =item invalid_holdingbranch
433 Holding branch, if not defined in branches table.
437 This function does NOT implement any policy-related checks,
438 e.g., whether current operator is allowed to save an
439 item that has a given branch code.
443 sub CheckItemPreSave {
444 my $item_ref = shift;
448 # check for duplicate barcode
449 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
450 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
451 if ($existing_item) {
452 if (!exists $item_ref->{'itemnumber'} # new item
453 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
454 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
459 # check for valid home branch
460 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
461 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
462 unless (defined $home_library) {
463 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
467 # check for valid holding branch
468 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
469 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
470 unless (defined $holding_library) {
471 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
479 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
481 The following functions provide various ways of
482 getting an item record, a set of item records, or
483 lists of authorized values for certain item fields.
487 =head2 GetItemsForInventory
489 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
490 minlocation => $minlocation,
491 maxlocation => $maxlocation,
492 location => $location,
493 itemtype => $itemtype,
494 ignoreissued => $ignoreissued,
495 datelastseen => $datelastseen,
496 branchcode => $branchcode,
500 statushash => $statushash,
503 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
505 The sub returns a reference to a list of hashes, each containing
506 itemnumber, author, title, barcode, item callnumber, and date last
507 seen. It is ordered by callnumber then title.
509 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
510 the datelastseen can be used to specify that you want to see items not seen since a past date only.
511 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
512 $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.
514 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
518 sub GetItemsForInventory {
519 my ( $parameters ) = @_;
520 my $minlocation = $parameters->{'minlocation'} // '';
521 my $maxlocation = $parameters->{'maxlocation'} // '';
522 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
523 my $location = $parameters->{'location'} // '';
524 my $itemtype = $parameters->{'itemtype'} // '';
525 my $ignoreissued = $parameters->{'ignoreissued'} // '';
526 my $datelastseen = $parameters->{'datelastseen'} // '';
527 my $branchcode = $parameters->{'branchcode'} // '';
528 my $branch = $parameters->{'branch'} // '';
529 my $offset = $parameters->{'offset'} // '';
530 my $size = $parameters->{'size'} // '';
531 my $statushash = $parameters->{'statushash'} // '';
532 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
534 my $dbh = C4::Context->dbh;
535 my ( @bind_params, @where_strings );
537 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
538 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
540 my $select_columns = q{
541 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
543 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
546 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
547 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
550 for my $authvfield (keys %$statushash){
551 if ( scalar @{$statushash->{$authvfield}} > 0 ){
552 my $joinedvals = join ',', @{$statushash->{$authvfield}};
553 push @where_strings, "$authvfield in (" . $joinedvals . ")";
559 push @where_strings, 'items.cn_sort >= ?';
560 push @bind_params, $min_cnsort;
564 push @where_strings, 'items.cn_sort <= ?';
565 push @bind_params, $max_cnsort;
569 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
570 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
571 push @bind_params, $datelastseen;
575 push @where_strings, 'items.location = ?';
576 push @bind_params, $location;
580 if($branch eq "homebranch"){
581 push @where_strings, 'items.homebranch = ?';
583 push @where_strings, 'items.holdingbranch = ?';
585 push @bind_params, $branchcode;
589 push @where_strings, 'biblioitems.itemtype = ?';
590 push @bind_params, $itemtype;
593 if ( $ignoreissued) {
594 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
595 push @where_strings, 'issues.date_due IS NULL';
598 if ( $ignore_waiting_holds ) {
599 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
600 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
603 if ( @where_strings ) {
605 $query .= join ' AND ', @where_strings;
607 my $count_query = $select_count . $query;
608 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
609 $query .= " LIMIT $offset, $size" if ($offset and $size);
610 $query = $select_columns . $query;
611 my $sth = $dbh->prepare($query);
612 $sth->execute( @bind_params );
615 my $tmpresults = $sth->fetchall_arrayref({});
616 $sth = $dbh->prepare( $count_query );
617 $sth->execute( @bind_params );
618 my ($iTotalRecords) = $sth->fetchrow_array();
620 my @avs = Koha::AuthorisedValues->search(
621 { 'marc_subfield_structures.kohafield' => { '>' => '' },
622 'me.authorised_value' => { '>' => '' },
624 { join => { category => 'marc_subfield_structures' },
625 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
626 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
627 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
631 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
633 foreach my $row (@$tmpresults) {
636 foreach (keys %$row) {
639 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
642 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
648 return (\@results, $iTotalRecords);
653 @results = GetItemsInfo($biblionumber);
655 Returns information about items with the given biblionumber.
657 C<GetItemsInfo> returns a list of references-to-hash. Each element
658 contains a number of keys. Most of them are attributes from the
659 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
660 Koha database. Other keys include:
664 =item C<$data-E<gt>{branchname}>
666 The name (not the code) of the branch to which the book belongs.
668 =item C<$data-E<gt>{datelastseen}>
670 This is simply C<items.datelastseen>, except that while the date is
671 stored in YYYY-MM-DD format in the database, here it is converted to
672 DD/MM/YYYY format. A NULL date is returned as C<//>.
674 =item C<$data-E<gt>{datedue}>
676 =item C<$data-E<gt>{class}>
678 This is the concatenation of C<biblioitems.classification>, the book's
679 Dewey code, and C<biblioitems.subclass>.
681 =item C<$data-E<gt>{ocount}>
683 I think this is the number of copies of the book available.
685 =item C<$data-E<gt>{order}>
687 If this is set, it is set to C<One Order>.
694 my ( $biblionumber ) = @_;
695 my $dbh = C4::Context->dbh;
696 require C4::Languages;
697 my $language = C4::Languages::getlanguage();
703 biblioitems.itemtype,
706 biblioitems.publicationyear,
707 biblioitems.publishercode,
708 biblioitems.volumedate,
709 biblioitems.volumedesc,
712 items.notforloan as itemnotforloan,
713 issues.borrowernumber,
714 issues.date_due as datedue,
715 issues.onsite_checkout,
716 borrowers.cardnumber,
719 borrowers.branchcode as bcode,
721 serial.publisheddate,
722 itemtypes.description,
723 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
724 itemtypes.notforloan as notforloan_per_itemtype,
728 holding.opac_info as holding_branch_opac_info,
729 home.opac_info as home_branch_opac_info,
730 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
732 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
733 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
734 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
735 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
736 LEFT JOIN issues USING (itemnumber)
737 LEFT JOIN borrowers USING (borrowernumber)
738 LEFT JOIN serialitems USING (itemnumber)
739 LEFT JOIN serial USING (serialid)
740 LEFT JOIN itemtypes ON itemtypes.itemtype = "
741 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
743 LEFT JOIN tmp_holdsqueue USING (itemnumber)
744 LEFT JOIN localization ON itemtypes.itemtype = localization.code
745 AND localization.entity = 'itemtypes'
746 AND localization.lang = ?
749 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
750 my $sth = $dbh->prepare($query);
751 $sth->execute($language, $biblionumber);
756 my $userenv = C4::Context->userenv;
757 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
758 while ( my $data = $sth->fetchrow_hashref ) {
759 if ( $data->{borrowernumber} && $want_not_same_branch) {
760 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
763 $serial ||= $data->{'serial'};
766 # get notforloan complete status if applicable
767 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
768 $data->{notforloanvalue} = $descriptions->{lib} // '';
769 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
771 # get restricted status and description if applicable
772 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
773 $data->{restrictedvalue} = $descriptions->{lib} // '';
774 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
776 # my stack procedures
777 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
778 $data->{stack} = $descriptions->{lib} // '';
780 # Find the last 3 people who borrowed this item.
781 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
783 AND old_issues.borrowernumber = borrowers.borrowernumber
784 ORDER BY returndate DESC
786 $sth2->execute($data->{'itemnumber'});
788 while (my $data2 = $sth2->fetchrow_hashref()) {
789 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
790 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
791 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
795 $results[$i] = $data;
800 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
804 =head2 GetItemsLocationInfo
806 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
808 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
810 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
814 =item C<$data-E<gt>{homebranch}>
816 Branch Name of the item's homebranch
818 =item C<$data-E<gt>{holdingbranch}>
820 Branch Name of the item's holdingbranch
822 =item C<$data-E<gt>{location}>
824 Item's shelving location code
826 =item C<$data-E<gt>{location_intranet}>
828 The intranet description for the Shelving Location as set in authorised_values 'LOC'
830 =item C<$data-E<gt>{location_opac}>
832 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
835 =item C<$data-E<gt>{itemcallnumber}>
837 Item's itemcallnumber
839 =item C<$data-E<gt>{cn_sort}>
841 Item's call number normalized for sorting
847 sub GetItemsLocationInfo {
848 my $biblionumber = shift;
851 my $dbh = C4::Context->dbh;
852 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
853 location, itemcallnumber, cn_sort
854 FROM items, branches as a, branches as b
855 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
857 ORDER BY cn_sort ASC";
858 my $sth = $dbh->prepare($query);
859 $sth->execute($biblionumber);
861 while ( my $data = $sth->fetchrow_hashref ) {
862 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
863 $av = $av->count ? $av->next : undef;
864 $data->{location_intranet} = $av ? $av->lib : '';
865 $data->{location_opac} = $av ? $av->opac_description : '';
866 push @results, $data;
871 =head2 GetHostItemsInfo
873 $hostiteminfo = GetHostItemsInfo($hostfield);
874 Returns the iteminfo for items linked to records via a host field
878 sub GetHostItemsInfo {
882 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
883 return @returnitemsInfo;
887 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
888 C4::Context->preference('marcflavour') eq 'NORMARC') {
889 @fields = $record->field('773');
890 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
891 @fields = $record->field('461');
894 foreach my $hostfield ( @fields ) {
895 my $hostbiblionumber = $hostfield->subfield("0");
896 my $linkeditemnumber = $hostfield->subfield("9");
897 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
898 foreach my $hostitemInfo (@hostitemInfos) {
899 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
900 push @returnitemsInfo, $hostitemInfo;
905 return @returnitemsInfo;
908 =head2 get_hostitemnumbers_of
910 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
912 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
914 Return a reference on a hash where key is a biblionumber and values are
915 references on array of itemnumbers.
920 sub get_hostitemnumbers_of {
921 my ($biblionumber) = @_;
923 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
927 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
928 return unless $marcrecord;
930 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
932 my $marcflavor = C4::Context->preference('marcflavour');
933 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
938 elsif ( $marcflavor eq 'UNIMARC' ) {
944 foreach my $hostfield ( $marcrecord->field($tag) ) {
945 my $hostbiblionumber = $hostfield->subfield($biblio_s);
946 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
947 my $linkeditemnumber = $hostfield->subfield($item_s);
948 if ( ! $linkeditemnumber ) {
949 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
952 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
953 push @returnhostitemnumbers, $linkeditemnumber
957 return @returnhostitemnumbers;
960 =head2 GetHiddenItemnumbers
962 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
964 Given a list of items it checks which should be hidden from the OPAC given
965 the current configuration. Returns a list of itemnumbers corresponding to
966 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
971 sub GetHiddenItemnumbers {
973 my $items = $params->{items};
974 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
975 foreach my $except (split(/\|/, $exceptions)){
976 if ($params->{'borcat'} eq $except){
977 return; # we don't hide anything for this borrower category
983 my $yaml = C4::Context->preference('OpacHiddenItems');
984 return () if (! $yaml =~ /\S/ );
985 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
988 $hidingrules = YAML::Load($yaml);
991 warn "Unable to parse OpacHiddenItems syspref : $@";
994 my $dbh = C4::Context->dbh;
997 foreach my $item (@$items) {
1000 foreach my $field (keys %$hidingrules) {
1002 if (exists $item->{$field}) {
1003 $val = $item->{$field};
1006 my $query = "SELECT $field from items where itemnumber = ?";
1007 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1009 $val = '' unless defined $val;
1011 # If the results matches the values in the yaml file
1012 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1014 # We add the itemnumber to the list
1015 push @resultitems, $item->{'itemnumber'};
1017 # If at least one rule matched for an item, no need to test the others
1022 return @resultitems;
1025 =head1 LIMITED USE FUNCTIONS
1027 The following functions, while part of the public API,
1028 are not exported. This is generally because they are
1029 meant to be used by only one script for a specific
1030 purpose, and should not be used in any other context
1031 without careful thought.
1037 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1039 Returns MARC::Record of the item passed in parameter.
1040 This function is meant for use only in C<cataloguing/additem.pl>,
1041 where it is needed to support that script's MARC-like
1047 my ( $biblionumber, $itemnumber ) = @_;
1049 # GetMarcItem has been revised so that it does the following:
1050 # 1. Gets the item information from the items table.
1051 # 2. Converts it to a MARC field for storage in the bib record.
1053 # The previous behavior was:
1054 # 1. Get the bib record.
1055 # 2. Return the MARC tag corresponding to the item record.
1057 # The difference is that one treats the items row as authoritative,
1058 # while the other treats the MARC representation as authoritative
1059 # under certain circumstances.
1061 my $item = Koha::Items->find($itemnumber) or return;
1063 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1064 # Also, don't emit a subfield if the underlying field is blank.
1066 return Item2Marc($item->unblessed, $biblionumber);
1070 my ($itemrecord,$biblionumber)=@_;
1073 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1074 } keys %{ $itemrecord }
1076 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1077 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1078 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1079 "items.itemnumber", $framework,
1082 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1083 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1084 foreach my $field ($itemmarc->field($itemtag)){
1085 $field->add_subfields(@$unlinked_item_subfields);
1091 =head1 PRIVATE FUNCTIONS AND VARIABLES
1093 The following functions are not meant to be called
1094 directly, but are documented in order to explain
1095 the inner workings of C<C4::Items>.
1099 =head2 MoveItemFromBiblio
1101 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1103 Moves an item from a biblio to another
1105 Returns undef if the move failed or the biblionumber of the destination record otherwise
1109 sub MoveItemFromBiblio {
1110 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1111 my $dbh = C4::Context->dbh;
1112 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1113 SELECT biblioitemnumber
1115 WHERE biblionumber = ?
1116 |, undef, $tobiblio );
1117 my $return = $dbh->do(q|
1119 SET biblioitemnumber = ?,
1121 WHERE itemnumber = ?
1122 AND biblionumber = ?
1123 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1125 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1126 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1127 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1128 # Checking if the item we want to move is in an order
1129 require C4::Acquisition;
1130 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1132 # Replacing the biblionumber within the order if necessary
1133 $order->{'biblionumber'} = $tobiblio;
1134 C4::Acquisition::ModOrder($order);
1137 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1138 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1141 SET biblionumber = ?
1142 WHERE itemnumber = ?
1143 |, undef, $tobiblio, $itemnumber );
1150 =head2 _marc_from_item_hash
1152 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1154 Given an item hash representing a complete item record,
1155 create a C<MARC::Record> object containing an embedded
1156 tag representing that item.
1158 The third, optional parameter C<$unlinked_item_subfields> is
1159 an arrayref of subfields (not mapped to C<items> fields per the
1160 framework) to be added to the MARC representation
1165 sub _marc_from_item_hash {
1167 my $frameworkcode = shift;
1168 my $unlinked_item_subfields;
1170 $unlinked_item_subfields = shift;
1173 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1174 # Also, don't emit a subfield if the underlying field is blank.
1175 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1176 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1177 : () } keys %{ $item } };
1179 my $item_marc = MARC::Record->new();
1180 foreach my $item_field ( keys %{$mungeditem} ) {
1181 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1182 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1183 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1184 foreach my $value (@values){
1185 if ( my $field = $item_marc->field($tag) ) {
1186 $field->add_subfields( $subfield => $value );
1188 my $add_subfields = [];
1189 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1190 $add_subfields = $unlinked_item_subfields;
1192 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1200 =head2 _repack_item_errors
1202 Add an error message hash generated by C<CheckItemPreSave>
1203 to a list of errors.
1207 sub _repack_item_errors {
1208 my $item_sequence_num = shift;
1209 my $item_ref = shift;
1210 my $error_ref = shift;
1212 my @repacked_errors = ();
1214 foreach my $error_code (sort keys %{ $error_ref }) {
1215 my $repacked_error = {};
1216 $repacked_error->{'item_sequence'} = $item_sequence_num;
1217 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1218 $repacked_error->{'error_code'} = $error_code;
1219 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1220 push @repacked_errors, $repacked_error;
1223 return @repacked_errors;
1226 =head2 _get_unlinked_item_subfields
1228 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1232 sub _get_unlinked_item_subfields {
1233 my $original_item_marc = shift;
1234 my $frameworkcode = shift;
1236 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1238 # assume that this record has only one field, and that that
1239 # field contains only the item information
1241 my @fields = $original_item_marc->fields();
1242 if ($#fields > -1) {
1243 my $field = $fields[0];
1244 my $tag = $field->tag();
1245 foreach my $subfield ($field->subfields()) {
1246 if (defined $subfield->[1] and
1247 $subfield->[1] ne '' and
1248 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1249 push @$subfields, $subfield->[0] => $subfield->[1];
1256 =head2 _get_unlinked_subfields_xml
1258 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1262 sub _get_unlinked_subfields_xml {
1263 my $unlinked_item_subfields = shift;
1266 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1267 my $marc = MARC::Record->new();
1268 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1269 # used in the framework
1270 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1271 $marc->encoding("UTF-8");
1272 $xml = $marc->as_xml("USMARC");
1278 =head2 _parse_unlinked_item_subfields_from_xml
1280 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1284 sub _parse_unlinked_item_subfields_from_xml {
1286 require C4::Charset;
1287 return unless defined $xml and $xml ne "";
1288 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1289 my $unlinked_subfields = [];
1290 my @fields = $marc->fields();
1291 if ($#fields > -1) {
1292 foreach my $subfield ($fields[0]->subfields()) {
1293 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1296 return $unlinked_subfields;
1299 =head2 GetAnalyticsCount
1301 $count= &GetAnalyticsCount($itemnumber)
1303 counts Usage of itemnumber in Analytical bibliorecords.
1307 sub GetAnalyticsCount {
1308 my ($itemnumber) = @_;
1310 ### ZOOM search here
1312 $query= "hi=".$itemnumber;
1313 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1314 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1318 sub _SearchItems_build_where_fragment {
1321 my $dbh = C4::Context->dbh;
1324 if (exists($filter->{conjunction})) {
1325 my (@where_strs, @where_args);
1326 foreach my $f (@{ $filter->{filters} }) {
1327 my $fragment = _SearchItems_build_where_fragment($f);
1329 push @where_strs, $fragment->{str};
1330 push @where_args, @{ $fragment->{args} };
1335 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1338 args => \@where_args,
1342 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1343 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1344 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1345 my @operators = qw(= != > < >= <= like);
1346 push @operators, 'not like';
1347 my $field = $filter->{field} // q{};
1348 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1349 my $op = $filter->{operator};
1350 my $query = $filter->{query};
1351 my $ifnull = $filter->{ifnull};
1353 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1354 $op = '='; # default operator
1358 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1360 my $marcsubfield = $2;
1361 my ($kohafield) = $dbh->selectrow_array(q|
1362 SELECT kohafield FROM marc_subfield_structure
1363 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1364 |, undef, $marcfield, $marcsubfield);
1367 $column = $kohafield;
1369 # MARC field is not linked to a DB field so we need to use
1370 # ExtractValue on marcxml from biblio_metadata or
1371 # items.more_subfields_xml, depending on the MARC field.
1374 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1375 if ($marcfield eq $itemfield) {
1376 $sqlfield = 'more_subfields_xml';
1377 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1379 $sqlfield = 'metadata'; # From biblio_metadata
1380 if ($marcfield < 10) {
1381 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1383 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1386 $column = "ExtractValue($sqlfield, '$xpath')";
1392 if ( defined $ifnull ) {
1393 $column = "COALESCE($column, ?)";
1396 if (ref $query eq 'ARRAY') {
1399 } elsif ($op eq '!=') {
1403 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1408 str => "$column $op ?",
1413 if ( defined $ifnull ) {
1414 unshift @{ $where_fragment->{args} }, $ifnull;
1419 return $where_fragment;
1424 my ($items, $total) = SearchItems($filter, $params);
1426 Perform a search among items
1428 $filter is a reference to a hash which can be a filter, or a combination of filters.
1430 A filter has the following keys:
1434 =item * field: the name of a SQL column in table items
1436 =item * query: the value to search in this column
1438 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1442 A combination of filters hash the following keys:
1446 =item * conjunction: 'AND' or 'OR'
1448 =item * filters: array ref of filters
1452 $params is a reference to a hash that can contain the following parameters:
1456 =item * rows: Number of items to return. 0 returns everything (default: 0)
1458 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1461 =item * sortby: A SQL column name in items table to sort on
1463 =item * sortorder: 'ASC' or 'DESC'
1470 my ($filter, $params) = @_;
1474 return unless ref $filter eq 'HASH';
1475 return unless ref $params eq 'HASH';
1477 # Default parameters
1478 $params->{rows} ||= 0;
1479 $params->{page} ||= 1;
1480 $params->{sortby} ||= 'itemnumber';
1481 $params->{sortorder} ||= 'ASC';
1483 my ($where_str, @where_args);
1484 my $where_fragment = _SearchItems_build_where_fragment($filter);
1485 if ($where_fragment) {
1486 $where_str = $where_fragment->{str};
1487 @where_args = @{ $where_fragment->{args} };
1490 my $dbh = C4::Context->dbh;
1492 SELECT SQL_CALC_FOUND_ROWS items.*
1494 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1495 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1496 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1499 if (defined $where_str and $where_str ne '') {
1500 $query .= qq{ AND $where_str };
1503 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1504 push @where_args, C4::Context->preference('marcflavour');
1506 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1507 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1508 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1509 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1510 ? $params->{sortby} : 'itemnumber';
1511 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1512 $query .= qq{ ORDER BY $sortby $sortorder };
1514 my $rows = $params->{rows};
1517 my $offset = $rows * ($params->{page}-1);
1518 $query .= qq { LIMIT ?, ? };
1519 push @limit_args, $offset, $rows;
1522 my $sth = $dbh->prepare($query);
1523 my $rv = $sth->execute(@where_args, @limit_args);
1525 return unless ($rv);
1526 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1528 return ($sth->fetchall_arrayref({}), $total_rows);
1532 =head1 OTHER FUNCTIONS
1536 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1538 Find the given $subfield in the given $tag in the given
1539 MARC::Record $record. If the subfield is found, returns
1540 the (indicators, value) pair; otherwise, (undef, undef) is
1544 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1545 I suggest we export it from this module.
1550 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1553 if ( $tagfield < 10 ) {
1554 if ( $record->field($tagfield) ) {
1555 push @result, $record->field($tagfield)->data();
1560 foreach my $field ( $record->field($tagfield) ) {
1561 my @subfields = $field->subfields();
1562 foreach my $subfield (@subfields) {
1563 if ( @$subfield[0] eq $insubfield ) {
1564 push @result, @$subfield[1];
1565 $indicator = $field->indicator(1) . $field->indicator(2);
1570 return ( $indicator, @result );
1574 =head2 PrepareItemrecordDisplay
1576 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1578 Returns a hash with all the fields for Display a given item data in a template
1580 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1584 sub PrepareItemrecordDisplay {
1586 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1588 my $dbh = C4::Context->dbh;
1589 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1590 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1592 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1593 # a shared data structure. No plugin (including custom ones) should change
1594 # its contents. See also GetMarcStructure.
1595 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1597 # return nothing if we don't have found an existing framework.
1598 return q{} unless $tagslib;
1601 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1605 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1607 SELECT authorised_value,lib FROM authorised_values
1610 LEFT JOIN authorised_values_branches ON ( id = av_id )
1615 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1616 $query .= qq{ ORDER BY lib};
1617 my $authorised_values_sth = $dbh->prepare( $query );
1618 foreach my $tag ( sort keys %{$tagslib} ) {
1621 # loop through each subfield
1623 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1624 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1625 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1626 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1628 $subfield_data{tag} = $tag;
1629 $subfield_data{subfield} = $subfield;
1630 $subfield_data{countsubfield} = $cntsubf++;
1631 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1632 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1634 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1635 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1636 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1637 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1638 $subfield_data{hidden} = "display:none"
1639 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1640 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1641 my ( $x, $defaultvalue );
1643 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1645 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1646 if ( !defined $defaultvalue ) {
1647 $defaultvalue = q||;
1649 $defaultvalue =~ s/"/"/g;
1650 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1651 my $today_dt = dt_from_string;
1652 my $year = $today_dt->strftime('%Y');
1653 my $shortyear = $today_dt->strftime('%y');
1654 my $month = $today_dt->strftime('%m');
1655 my $day = $today_dt->strftime('%d');
1656 $defaultvalue =~ s/<<YYYY>>/$year/g;
1657 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1658 $defaultvalue =~ s/<<MM>>/$month/g;
1659 $defaultvalue =~ s/<<DD>>/$day/g;
1661 # And <<USER>> with surname (?)
1663 ( C4::Context->userenv
1664 ? C4::Context->userenv->{'surname'}
1665 : "superlibrarian" );
1666 $defaultvalue =~ s/<<USER>>/$username/g;
1669 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1671 # search for itemcallnumber if applicable
1672 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1673 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1674 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1675 my $CNtag = substr( $itemcn_pref, 0, 3 );
1676 next unless my $field = $itemrecord->field($CNtag);
1677 my $CNsubfields = substr( $itemcn_pref, 3 );
1678 $CNsubfields = undef if $CNsubfields eq '';
1679 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1680 last if $defaultvalue;
1683 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1685 && $defaultvalues->{'callnumber'} ) {
1686 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1687 # if the item record exists, only use default value if the item has no callnumber
1688 $defaultvalue = $defaultvalues->{callnumber};
1689 } elsif ( !$itemrecord and $defaultvalues ) {
1690 # if the item record *doesn't* exists, always use the default value
1691 $defaultvalue = $defaultvalues->{callnumber};
1694 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1696 && $defaultvalues->{'branchcode'} ) {
1697 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1698 $defaultvalue = $defaultvalues->{branchcode};
1701 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1703 && $defaultvalues->{'location'} ) {
1705 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1706 # if the item record exists, only use default value if the item has no locationr
1707 $defaultvalue = $defaultvalues->{location};
1708 } elsif ( !$itemrecord and $defaultvalues ) {
1709 # if the item record *doesn't* exists, always use the default value
1710 $defaultvalue = $defaultvalues->{location};
1713 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1714 my @authorised_values;
1717 # builds list, depending on authorised value...
1719 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1720 if ( ( C4::Context->preference("IndependentBranches") )
1721 && !C4::Context->IsSuperLibrarian() ) {
1722 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1723 $sth->execute( C4::Context->userenv->{branch} );
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;
1731 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1733 push @authorised_values, ""
1734 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1735 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1736 push @authorised_values, $branchcode;
1737 $authorised_lib{$branchcode} = $branchname;
1741 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1742 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1743 $defaultvalue = $defaultvalues->{branchcode};
1747 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1748 my $itemtypes = Koha::ItemTypes->search_with_localization;
1749 push @authorised_values, "";
1750 while ( my $itemtype = $itemtypes->next ) {
1751 push @authorised_values, $itemtype->itemtype;
1752 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1754 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1755 $defaultvalue = $defaultvalues->{'itemtype'};
1759 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1760 push @authorised_values, "";
1762 my $class_sources = GetClassSources();
1763 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1765 foreach my $class_source (sort keys %$class_sources) {
1766 next unless $class_sources->{$class_source}->{'used'} or
1767 ($class_source eq $default_source);
1768 push @authorised_values, $class_source;
1769 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1772 $defaultvalue = $default_source;
1774 #---- "true" authorised value
1776 $authorised_values_sth->execute(
1777 $tagslib->{$tag}->{$subfield}->{authorised_value},
1778 $branch_limit ? $branch_limit : ()
1780 push @authorised_values, "";
1781 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1782 push @authorised_values, $value;
1783 $authorised_lib{$value} = $lib;
1786 $subfield_data{marc_value} = {
1788 values => \@authorised_values,
1789 default => $defaultvalue // q{},
1790 labels => \%authorised_lib,
1792 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1794 require Koha::FrameworkPlugin;
1795 my $plugin = Koha::FrameworkPlugin->new({
1796 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1799 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1800 $plugin->build( $pars );
1801 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1802 $defaultvalue = $field->subfield($subfield) || q{};
1804 if( !$plugin->errstr ) {
1805 #TODO Move html to template; see report 12176/13397
1806 my $tab= $plugin->noclick? '-1': '';
1807 my $class= $plugin->noclick? ' disabled': '';
1808 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1809 $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;
1811 warn $plugin->errstr;
1812 $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
1815 elsif ( $tag eq '' ) { # it's an hidden field
1816 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1818 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
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" />);
1821 elsif ( length($defaultvalue) > 100
1822 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1823 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1824 or (C4::Context->preference("marcflavour") eq "MARC21" and
1825 500 <= $tag && $tag < 600 )
1827 # oversize field (textarea)
1828 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1830 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1832 push( @loop_data, \%subfield_data );
1837 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1838 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1841 'itemtagfield' => $itemtagfield,
1842 'itemtagsubfield' => $itemtagsubfield,
1843 'itemnumber' => $itemnumber,
1844 'iteminformation' => \@loop_data
1848 sub ToggleNewStatus {
1849 my ( $params ) = @_;
1850 my @rules = @{ $params->{rules} };
1851 my $report_only = $params->{report_only};
1853 my $dbh = C4::Context->dbh;
1855 my @item_columns = map { "items.$_" } Koha::Items->columns;
1856 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1858 for my $rule ( @rules ) {
1859 my $age = $rule->{age};
1860 my $conditions = $rule->{conditions};
1861 my $substitutions = $rule->{substitutions};
1862 foreach ( @$substitutions ) {
1863 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1870 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1873 for my $condition ( @$conditions ) {
1875 grep { $_ eq $condition->{field} } @item_columns
1876 or grep { $_ eq $condition->{field} } @biblioitem_columns
1878 if ( $condition->{value} =~ /\|/ ) {
1879 my @values = split /\|/, $condition->{value};
1880 $query .= qq| AND $condition->{field} IN (|
1881 . join( ',', ('?') x scalar @values )
1883 push @params, @values;
1885 $query .= qq| AND $condition->{field} = ?|;
1886 push @params, $condition->{value};
1890 if ( defined $age ) {
1891 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1894 my $sth = $dbh->prepare($query);
1895 $sth->execute( @params );
1896 while ( my $values = $sth->fetchrow_hashref ) {
1897 my $biblionumber = $values->{biblionumber};
1898 my $itemnumber = $values->{itemnumber};
1899 my $item = Koha::Items->find($itemnumber);
1900 for my $substitution ( @$substitutions ) {
1901 my $field = $substitution->{item_field};
1902 my $value = $substitution->{value};
1903 next unless $substitution->{field};
1904 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1905 $item->$field($value);
1906 push @{ $report->{$itemnumber} }, $substitution;
1908 $item->store unless $report_only;