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 # Retrieving the values for the fields that are not linked
312 my @mapped_fields = Koha::MarcSubfieldStructures->search(
314 frameworkcode => $frameworkcode,
315 kohafield => { -like => "items.%" }
317 )->get_column('kohafield');
318 for my $c ( $item_object->_result->result_source->columns ) {
319 next if grep { "items.$c" eq $_ } @mapped_fields;
320 $item->{$c} = $item_object->$c;
323 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
324 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
325 $item->{itemnumber} = $itemnumber;
326 $item->{biblionumber} = $biblionumber;
328 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
329 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
330 $item_object = $item_object->set_or_blank($item);
331 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
333 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
334 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
335 $item_object->store({ skip_record_index => $params->{skip_record_index} });
337 return $item_object->unblessed;
340 =head2 ModItemTransfer
342 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
344 Marks an item as being transferred from one branch to another and records the trigger.
346 The last optional parameter allows for passing skip_record_index through to the items store call.
350 sub ModItemTransfer {
351 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
353 my $dbh = C4::Context->dbh;
354 my $item = Koha::Items->find( $itemnumber );
356 # Remove the 'shelving cart' location status if it is being used.
357 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
359 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
361 #new entry in branchtransfers....
362 my $sth = $dbh->prepare(
363 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
364 VALUES (?, ?, NOW(), ?, ?)");
365 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
367 # FIXME we are fetching the item twice in the 2 next statements!
368 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
369 ModDateLastSeen($itemnumber, undef, { skip_record_index => $params->{skip_record_index} });
373 =head2 ModDateLastSeen
375 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
377 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
378 C<$itemnumber> is the item number
379 C<$leave_item_lost> determines if a lost item will be found or remain lost
381 The last optional parameter allows for passing skip_record_index through to the items store call.
385 sub ModDateLastSeen {
386 my ( $itemnumber, $leave_item_lost, $params ) = @_;
388 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
390 my $item = Koha::Items->find($itemnumber);
391 $item->datelastseen($today);
392 $item->itemlost(0) unless $leave_item_lost;
393 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
396 =head2 CheckItemPreSave
398 my $item_ref = TransformMarcToKoha($marc, 'items');
400 my %errors = CheckItemPreSave($item_ref);
401 if (exists $errors{'duplicate_barcode'}) {
402 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
403 } elsif (exists $errors{'invalid_homebranch'}) {
404 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
405 } elsif (exists $errors{'invalid_holdingbranch'}) {
406 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
411 Given a hashref containing item fields, determine if it can be
412 inserted or updated in the database. Specifically, checks for
413 database integrity issues, and returns a hash containing any
414 of the following keys, if applicable.
418 =item duplicate_barcode
420 Barcode, if it duplicates one already found in the database.
422 =item invalid_homebranch
424 Home branch, if not defined in branches table.
426 =item invalid_holdingbranch
428 Holding branch, if not defined in branches table.
432 This function does NOT implement any policy-related checks,
433 e.g., whether current operator is allowed to save an
434 item that has a given branch code.
438 sub CheckItemPreSave {
439 my $item_ref = shift;
443 # check for duplicate barcode
444 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
445 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
446 if ($existing_item) {
447 if (!exists $item_ref->{'itemnumber'} # new item
448 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
449 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
454 # check for valid home branch
455 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
456 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
457 unless (defined $home_library) {
458 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
462 # check for valid holding branch
463 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
464 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
465 unless (defined $holding_library) {
466 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
474 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
476 The following functions provide various ways of
477 getting an item record, a set of item records, or
478 lists of authorized values for certain item fields.
482 =head2 GetItemsForInventory
484 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
485 minlocation => $minlocation,
486 maxlocation => $maxlocation,
487 location => $location,
488 itemtype => $itemtype,
489 ignoreissued => $ignoreissued,
490 datelastseen => $datelastseen,
491 branchcode => $branchcode,
495 statushash => $statushash,
498 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
500 The sub returns a reference to a list of hashes, each containing
501 itemnumber, author, title, barcode, item callnumber, and date last
502 seen. It is ordered by callnumber then title.
504 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
505 the datelastseen can be used to specify that you want to see items not seen since a past date only.
506 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
507 $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.
509 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
513 sub GetItemsForInventory {
514 my ( $parameters ) = @_;
515 my $minlocation = $parameters->{'minlocation'} // '';
516 my $maxlocation = $parameters->{'maxlocation'} // '';
517 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
518 my $location = $parameters->{'location'} // '';
519 my $itemtype = $parameters->{'itemtype'} // '';
520 my $ignoreissued = $parameters->{'ignoreissued'} // '';
521 my $datelastseen = $parameters->{'datelastseen'} // '';
522 my $branchcode = $parameters->{'branchcode'} // '';
523 my $branch = $parameters->{'branch'} // '';
524 my $offset = $parameters->{'offset'} // '';
525 my $size = $parameters->{'size'} // '';
526 my $statushash = $parameters->{'statushash'} // '';
527 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
529 my $dbh = C4::Context->dbh;
530 my ( @bind_params, @where_strings );
532 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
533 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
535 my $select_columns = q{
536 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
538 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
541 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
542 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
545 for my $authvfield (keys %$statushash){
546 if ( scalar @{$statushash->{$authvfield}} > 0 ){
547 my $joinedvals = join ',', @{$statushash->{$authvfield}};
548 push @where_strings, "$authvfield in (" . $joinedvals . ")";
554 push @where_strings, 'items.cn_sort >= ?';
555 push @bind_params, $min_cnsort;
559 push @where_strings, 'items.cn_sort <= ?';
560 push @bind_params, $max_cnsort;
564 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
565 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
566 push @bind_params, $datelastseen;
570 push @where_strings, 'items.location = ?';
571 push @bind_params, $location;
575 if($branch eq "homebranch"){
576 push @where_strings, 'items.homebranch = ?';
578 push @where_strings, 'items.holdingbranch = ?';
580 push @bind_params, $branchcode;
584 push @where_strings, 'biblioitems.itemtype = ?';
585 push @bind_params, $itemtype;
588 if ( $ignoreissued) {
589 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
590 push @where_strings, 'issues.date_due IS NULL';
593 if ( $ignore_waiting_holds ) {
594 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
595 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
598 if ( @where_strings ) {
600 $query .= join ' AND ', @where_strings;
602 my $count_query = $select_count . $query;
603 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
604 $query .= " LIMIT $offset, $size" if ($offset and $size);
605 $query = $select_columns . $query;
606 my $sth = $dbh->prepare($query);
607 $sth->execute( @bind_params );
610 my $tmpresults = $sth->fetchall_arrayref({});
611 $sth = $dbh->prepare( $count_query );
612 $sth->execute( @bind_params );
613 my ($iTotalRecords) = $sth->fetchrow_array();
615 my @avs = Koha::AuthorisedValues->search(
616 { 'marc_subfield_structures.kohafield' => { '>' => '' },
617 'me.authorised_value' => { '>' => '' },
619 { join => { category => 'marc_subfield_structures' },
620 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
621 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
622 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
626 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
628 foreach my $row (@$tmpresults) {
631 foreach (keys %$row) {
634 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
637 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
643 return (\@results, $iTotalRecords);
648 @results = GetItemsInfo($biblionumber);
650 Returns information about items with the given biblionumber.
652 C<GetItemsInfo> returns a list of references-to-hash. Each element
653 contains a number of keys. Most of them are attributes from the
654 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
655 Koha database. Other keys include:
659 =item C<$data-E<gt>{branchname}>
661 The name (not the code) of the branch to which the book belongs.
663 =item C<$data-E<gt>{datelastseen}>
665 This is simply C<items.datelastseen>, except that while the date is
666 stored in YYYY-MM-DD format in the database, here it is converted to
667 DD/MM/YYYY format. A NULL date is returned as C<//>.
669 =item C<$data-E<gt>{datedue}>
671 =item C<$data-E<gt>{class}>
673 This is the concatenation of C<biblioitems.classification>, the book's
674 Dewey code, and C<biblioitems.subclass>.
676 =item C<$data-E<gt>{ocount}>
678 I think this is the number of copies of the book available.
680 =item C<$data-E<gt>{order}>
682 If this is set, it is set to C<One Order>.
689 my ( $biblionumber ) = @_;
690 my $dbh = C4::Context->dbh;
691 require C4::Languages;
692 my $language = C4::Languages::getlanguage();
698 biblioitems.itemtype,
701 biblioitems.publicationyear,
702 biblioitems.publishercode,
703 biblioitems.volumedate,
704 biblioitems.volumedesc,
707 items.notforloan as itemnotforloan,
708 issues.borrowernumber,
709 issues.date_due as datedue,
710 issues.onsite_checkout,
711 borrowers.cardnumber,
714 borrowers.branchcode as bcode,
716 serial.publisheddate,
717 itemtypes.description,
718 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
719 itemtypes.notforloan as notforloan_per_itemtype,
723 holding.opac_info as holding_branch_opac_info,
724 home.opac_info as home_branch_opac_info,
725 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
727 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
728 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
729 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
730 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
731 LEFT JOIN issues USING (itemnumber)
732 LEFT JOIN borrowers USING (borrowernumber)
733 LEFT JOIN serialitems USING (itemnumber)
734 LEFT JOIN serial USING (serialid)
735 LEFT JOIN itemtypes ON itemtypes.itemtype = "
736 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
738 LEFT JOIN tmp_holdsqueue USING (itemnumber)
739 LEFT JOIN localization ON itemtypes.itemtype = localization.code
740 AND localization.entity = 'itemtypes'
741 AND localization.lang = ?
744 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
745 my $sth = $dbh->prepare($query);
746 $sth->execute($language, $biblionumber);
751 my $userenv = C4::Context->userenv;
752 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
753 while ( my $data = $sth->fetchrow_hashref ) {
754 if ( $data->{borrowernumber} && $want_not_same_branch) {
755 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
758 $serial ||= $data->{'serial'};
761 # get notforloan complete status if applicable
762 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
763 $data->{notforloanvalue} = $descriptions->{lib} // '';
764 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
766 # get restricted status and description if applicable
767 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
768 $data->{restrictedvalue} = $descriptions->{lib} // '';
769 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
771 # my stack procedures
772 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
773 $data->{stack} = $descriptions->{lib} // '';
775 # Find the last 3 people who borrowed this item.
776 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
778 AND old_issues.borrowernumber = borrowers.borrowernumber
779 ORDER BY returndate DESC
781 $sth2->execute($data->{'itemnumber'});
783 while (my $data2 = $sth2->fetchrow_hashref()) {
784 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
785 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
786 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
790 $results[$i] = $data;
795 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
799 =head2 GetItemsLocationInfo
801 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
803 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
805 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
809 =item C<$data-E<gt>{homebranch}>
811 Branch Name of the item's homebranch
813 =item C<$data-E<gt>{holdingbranch}>
815 Branch Name of the item's holdingbranch
817 =item C<$data-E<gt>{location}>
819 Item's shelving location code
821 =item C<$data-E<gt>{location_intranet}>
823 The intranet description for the Shelving Location as set in authorised_values 'LOC'
825 =item C<$data-E<gt>{location_opac}>
827 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
830 =item C<$data-E<gt>{itemcallnumber}>
832 Item's itemcallnumber
834 =item C<$data-E<gt>{cn_sort}>
836 Item's call number normalized for sorting
842 sub GetItemsLocationInfo {
843 my $biblionumber = shift;
846 my $dbh = C4::Context->dbh;
847 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
848 location, itemcallnumber, cn_sort
849 FROM items, branches as a, branches as b
850 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
852 ORDER BY cn_sort ASC";
853 my $sth = $dbh->prepare($query);
854 $sth->execute($biblionumber);
856 while ( my $data = $sth->fetchrow_hashref ) {
857 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
858 $av = $av->count ? $av->next : undef;
859 $data->{location_intranet} = $av ? $av->lib : '';
860 $data->{location_opac} = $av ? $av->opac_description : '';
861 push @results, $data;
866 =head2 GetHostItemsInfo
868 $hostiteminfo = GetHostItemsInfo($hostfield);
869 Returns the iteminfo for items linked to records via a host field
873 sub GetHostItemsInfo {
877 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
878 return @returnitemsInfo;
882 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
883 C4::Context->preference('marcflavour') eq 'NORMARC') {
884 @fields = $record->field('773');
885 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
886 @fields = $record->field('461');
889 foreach my $hostfield ( @fields ) {
890 my $hostbiblionumber = $hostfield->subfield("0");
891 my $linkeditemnumber = $hostfield->subfield("9");
892 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
893 foreach my $hostitemInfo (@hostitemInfos) {
894 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
895 push @returnitemsInfo, $hostitemInfo;
900 return @returnitemsInfo;
903 =head2 get_hostitemnumbers_of
905 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
907 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
909 Return a reference on a hash where key is a biblionumber and values are
910 references on array of itemnumbers.
915 sub get_hostitemnumbers_of {
916 my ($biblionumber) = @_;
918 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
922 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
923 return unless $marcrecord;
925 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
927 my $marcflavor = C4::Context->preference('marcflavour');
928 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
933 elsif ( $marcflavor eq 'UNIMARC' ) {
939 foreach my $hostfield ( $marcrecord->field($tag) ) {
940 my $hostbiblionumber = $hostfield->subfield($biblio_s);
941 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
942 my $linkeditemnumber = $hostfield->subfield($item_s);
943 if ( ! $linkeditemnumber ) {
944 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
947 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
948 push @returnhostitemnumbers, $linkeditemnumber
952 return @returnhostitemnumbers;
955 =head2 GetHiddenItemnumbers
957 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
959 Given a list of items it checks which should be hidden from the OPAC given
960 the current configuration. Returns a list of itemnumbers corresponding to
961 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
966 sub GetHiddenItemnumbers {
968 my $items = $params->{items};
969 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
970 foreach my $except (split(/\|/, $exceptions)){
971 if ($params->{'borcat'} eq $except){
972 return; # we don't hide anything for this borrower category
978 my $yaml = C4::Context->preference('OpacHiddenItems');
979 return () if (! $yaml =~ /\S/ );
980 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
983 $hidingrules = YAML::Load($yaml);
986 warn "Unable to parse OpacHiddenItems syspref : $@";
989 my $dbh = C4::Context->dbh;
992 foreach my $item (@$items) {
995 foreach my $field (keys %$hidingrules) {
997 if (exists $item->{$field}) {
998 $val = $item->{$field};
1001 my $query = "SELECT $field from items where itemnumber = ?";
1002 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1004 $val = '' unless defined $val;
1006 # If the results matches the values in the yaml file
1007 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1009 # We add the itemnumber to the list
1010 push @resultitems, $item->{'itemnumber'};
1012 # If at least one rule matched for an item, no need to test the others
1017 return @resultitems;
1020 =head1 LIMITED USE FUNCTIONS
1022 The following functions, while part of the public API,
1023 are not exported. This is generally because they are
1024 meant to be used by only one script for a specific
1025 purpose, and should not be used in any other context
1026 without careful thought.
1032 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1034 Returns MARC::Record of the item passed in parameter.
1035 This function is meant for use only in C<cataloguing/additem.pl>,
1036 where it is needed to support that script's MARC-like
1042 my ( $biblionumber, $itemnumber ) = @_;
1044 # GetMarcItem has been revised so that it does the following:
1045 # 1. Gets the item information from the items table.
1046 # 2. Converts it to a MARC field for storage in the bib record.
1048 # The previous behavior was:
1049 # 1. Get the bib record.
1050 # 2. Return the MARC tag corresponding to the item record.
1052 # The difference is that one treats the items row as authoritative,
1053 # while the other treats the MARC representation as authoritative
1054 # under certain circumstances.
1056 my $item = Koha::Items->find($itemnumber) or return;
1058 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1059 # Also, don't emit a subfield if the underlying field is blank.
1061 return Item2Marc($item->unblessed, $biblionumber);
1065 my ($itemrecord,$biblionumber)=@_;
1068 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1069 } keys %{ $itemrecord }
1071 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1072 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1073 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1074 "items.itemnumber", $framework,
1077 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1078 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1079 foreach my $field ($itemmarc->field($itemtag)){
1080 $field->add_subfields(@$unlinked_item_subfields);
1086 =head1 PRIVATE FUNCTIONS AND VARIABLES
1088 The following functions are not meant to be called
1089 directly, but are documented in order to explain
1090 the inner workings of C<C4::Items>.
1094 =head2 MoveItemFromBiblio
1096 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1098 Moves an item from a biblio to another
1100 Returns undef if the move failed or the biblionumber of the destination record otherwise
1104 sub MoveItemFromBiblio {
1105 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1106 my $dbh = C4::Context->dbh;
1107 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1108 SELECT biblioitemnumber
1110 WHERE biblionumber = ?
1111 |, undef, $tobiblio );
1112 my $return = $dbh->do(q|
1114 SET biblioitemnumber = ?,
1116 WHERE itemnumber = ?
1117 AND biblionumber = ?
1118 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1120 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1121 $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
1122 $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
1123 # Checking if the item we want to move is in an order
1124 require C4::Acquisition;
1125 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1127 # Replacing the biblionumber within the order if necessary
1128 $order->{'biblionumber'} = $tobiblio;
1129 C4::Acquisition::ModOrder($order);
1132 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1133 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1136 SET biblionumber = ?
1137 WHERE itemnumber = ?
1138 |, undef, $tobiblio, $itemnumber );
1145 =head2 _marc_from_item_hash
1147 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1149 Given an item hash representing a complete item record,
1150 create a C<MARC::Record> object containing an embedded
1151 tag representing that item.
1153 The third, optional parameter C<$unlinked_item_subfields> is
1154 an arrayref of subfields (not mapped to C<items> fields per the
1155 framework) to be added to the MARC representation
1160 sub _marc_from_item_hash {
1162 my $frameworkcode = shift;
1163 my $unlinked_item_subfields;
1165 $unlinked_item_subfields = shift;
1168 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1169 # Also, don't emit a subfield if the underlying field is blank.
1170 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1171 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1172 : () } keys %{ $item } };
1174 my $item_marc = MARC::Record->new();
1175 foreach my $item_field ( keys %{$mungeditem} ) {
1176 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1177 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1178 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1179 foreach my $value (@values){
1180 if ( my $field = $item_marc->field($tag) ) {
1181 $field->add_subfields( $subfield => $value );
1183 my $add_subfields = [];
1184 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1185 $add_subfields = $unlinked_item_subfields;
1187 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1195 =head2 _repack_item_errors
1197 Add an error message hash generated by C<CheckItemPreSave>
1198 to a list of errors.
1202 sub _repack_item_errors {
1203 my $item_sequence_num = shift;
1204 my $item_ref = shift;
1205 my $error_ref = shift;
1207 my @repacked_errors = ();
1209 foreach my $error_code (sort keys %{ $error_ref }) {
1210 my $repacked_error = {};
1211 $repacked_error->{'item_sequence'} = $item_sequence_num;
1212 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1213 $repacked_error->{'error_code'} = $error_code;
1214 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1215 push @repacked_errors, $repacked_error;
1218 return @repacked_errors;
1221 =head2 _get_unlinked_item_subfields
1223 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1227 sub _get_unlinked_item_subfields {
1228 my $original_item_marc = shift;
1229 my $frameworkcode = shift;
1231 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1233 # assume that this record has only one field, and that that
1234 # field contains only the item information
1236 my @fields = $original_item_marc->fields();
1237 if ($#fields > -1) {
1238 my $field = $fields[0];
1239 my $tag = $field->tag();
1240 foreach my $subfield ($field->subfields()) {
1241 if (defined $subfield->[1] and
1242 $subfield->[1] ne '' and
1243 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1244 push @$subfields, $subfield->[0] => $subfield->[1];
1251 =head2 _get_unlinked_subfields_xml
1253 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1257 sub _get_unlinked_subfields_xml {
1258 my $unlinked_item_subfields = shift;
1261 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1262 my $marc = MARC::Record->new();
1263 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1264 # used in the framework
1265 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1266 $marc->encoding("UTF-8");
1267 $xml = $marc->as_xml("USMARC");
1273 =head2 _parse_unlinked_item_subfields_from_xml
1275 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1279 sub _parse_unlinked_item_subfields_from_xml {
1281 require C4::Charset;
1282 return unless defined $xml and $xml ne "";
1283 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1284 my $unlinked_subfields = [];
1285 my @fields = $marc->fields();
1286 if ($#fields > -1) {
1287 foreach my $subfield ($fields[0]->subfields()) {
1288 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1291 return $unlinked_subfields;
1294 =head2 GetAnalyticsCount
1296 $count= &GetAnalyticsCount($itemnumber)
1298 counts Usage of itemnumber in Analytical bibliorecords.
1302 sub GetAnalyticsCount {
1303 my ($itemnumber) = @_;
1305 ### ZOOM search here
1307 $query= "hi=".$itemnumber;
1308 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1309 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1313 sub _SearchItems_build_where_fragment {
1316 my $dbh = C4::Context->dbh;
1319 if (exists($filter->{conjunction})) {
1320 my (@where_strs, @where_args);
1321 foreach my $f (@{ $filter->{filters} }) {
1322 my $fragment = _SearchItems_build_where_fragment($f);
1324 push @where_strs, $fragment->{str};
1325 push @where_args, @{ $fragment->{args} };
1330 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1333 args => \@where_args,
1337 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1338 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1339 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1340 my @operators = qw(= != > < >= <= like);
1341 push @operators, 'not like';
1342 my $field = $filter->{field} // q{};
1343 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1344 my $op = $filter->{operator};
1345 my $query = $filter->{query};
1346 my $ifnull = $filter->{ifnull};
1348 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1349 $op = '='; # default operator
1353 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1355 my $marcsubfield = $2;
1356 my ($kohafield) = $dbh->selectrow_array(q|
1357 SELECT kohafield FROM marc_subfield_structure
1358 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1359 |, undef, $marcfield, $marcsubfield);
1362 $column = $kohafield;
1364 # MARC field is not linked to a DB field so we need to use
1365 # ExtractValue on marcxml from biblio_metadata or
1366 # items.more_subfields_xml, depending on the MARC field.
1369 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1370 if ($marcfield eq $itemfield) {
1371 $sqlfield = 'more_subfields_xml';
1372 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1374 $sqlfield = 'metadata'; # From biblio_metadata
1375 if ($marcfield < 10) {
1376 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1378 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1381 $column = "ExtractValue($sqlfield, '$xpath')";
1387 if ( defined $ifnull ) {
1388 $column = "COALESCE($column, ?)";
1391 if (ref $query eq 'ARRAY') {
1394 } elsif ($op eq '!=') {
1398 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1403 str => "$column $op ?",
1408 if ( defined $ifnull ) {
1409 unshift @{ $where_fragment->{args} }, $ifnull;
1414 return $where_fragment;
1419 my ($items, $total) = SearchItems($filter, $params);
1421 Perform a search among items
1423 $filter is a reference to a hash which can be a filter, or a combination of filters.
1425 A filter has the following keys:
1429 =item * field: the name of a SQL column in table items
1431 =item * query: the value to search in this column
1433 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1437 A combination of filters hash the following keys:
1441 =item * conjunction: 'AND' or 'OR'
1443 =item * filters: array ref of filters
1447 $params is a reference to a hash that can contain the following parameters:
1451 =item * rows: Number of items to return. 0 returns everything (default: 0)
1453 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1456 =item * sortby: A SQL column name in items table to sort on
1458 =item * sortorder: 'ASC' or 'DESC'
1465 my ($filter, $params) = @_;
1469 return unless ref $filter eq 'HASH';
1470 return unless ref $params eq 'HASH';
1472 # Default parameters
1473 $params->{rows} ||= 0;
1474 $params->{page} ||= 1;
1475 $params->{sortby} ||= 'itemnumber';
1476 $params->{sortorder} ||= 'ASC';
1478 my ($where_str, @where_args);
1479 my $where_fragment = _SearchItems_build_where_fragment($filter);
1480 if ($where_fragment) {
1481 $where_str = $where_fragment->{str};
1482 @where_args = @{ $where_fragment->{args} };
1485 my $dbh = C4::Context->dbh;
1487 SELECT SQL_CALC_FOUND_ROWS items.*
1489 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1490 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1491 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1494 if (defined $where_str and $where_str ne '') {
1495 $query .= qq{ AND $where_str };
1498 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1499 push @where_args, C4::Context->preference('marcflavour');
1501 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1502 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1503 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1504 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1505 ? $params->{sortby} : 'itemnumber';
1506 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1507 $query .= qq{ ORDER BY $sortby $sortorder };
1509 my $rows = $params->{rows};
1512 my $offset = $rows * ($params->{page}-1);
1513 $query .= qq { LIMIT ?, ? };
1514 push @limit_args, $offset, $rows;
1517 my $sth = $dbh->prepare($query);
1518 my $rv = $sth->execute(@where_args, @limit_args);
1520 return unless ($rv);
1521 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1523 return ($sth->fetchall_arrayref({}), $total_rows);
1527 =head1 OTHER FUNCTIONS
1531 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1533 Find the given $subfield in the given $tag in the given
1534 MARC::Record $record. If the subfield is found, returns
1535 the (indicators, value) pair; otherwise, (undef, undef) is
1539 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1540 I suggest we export it from this module.
1545 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1548 if ( $tagfield < 10 ) {
1549 if ( $record->field($tagfield) ) {
1550 push @result, $record->field($tagfield)->data();
1555 foreach my $field ( $record->field($tagfield) ) {
1556 my @subfields = $field->subfields();
1557 foreach my $subfield (@subfields) {
1558 if ( @$subfield[0] eq $insubfield ) {
1559 push @result, @$subfield[1];
1560 $indicator = $field->indicator(1) . $field->indicator(2);
1565 return ( $indicator, @result );
1569 =head2 PrepareItemrecordDisplay
1571 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1573 Returns a hash with all the fields for Display a given item data in a template
1575 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1579 sub PrepareItemrecordDisplay {
1581 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1583 my $dbh = C4::Context->dbh;
1584 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1585 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1587 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1588 # a shared data structure. No plugin (including custom ones) should change
1589 # its contents. See also GetMarcStructure.
1590 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1592 # return nothing if we don't have found an existing framework.
1593 return q{} unless $tagslib;
1596 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1600 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1602 SELECT authorised_value,lib FROM authorised_values
1605 LEFT JOIN authorised_values_branches ON ( id = av_id )
1610 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1611 $query .= qq{ ORDER BY lib};
1612 my $authorised_values_sth = $dbh->prepare( $query );
1613 foreach my $tag ( sort keys %{$tagslib} ) {
1616 # loop through each subfield
1618 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1619 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1620 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1621 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1623 $subfield_data{tag} = $tag;
1624 $subfield_data{subfield} = $subfield;
1625 $subfield_data{countsubfield} = $cntsubf++;
1626 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1627 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1629 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1630 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1631 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1632 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1633 $subfield_data{hidden} = "display:none"
1634 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1635 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1636 my ( $x, $defaultvalue );
1638 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1640 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1641 if ( !defined $defaultvalue ) {
1642 $defaultvalue = q||;
1644 $defaultvalue =~ s/"/"/g;
1645 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1646 my $today_dt = dt_from_string;
1647 my $year = $today_dt->strftime('%Y');
1648 my $shortyear = $today_dt->strftime('%y');
1649 my $month = $today_dt->strftime('%m');
1650 my $day = $today_dt->strftime('%d');
1651 $defaultvalue =~ s/<<YYYY>>/$year/g;
1652 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1653 $defaultvalue =~ s/<<MM>>/$month/g;
1654 $defaultvalue =~ s/<<DD>>/$day/g;
1656 # And <<USER>> with surname (?)
1658 ( C4::Context->userenv
1659 ? C4::Context->userenv->{'surname'}
1660 : "superlibrarian" );
1661 $defaultvalue =~ s/<<USER>>/$username/g;
1664 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1666 # search for itemcallnumber if applicable
1667 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1668 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1669 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1670 my $CNtag = substr( $itemcn_pref, 0, 3 );
1671 next unless my $field = $itemrecord->field($CNtag);
1672 my $CNsubfields = substr( $itemcn_pref, 3 );
1673 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1674 last if $defaultvalue;
1677 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1679 && $defaultvalues->{'callnumber'} ) {
1680 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1681 # if the item record exists, only use default value if the item has no callnumber
1682 $defaultvalue = $defaultvalues->{callnumber};
1683 } elsif ( !$itemrecord and $defaultvalues ) {
1684 # if the item record *doesn't* exists, always use the default value
1685 $defaultvalue = $defaultvalues->{callnumber};
1688 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1690 && $defaultvalues->{'branchcode'} ) {
1691 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1692 $defaultvalue = $defaultvalues->{branchcode};
1695 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1697 && $defaultvalues->{'location'} ) {
1699 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1700 # if the item record exists, only use default value if the item has no locationr
1701 $defaultvalue = $defaultvalues->{location};
1702 } elsif ( !$itemrecord and $defaultvalues ) {
1703 # if the item record *doesn't* exists, always use the default value
1704 $defaultvalue = $defaultvalues->{location};
1707 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1708 my @authorised_values;
1711 # builds list, depending on authorised value...
1713 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1714 if ( ( C4::Context->preference("IndependentBranches") )
1715 && !C4::Context->IsSuperLibrarian() ) {
1716 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1717 $sth->execute( C4::Context->userenv->{branch} );
1718 push @authorised_values, ""
1719 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1720 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1721 push @authorised_values, $branchcode;
1722 $authorised_lib{$branchcode} = $branchname;
1725 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1727 push @authorised_values, ""
1728 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1729 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1730 push @authorised_values, $branchcode;
1731 $authorised_lib{$branchcode} = $branchname;
1735 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1736 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1737 $defaultvalue = $defaultvalues->{branchcode};
1741 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1742 my $itemtypes = Koha::ItemTypes->search_with_localization;
1743 push @authorised_values, "";
1744 while ( my $itemtype = $itemtypes->next ) {
1745 push @authorised_values, $itemtype->itemtype;
1746 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1748 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1749 $defaultvalue = $defaultvalues->{'itemtype'};
1753 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1754 push @authorised_values, "";
1756 my $class_sources = GetClassSources();
1757 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1759 foreach my $class_source (sort keys %$class_sources) {
1760 next unless $class_sources->{$class_source}->{'used'} or
1761 ($class_source eq $default_source);
1762 push @authorised_values, $class_source;
1763 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1766 $defaultvalue = $default_source;
1768 #---- "true" authorised value
1770 $authorised_values_sth->execute(
1771 $tagslib->{$tag}->{$subfield}->{authorised_value},
1772 $branch_limit ? $branch_limit : ()
1774 push @authorised_values, "";
1775 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1776 push @authorised_values, $value;
1777 $authorised_lib{$value} = $lib;
1780 $subfield_data{marc_value} = {
1782 values => \@authorised_values,
1783 default => $defaultvalue // q{},
1784 labels => \%authorised_lib,
1786 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1788 require Koha::FrameworkPlugin;
1789 my $plugin = Koha::FrameworkPlugin->new({
1790 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1793 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1794 $plugin->build( $pars );
1795 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1796 $defaultvalue = $field->subfield($subfield) || q{};
1798 if( !$plugin->errstr ) {
1799 #TODO Move html to template; see report 12176/13397
1800 my $tab= $plugin->noclick? '-1': '';
1801 my $class= $plugin->noclick? ' disabled': '';
1802 my $title= $plugin->noclick? 'No popup': 'Tag editor';
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" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1805 warn $plugin->errstr;
1806 $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
1809 elsif ( $tag eq '' ) { # it's an hidden field
1810 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1812 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1813 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1815 elsif ( length($defaultvalue) > 100
1816 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1817 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1818 or (C4::Context->preference("marcflavour") eq "MARC21" and
1819 500 <= $tag && $tag < 600 )
1821 # oversize field (textarea)
1822 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1824 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1826 push( @loop_data, \%subfield_data );
1831 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1832 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1835 'itemtagfield' => $itemtagfield,
1836 'itemtagsubfield' => $itemtagsubfield,
1837 'itemnumber' => $itemnumber,
1838 'iteminformation' => \@loop_data
1842 sub ToggleNewStatus {
1843 my ( $params ) = @_;
1844 my @rules = @{ $params->{rules} };
1845 my $report_only = $params->{report_only};
1847 my $dbh = C4::Context->dbh;
1849 my @item_columns = map { "items.$_" } Koha::Items->columns;
1850 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1852 for my $rule ( @rules ) {
1853 my $age = $rule->{age};
1854 my $conditions = $rule->{conditions};
1855 my $substitutions = $rule->{substitutions};
1856 foreach ( @$substitutions ) {
1857 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1864 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1867 for my $condition ( @$conditions ) {
1869 grep { $_ eq $condition->{field} } @item_columns
1870 or grep { $_ eq $condition->{field} } @biblioitem_columns
1872 if ( $condition->{value} =~ /\|/ ) {
1873 my @values = split /\|/, $condition->{value};
1874 $query .= qq| AND $condition->{field} IN (|
1875 . join( ',', ('?') x scalar @values )
1877 push @params, @values;
1879 $query .= qq| AND $condition->{field} = ?|;
1880 push @params, $condition->{value};
1884 if ( defined $age ) {
1885 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1888 my $sth = $dbh->prepare($query);
1889 $sth->execute( @params );
1890 while ( my $values = $sth->fetchrow_hashref ) {
1891 my $biblionumber = $values->{biblionumber};
1892 my $itemnumber = $values->{itemnumber};
1893 my $item = Koha::Items->find($itemnumber);
1894 for my $substitution ( @$substitutions ) {
1895 my $field = $substitution->{item_field};
1896 my $value = $substitution->{value};
1897 next unless $substitution->{field};
1898 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1899 $item->$field($value);
1900 push @{ $report->{$itemnumber} }, $substitution;
1902 $item->store unless $report_only;