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 our (@ISA, @EXPORT_OK);
40 get_hostitemnumbers_of
46 PrepareItemrecordDisplay
54 use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
55 use Koha::DateUtils qw( dt_from_string output_pref );
57 use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
58 use C4::Log qw( logaction );
59 use List::MoreUtils qw( any );
60 use DateTime::Format::MySQL;
61 # debugging; so please don't remove this
63 use Koha::AuthorisedValues;
64 use Koha::DateUtils qw( dt_from_string output_pref );
67 use Koha::Biblioitems;
70 use Koha::SearchEngine;
71 use Koha::SearchEngine::Indexer;
72 use Koha::SearchEngine::Search;
77 C4::Items - item management functions
81 This module contains an API for manipulating item
82 records in Koha, and is used by cataloguing, circulation,
83 acquisitions, and serials management.
85 # FIXME This POD is not up-to-date
86 A Koha item record is stored in two places: the
87 items table and embedded in a MARC tag in the XML
88 version of the associated bib record in C<biblioitems.marcxml>.
89 This is done to allow the item information to be readily
90 indexed (e.g., by Zebra), but means that each item
91 modification transaction must keep the items table
92 and the MARC XML in sync at all times.
94 The items table will be considered authoritative. In other
95 words, if there is ever a discrepancy between the items
96 table and the MARC XML, the items table should be considered
99 =head1 HISTORICAL NOTE
101 Most of the functions in C<C4::Items> were originally in
102 the C<C4::Biblio> module.
104 =head1 CORE EXPORTED FUNCTIONS
106 The following functions are meant for use by users
113 CartToShelf($itemnumber);
115 Set the current shelving location of the item record
116 to its stored permanent shelving location. This is
117 primarily used to indicate when an item whose current
118 location is a special processing ('PROC') or shelving cart
119 ('CART') location is back in the stacks.
124 my ( $itemnumber ) = @_;
126 unless ( $itemnumber ) {
127 croak "FAILED CartToShelf() - no itemnumber supplied";
130 my $item = Koha::Items->find($itemnumber);
131 if ( $item->location eq 'CART' ) {
132 $item->location($item->permanent_location)->store;
136 =head2 AddItemFromMarc
138 my ($biblionumber, $biblioitemnumber, $itemnumber)
139 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
141 Given a MARC::Record object containing an embedded item
142 record and a biblionumber, create a new item record.
144 The final optional parameter, C<$params>, expected to contain
145 'skip_record_index' key, which relayed down to Koha::Item/store,
146 there it prevents calling of index_records,
147 which takes most of the time in batch adds/deletes: index_records
148 to be called later in C<additem.pl> after the whole loop.
151 skip_record_index => 1|0
155 sub AddItemFromMarc {
156 my $source_item_marc = shift;
157 my $biblionumber = shift;
158 my $params = @_ ? shift : {};
160 my $dbh = C4::Context->dbh;
162 # parse item hash from MARC
163 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
164 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
166 my $localitemmarc = MARC::Record->new;
167 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
169 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
170 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
171 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
172 $item_values->{biblionumber} = $biblionumber;
173 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
174 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
175 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
176 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
179 =head2 AddItemBatchFromMarc
181 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
182 $biblionumber, $biblioitemnumber, $frameworkcode);
184 Efficiently create item records from a MARC biblio record with
185 embedded item fields. This routine is suitable for batch jobs.
187 This API assumes that the bib record has already been
188 saved to the C<biblio> and C<biblioitems> tables. It does
189 not expect that C<biblio_metadata.metadata> is populated, but it
190 will do so via a call to ModBibiloMarc.
192 The goal of this API is to have a similar effect to using AddBiblio
193 and AddItems in succession, but without inefficient repeated
194 parsing of the MARC XML bib record.
196 This function returns an arrayref of new itemsnumbers and an arrayref of item
197 errors encountered during the processing. Each entry in the errors
198 list is a hashref containing the following keys:
204 Sequence number of original item tag in the MARC record.
208 Item barcode, provide to assist in the construction of
209 useful error messages.
213 Code representing the error condition. Can be 'duplicate_barcode',
214 'invalid_homebranch', or 'invalid_holdingbranch'.
216 =item error_information
218 Additional information appropriate to the error condition.
224 sub AddItemBatchFromMarc {
225 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
226 my @itemnumbers = ();
228 my $dbh = C4::Context->dbh;
230 # We modify the record, so lets work on a clone so we don't change the
232 $record = $record->clone();
233 # loop through the item tags and start creating items
234 my @bad_item_fields = ();
235 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
236 my $item_sequence_num = 0;
237 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
238 $item_sequence_num++;
239 # we take the item field and stick it into a new
240 # MARC record -- this is required so far because (FIXME)
241 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
242 # and there is no TransformMarcFieldToKoha
243 my $temp_item_marc = MARC::Record->new();
244 $temp_item_marc->append_fields($item_field);
246 # add biblionumber and biblioitemnumber
247 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
248 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
249 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
250 $item->{'biblionumber'} = $biblionumber;
251 $item->{'biblioitemnumber'} = $biblioitemnumber;
252 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
253 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
255 # check for duplicate barcode
256 my %item_errors = CheckItemPreSave($item);
258 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
259 push @bad_item_fields, $item_field;
263 my $item_object = Koha::Item->new($item)->store;
264 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
266 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
268 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
269 $item_field->replace_with($new_item_marc->field($itemtag));
272 # remove any MARC item fields for rejected items
273 foreach my $item_field (@bad_item_fields) {
274 $record->delete_field($item_field);
277 return (\@itemnumbers, \@errors);
280 =head2 ModItemFromMarc
282 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
284 The final optional parameter, C<$params>, expected to contain
285 'skip_record_index' key, which relayed down to Koha::Item/store,
286 there it prevents calling of index_records,
287 which takes most of the time in batch adds/deletes: index_records better
288 to be called later in C<additem.pl> after the whole loop.
291 skip_record_index => 1|0
295 sub ModItemFromMarc {
296 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
298 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
299 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
301 my $localitemmarc = MARC::Record->new;
302 $localitemmarc->append_fields( $item_marc->field($itemtag) );
303 my $item_object = Koha::Items->find($itemnumber);
304 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
306 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
307 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
309 # Retrieving the values for the fields that are not linked
310 my @mapped_fields = Koha::MarcSubfieldStructures->search(
312 frameworkcode => $frameworkcode,
313 kohafield => { -like => "items.%" }
315 )->get_column('kohafield');
316 for my $c ( $item_object->_result->result_source->columns ) {
317 next if grep { "items.$c" eq $_ } @mapped_fields;
318 $item->{$c} = $item_object->$c;
321 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
322 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
323 $item->{itemnumber} = $itemnumber;
324 $item->{biblionumber} = $biblionumber;
326 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
327 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
328 $item_object = $item_object->set_or_blank($item);
329 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
331 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
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 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
357 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
358 # will have been preceded by a check of branch transfer limits)
359 my $to_library = Koha::Libraries->find($tobranch);
360 my $transfer = $item->request_transfer(
369 # Immediately set the item to in transit if it is checked in
370 if ( !$item->checkout ) {
371 $item->holdingbranch($frombranch)->store(
374 skip_record_index => $params->{skip_record_index}
383 =head2 ModDateLastSeen
385 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
387 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
388 C<$itemnumber> is the item number
389 C<$leave_item_lost> determines if a lost item will be found or remain lost
391 The last optional parameter allows for passing skip_record_index through to the items store call.
395 sub ModDateLastSeen {
396 my ( $itemnumber, $leave_item_lost, $params ) = @_;
398 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
400 my $item = Koha::Items->find($itemnumber);
401 $item->datelastseen($today);
402 $item->itemlost(0) unless $leave_item_lost;
403 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
406 =head2 CheckItemPreSave
408 my $item_ref = TransformMarcToKoha($marc, 'items');
410 my %errors = CheckItemPreSave($item_ref);
411 if (exists $errors{'duplicate_barcode'}) {
412 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
413 } elsif (exists $errors{'invalid_homebranch'}) {
414 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
415 } elsif (exists $errors{'invalid_holdingbranch'}) {
416 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
421 Given a hashref containing item fields, determine if it can be
422 inserted or updated in the database. Specifically, checks for
423 database integrity issues, and returns a hash containing any
424 of the following keys, if applicable.
428 =item duplicate_barcode
430 Barcode, if it duplicates one already found in the database.
432 =item invalid_homebranch
434 Home branch, if not defined in branches table.
436 =item invalid_holdingbranch
438 Holding branch, if not defined in branches table.
442 This function does NOT implement any policy-related checks,
443 e.g., whether current operator is allowed to save an
444 item that has a given branch code.
448 sub CheckItemPreSave {
449 my $item_ref = shift;
453 # check for duplicate barcode
454 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
455 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
456 if ($existing_item) {
457 if (!exists $item_ref->{'itemnumber'} # new item
458 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
459 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
464 # check for valid home branch
465 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
466 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
467 unless (defined $home_library) {
468 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
472 # check for valid holding branch
473 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
474 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
475 unless (defined $holding_library) {
476 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
484 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
486 The following functions provide various ways of
487 getting an item record, a set of item records, or
488 lists of authorized values for certain item fields.
492 =head2 GetItemsForInventory
494 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
495 minlocation => $minlocation,
496 maxlocation => $maxlocation,
497 location => $location,
498 ignoreissued => $ignoreissued,
499 datelastseen => $datelastseen,
500 branchcode => $branchcode,
504 statushash => $statushash,
505 itemtypes => \@itemsarray,
508 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
510 The sub returns a reference to a list of hashes, each containing
511 itemnumber, author, title, barcode, item callnumber, and date last
512 seen. It is ordered by callnumber then title.
514 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
515 the datelastseen can be used to specify that you want to see items not seen since a past date only.
516 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
517 $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.
519 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
523 sub GetItemsForInventory {
524 my ( $parameters ) = @_;
525 my $minlocation = $parameters->{'minlocation'} // '';
526 my $maxlocation = $parameters->{'maxlocation'} // '';
527 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
528 my $location = $parameters->{'location'} // '';
529 my $itemtype = $parameters->{'itemtype'} // '';
530 my $ignoreissued = $parameters->{'ignoreissued'} // '';
531 my $datelastseen = $parameters->{'datelastseen'} // '';
532 my $branchcode = $parameters->{'branchcode'} // '';
533 my $branch = $parameters->{'branch'} // '';
534 my $offset = $parameters->{'offset'} // '';
535 my $size = $parameters->{'size'} // '';
536 my $statushash = $parameters->{'statushash'} // '';
537 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
538 my $itemtypes = $parameters->{'itemtypes'} || [];
539 my $ccode = $parameters->{'ccode'} // '';
541 my $dbh = C4::Context->dbh;
542 my ( @bind_params, @where_strings );
544 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
545 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
547 my $select_columns = q{
548 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
551 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
554 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
555 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
558 for my $authvfield (keys %$statushash){
559 if ( scalar @{$statushash->{$authvfield}} > 0 ){
560 my $joinedvals = join ',', @{$statushash->{$authvfield}};
561 push @where_strings, "$authvfield in (" . $joinedvals . ")";
567 push @where_strings, 'ccode = ?';
568 push @bind_params, $ccode;
572 push @where_strings, 'items.cn_sort >= ?';
573 push @bind_params, $min_cnsort;
577 push @where_strings, 'items.cn_sort <= ?';
578 push @bind_params, $max_cnsort;
582 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
583 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
584 push @bind_params, $datelastseen;
588 push @where_strings, 'items.location = ?';
589 push @bind_params, $location;
593 if($branch eq "homebranch"){
594 push @where_strings, 'items.homebranch = ?';
596 push @where_strings, 'items.holdingbranch = ?';
598 push @bind_params, $branchcode;
602 push @where_strings, 'biblioitems.itemtype = ?';
603 push @bind_params, $itemtype;
605 if ( $ignoreissued) {
606 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
607 push @where_strings, 'issues.date_due IS NULL';
610 if ( $ignore_waiting_holds ) {
611 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
612 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
616 my $itemtypes_str = join ', ', @$itemtypes;
617 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
620 if ( @where_strings ) {
622 $query .= join ' AND ', @where_strings;
624 my $count_query = $select_count . $query;
625 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
626 $query .= " LIMIT $offset, $size" if ($offset and $size);
627 $query = $select_columns . $query;
628 my $sth = $dbh->prepare($query);
629 $sth->execute( @bind_params );
632 my $tmpresults = $sth->fetchall_arrayref({});
633 $sth = $dbh->prepare( $count_query );
634 $sth->execute( @bind_params );
635 my ($iTotalRecords) = $sth->fetchrow_array();
637 my @avs = Koha::AuthorisedValues->search(
638 { 'marc_subfield_structures.kohafield' => { '>' => '' },
639 'me.authorised_value' => { '>' => '' },
641 { join => { category => 'marc_subfield_structures' },
642 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
643 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
644 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
648 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
650 foreach my $row (@$tmpresults) {
653 foreach (keys %$row) {
656 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
659 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
665 return (\@results, $iTotalRecords);
670 @results = GetItemsInfo($biblionumber);
672 Returns information about items with the given biblionumber.
674 C<GetItemsInfo> returns a list of references-to-hash. Each element
675 contains a number of keys. Most of them are attributes from the
676 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
677 Koha database. Other keys include:
681 =item C<$data-E<gt>{branchname}>
683 The name (not the code) of the branch to which the book belongs.
685 =item C<$data-E<gt>{datelastseen}>
687 This is simply C<items.datelastseen>, except that while the date is
688 stored in YYYY-MM-DD format in the database, here it is converted to
689 DD/MM/YYYY format. A NULL date is returned as C<//>.
691 =item C<$data-E<gt>{datedue}>
693 =item C<$data-E<gt>{class}>
695 This is the concatenation of C<biblioitems.classification>, the book's
696 Dewey code, and C<biblioitems.subclass>.
698 =item C<$data-E<gt>{ocount}>
700 I think this is the number of copies of the book available.
702 =item C<$data-E<gt>{order}>
704 If this is set, it is set to C<One Order>.
711 my ( $biblionumber ) = @_;
712 my $dbh = C4::Context->dbh;
713 require C4::Languages;
714 my $language = C4::Languages::getlanguage();
720 biblioitems.itemtype,
723 biblioitems.publicationyear,
724 biblioitems.publishercode,
725 biblioitems.volumedate,
726 biblioitems.volumedesc,
729 items.notforloan as itemnotforloan,
730 issues.borrowernumber,
731 issues.date_due as datedue,
732 issues.onsite_checkout,
733 borrowers.cardnumber,
736 borrowers.branchcode as bcode,
738 serial.publisheddate,
739 itemtypes.description,
740 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
741 itemtypes.notforloan as notforloan_per_itemtype,
745 holding.opac_info as holding_branch_opac_info,
746 home.opac_info as home_branch_opac_info,
747 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
749 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
750 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
751 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
752 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
753 LEFT JOIN issues USING (itemnumber)
754 LEFT JOIN borrowers USING (borrowernumber)
755 LEFT JOIN serialitems USING (itemnumber)
756 LEFT JOIN serial USING (serialid)
757 LEFT JOIN itemtypes ON itemtypes.itemtype = "
758 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
760 LEFT JOIN tmp_holdsqueue USING (itemnumber)
761 LEFT JOIN localization ON itemtypes.itemtype = localization.code
762 AND localization.entity = 'itemtypes'
763 AND localization.lang = ?
766 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
767 my $sth = $dbh->prepare($query);
768 $sth->execute($language, $biblionumber);
773 my $userenv = C4::Context->userenv;
774 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
775 while ( my $data = $sth->fetchrow_hashref ) {
776 if ( $data->{borrowernumber} && $want_not_same_branch) {
777 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
780 $serial ||= $data->{'serial'};
783 # get notforloan complete status if applicable
784 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
785 $data->{notforloanvalue} = $descriptions->{lib} // '';
786 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
788 # get restricted status and description if applicable
789 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
790 $data->{restrictedvalue} = $descriptions->{lib} // '';
791 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
793 # my stack procedures
794 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
795 $data->{stack} = $descriptions->{lib} // '';
797 # Find the last 3 people who borrowed this item.
798 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
800 AND old_issues.borrowernumber = borrowers.borrowernumber
801 ORDER BY returndate DESC
803 $sth2->execute($data->{'itemnumber'});
805 while (my $data2 = $sth2->fetchrow_hashref()) {
806 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
807 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
808 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
812 $results[$i] = $data;
817 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
821 =head2 GetItemsLocationInfo
823 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
825 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
827 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
831 =item C<$data-E<gt>{homebranch}>
833 Branch Name of the item's homebranch
835 =item C<$data-E<gt>{holdingbranch}>
837 Branch Name of the item's holdingbranch
839 =item C<$data-E<gt>{location}>
841 Item's shelving location code
843 =item C<$data-E<gt>{location_intranet}>
845 The intranet description for the Shelving Location as set in authorised_values 'LOC'
847 =item C<$data-E<gt>{location_opac}>
849 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
852 =item C<$data-E<gt>{itemcallnumber}>
854 Item's itemcallnumber
856 =item C<$data-E<gt>{cn_sort}>
858 Item's call number normalized for sorting
864 sub GetItemsLocationInfo {
865 my $biblionumber = shift;
868 my $dbh = C4::Context->dbh;
869 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
870 location, itemcallnumber, cn_sort
871 FROM items, branches as a, branches as b
872 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
874 ORDER BY cn_sort ASC";
875 my $sth = $dbh->prepare($query);
876 $sth->execute($biblionumber);
878 while ( my $data = $sth->fetchrow_hashref ) {
879 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
880 $av = $av->count ? $av->next : undef;
881 $data->{location_intranet} = $av ? $av->lib : '';
882 $data->{location_opac} = $av ? $av->opac_description : '';
883 push @results, $data;
888 =head2 GetHostItemsInfo
890 $hostiteminfo = GetHostItemsInfo($hostfield);
891 Returns the iteminfo for items linked to records via a host field
895 sub GetHostItemsInfo {
899 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
900 return @returnitemsInfo;
904 if( C4::Context->preference('marcflavour') eq 'MARC21' ) {
905 @fields = $record->field('773');
906 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
907 @fields = $record->field('461');
910 foreach my $hostfield ( @fields ) {
911 my $hostbiblionumber = $hostfield->subfield("0");
912 my $linkeditemnumber = $hostfield->subfield("9");
913 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
914 foreach my $hostitemInfo (@hostitemInfos) {
915 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
916 push @returnitemsInfo, $hostitemInfo;
921 return @returnitemsInfo;
924 =head2 get_hostitemnumbers_of
926 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
928 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
930 Return a reference on a hash where key is a biblionumber and values are
931 references on array of itemnumbers.
936 sub get_hostitemnumbers_of {
937 my ($biblionumber) = @_;
939 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
943 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
944 return unless $marcrecord;
946 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
948 my $marcflavor = C4::Context->preference('marcflavour');
949 if ( $marcflavor eq 'MARC21' ) {
954 elsif ( $marcflavor eq 'UNIMARC' ) {
960 foreach my $hostfield ( $marcrecord->field($tag) ) {
961 my $hostbiblionumber = $hostfield->subfield($biblio_s);
962 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
963 my $linkeditemnumber = $hostfield->subfield($item_s);
964 if ( ! $linkeditemnumber ) {
965 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
968 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
969 push @returnhostitemnumbers, $linkeditemnumber
973 return @returnhostitemnumbers;
976 =head2 GetHiddenItemnumbers
978 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
980 Given a list of items it checks which should be hidden from the OPAC given
981 the current configuration. Returns a list of itemnumbers corresponding to
982 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
987 sub GetHiddenItemnumbers {
989 my $items = $params->{items};
990 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
991 foreach my $except (split(/\|/, $exceptions)){
992 if ($params->{'borcat'} eq $except){
993 return; # we don't hide anything for this borrower category
999 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
1002 unless $hidingrules;
1004 my $dbh = C4::Context->dbh;
1007 foreach my $item (@$items) {
1009 # We check each rule
1010 foreach my $field (keys %$hidingrules) {
1012 if (exists $item->{$field}) {
1013 $val = $item->{$field};
1016 my $query = "SELECT $field from items where itemnumber = ?";
1017 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1019 $val = '' unless defined $val;
1021 # If the results matches the values in the yaml file
1022 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1024 # We add the itemnumber to the list
1025 push @resultitems, $item->{'itemnumber'};
1027 # If at least one rule matched for an item, no need to test the others
1032 return @resultitems;
1035 =head1 LIMITED USE FUNCTIONS
1037 The following functions, while part of the public API,
1038 are not exported. This is generally because they are
1039 meant to be used by only one script for a specific
1040 purpose, and should not be used in any other context
1041 without careful thought.
1047 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1049 Returns MARC::Record of the item passed in parameter.
1050 This function is meant for use only in C<cataloguing/additem.pl>,
1051 where it is needed to support that script's MARC-like
1057 my ( $biblionumber, $itemnumber ) = @_;
1059 # GetMarcItem has been revised so that it does the following:
1060 # 1. Gets the item information from the items table.
1061 # 2. Converts it to a MARC field for storage in the bib record.
1063 # The previous behavior was:
1064 # 1. Get the bib record.
1065 # 2. Return the MARC tag corresponding to the item record.
1067 # The difference is that one treats the items row as authoritative,
1068 # while the other treats the MARC representation as authoritative
1069 # under certain circumstances.
1071 my $item = Koha::Items->find($itemnumber) or return;
1073 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1074 # Also, don't emit a subfield if the underlying field is blank.
1076 return Item2Marc($item->unblessed, $biblionumber);
1080 my ($itemrecord,$biblionumber)=@_;
1083 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1084 } keys %{ $itemrecord }
1086 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1087 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1088 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1089 "items.itemnumber", $framework,
1092 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1093 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1094 foreach my $field ($itemmarc->field($itemtag)){
1095 $field->add_subfields(@$unlinked_item_subfields);
1101 =head1 PRIVATE FUNCTIONS AND VARIABLES
1103 The following functions are not meant to be called
1104 directly, but are documented in order to explain
1105 the inner workings of C<C4::Items>.
1109 =head2 _marc_from_item_hash
1111 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1113 Given an item hash representing a complete item record,
1114 create a C<MARC::Record> object containing an embedded
1115 tag representing that item.
1117 The third, optional parameter C<$unlinked_item_subfields> is
1118 an arrayref of subfields (not mapped to C<items> fields per the
1119 framework) to be added to the MARC representation
1124 sub _marc_from_item_hash {
1126 my $frameworkcode = shift;
1127 my $unlinked_item_subfields;
1129 $unlinked_item_subfields = shift;
1132 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1133 # Also, don't emit a subfield if the underlying field is blank.
1134 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1135 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1136 : () } keys %{ $item } };
1138 my $item_marc = MARC::Record->new();
1139 foreach my $item_field ( keys %{$mungeditem} ) {
1140 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1141 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1142 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1143 foreach my $value (@values){
1144 if ( my $field = $item_marc->field($tag) ) {
1145 $field->add_subfields( $subfield => $value );
1147 my $add_subfields = [];
1148 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1149 $add_subfields = $unlinked_item_subfields;
1151 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1159 =head2 _repack_item_errors
1161 Add an error message hash generated by C<CheckItemPreSave>
1162 to a list of errors.
1166 sub _repack_item_errors {
1167 my $item_sequence_num = shift;
1168 my $item_ref = shift;
1169 my $error_ref = shift;
1171 my @repacked_errors = ();
1173 foreach my $error_code (sort keys %{ $error_ref }) {
1174 my $repacked_error = {};
1175 $repacked_error->{'item_sequence'} = $item_sequence_num;
1176 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1177 $repacked_error->{'error_code'} = $error_code;
1178 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1179 push @repacked_errors, $repacked_error;
1182 return @repacked_errors;
1185 =head2 _get_unlinked_item_subfields
1187 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1191 sub _get_unlinked_item_subfields {
1192 my $original_item_marc = shift;
1193 my $frameworkcode = shift;
1195 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1197 # assume that this record has only one field, and that that
1198 # field contains only the item information
1200 my @fields = $original_item_marc->fields();
1201 if ($#fields > -1) {
1202 my $field = $fields[0];
1203 my $tag = $field->tag();
1204 foreach my $subfield ($field->subfields()) {
1205 if (defined $subfield->[1] and
1206 $subfield->[1] ne '' and
1207 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1208 push @$subfields, $subfield->[0] => $subfield->[1];
1215 =head2 _get_unlinked_subfields_xml
1217 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1221 sub _get_unlinked_subfields_xml {
1222 my $unlinked_item_subfields = shift;
1225 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1226 my $marc = MARC::Record->new();
1227 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1228 # used in the framework
1229 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1230 $marc->encoding("UTF-8");
1231 $xml = $marc->as_xml("USMARC");
1237 =head2 _parse_unlinked_item_subfields_from_xml
1239 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1243 sub _parse_unlinked_item_subfields_from_xml {
1245 require C4::Charset;
1246 return unless defined $xml and $xml ne "";
1247 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1248 my $unlinked_subfields = [];
1249 my @fields = $marc->fields();
1250 if ($#fields > -1) {
1251 foreach my $subfield ($fields[0]->subfields()) {
1252 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1255 return $unlinked_subfields;
1258 =head2 GetAnalyticsCount
1260 $count= &GetAnalyticsCount($itemnumber)
1262 counts Usage of itemnumber in Analytical bibliorecords.
1266 sub GetAnalyticsCount {
1267 my ($itemnumber) = @_;
1269 ### ZOOM search here
1271 $query= "hi=".$itemnumber;
1272 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1273 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1277 sub _SearchItems_build_where_fragment {
1280 my $dbh = C4::Context->dbh;
1283 if (exists($filter->{conjunction})) {
1284 my (@where_strs, @where_args);
1285 foreach my $f (@{ $filter->{filters} }) {
1286 my $fragment = _SearchItems_build_where_fragment($f);
1288 push @where_strs, $fragment->{str};
1289 push @where_args, @{ $fragment->{args} };
1294 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1297 args => \@where_args,
1301 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1302 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1303 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1304 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1305 my @operators = qw(= != > < >= <= is like);
1306 push @operators, 'not like';
1307 my $field = $filter->{field} // q{};
1308 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1309 my $op = $filter->{operator};
1310 my $query = $filter->{query};
1311 my $ifnull = $filter->{ifnull};
1313 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1314 $op = '='; # default operator
1318 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1320 my $marcsubfield = $2;
1321 my ($kohafield) = $dbh->selectrow_array(q|
1322 SELECT kohafield FROM marc_subfield_structure
1323 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1324 |, undef, $marcfield, $marcsubfield);
1327 $column = $kohafield;
1329 # MARC field is not linked to a DB field so we need to use
1330 # ExtractValue on marcxml from biblio_metadata or
1331 # items.more_subfields_xml, depending on the MARC field.
1334 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1335 if ($marcfield eq $itemfield) {
1336 $sqlfield = 'more_subfields_xml';
1337 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1339 $sqlfield = 'metadata'; # From biblio_metadata
1340 if ($marcfield < 10) {
1341 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1343 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1346 $column = "ExtractValue($sqlfield, '$xpath')";
1352 if ( defined $ifnull ) {
1353 $column = "COALESCE($column, ?)";
1356 if (ref $query eq 'ARRAY') {
1359 } elsif ($op eq '!=') {
1363 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1366 } elsif ( $op eq 'is' ) {
1368 str => "$column $op $query",
1373 str => "$column $op ?",
1378 if ( defined $ifnull ) {
1379 unshift @{ $where_fragment->{args} }, $ifnull;
1384 return $where_fragment;
1389 my ($items, $total) = SearchItems($filter, $params);
1391 Perform a search among items
1393 $filter is a reference to a hash which can be a filter, or a combination of filters.
1395 A filter has the following keys:
1399 =item * field: the name of a SQL column in table items
1401 =item * query: the value to search in this column
1403 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1407 A combination of filters hash the following keys:
1411 =item * conjunction: 'AND' or 'OR'
1413 =item * filters: array ref of filters
1417 $params is a reference to a hash that can contain the following parameters:
1421 =item * rows: Number of items to return. 0 returns everything (default: 0)
1423 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1426 =item * sortby: A SQL column name in items table to sort on
1428 =item * sortorder: 'ASC' or 'DESC'
1435 my ($filter, $params) = @_;
1439 return unless ref $filter eq 'HASH';
1440 return unless ref $params eq 'HASH';
1442 # Default parameters
1443 $params->{rows} ||= 0;
1444 $params->{page} ||= 1;
1445 $params->{sortby} ||= 'itemnumber';
1446 $params->{sortorder} ||= 'ASC';
1448 my ($where_str, @where_args);
1449 my $where_fragment = _SearchItems_build_where_fragment($filter);
1450 if ($where_fragment) {
1451 $where_str = $where_fragment->{str};
1452 @where_args = @{ $where_fragment->{args} };
1455 my $dbh = C4::Context->dbh;
1457 SELECT SQL_CALC_FOUND_ROWS items.*
1459 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1460 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1461 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1462 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1465 if (defined $where_str and $where_str ne '') {
1466 $query .= qq{ AND $where_str };
1469 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1470 push @where_args, C4::Context->preference('marcflavour');
1472 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1473 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1474 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1475 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1477 if ( $params->{sortby} eq 'availability' ) {
1478 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1479 $query .= qq{ ORDER BY onloan $sortorder };
1481 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1482 ? $params->{sortby} : 'itemnumber';
1483 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1484 $query .= qq{ ORDER BY $sortby $sortorder };
1487 my $rows = $params->{rows};
1490 my $offset = $rows * ($params->{page}-1);
1491 $query .= qq { LIMIT ?, ? };
1492 push @limit_args, $offset, $rows;
1495 my $sth = $dbh->prepare($query);
1496 my $rv = $sth->execute(@where_args, @limit_args);
1498 return unless ($rv);
1499 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1501 return ($sth->fetchall_arrayref({}), $total_rows);
1505 =head1 OTHER FUNCTIONS
1509 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1511 Find the given $subfield in the given $tag in the given
1512 MARC::Record $record. If the subfield is found, returns
1513 the (indicators, value) pair; otherwise, (undef, undef) is
1517 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1518 I suggest we export it from this module.
1523 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1526 if ( $tagfield < 10 ) {
1527 if ( $record->field($tagfield) ) {
1528 push @result, $record->field($tagfield)->data();
1533 foreach my $field ( $record->field($tagfield) ) {
1534 my @subfields = $field->subfields();
1535 foreach my $subfield (@subfields) {
1536 if ( @$subfield[0] eq $insubfield ) {
1537 push @result, @$subfield[1];
1538 $indicator = $field->indicator(1) . $field->indicator(2);
1543 return ( $indicator, @result );
1547 =head2 PrepareItemrecordDisplay
1549 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1551 Returns a hash with all the fields for Display a given item data in a template
1553 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1555 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1559 sub PrepareItemrecordDisplay {
1561 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1563 my $dbh = C4::Context->dbh;
1564 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1565 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1567 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1568 # a shared data structure. No plugin (including custom ones) should change
1569 # its contents. See also GetMarcStructure.
1570 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1572 # Pick the default location from NewItemsDefaultLocation
1573 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1574 $defaultvalues //= {};
1575 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1578 # return nothing if we don't have found an existing framework.
1579 return q{} unless $tagslib;
1582 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1586 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1588 SELECT authorised_value,lib FROM authorised_values
1591 LEFT JOIN authorised_values_branches ON ( id = av_id )
1596 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1597 $query .= qq{ ORDER BY lib};
1598 my $authorised_values_sth = $dbh->prepare( $query );
1599 foreach my $tag ( sort keys %{$tagslib} ) {
1602 # loop through each subfield
1604 foreach my $subfield (
1605 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1606 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1607 values %{ $tagslib->{$tag} } )
1609 next unless ( $subfield->{'tab'} );
1610 next if ( $subfield->{'tab'} ne "10" );
1612 $subfield_data{tag} = $tag;
1613 $subfield_data{subfield} = $subfield->{subfield};
1614 $subfield_data{countsubfield} = $cntsubf++;
1615 $subfield_data{kohafield} = $subfield->{kohafield};
1616 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1618 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1619 $subfield_data{marc_lib} = $subfield->{lib};
1620 $subfield_data{mandatory} = $subfield->{mandatory};
1621 $subfield_data{repeatable} = $subfield->{repeatable};
1622 $subfield_data{hidden} = "display:none"
1623 if ( ( $subfield->{hidden} > 4 )
1624 || ( $subfield->{hidden} < -4 ) );
1625 my ( $x, $defaultvalue );
1627 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1629 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1630 if ( !defined $defaultvalue ) {
1631 $defaultvalue = q||;
1633 $defaultvalue =~ s/"/"/g;
1634 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1635 my $today_dt = dt_from_string;
1636 my $year = $today_dt->strftime('%Y');
1637 my $shortyear = $today_dt->strftime('%y');
1638 my $month = $today_dt->strftime('%m');
1639 my $day = $today_dt->strftime('%d');
1640 $defaultvalue =~ s/<<YYYY>>/$year/g;
1641 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1642 $defaultvalue =~ s/<<MM>>/$month/g;
1643 $defaultvalue =~ s/<<DD>>/$day/g;
1645 # And <<USER>> with surname (?)
1647 ( C4::Context->userenv
1648 ? C4::Context->userenv->{'surname'}
1649 : "superlibrarian" );
1650 $defaultvalue =~ s/<<USER>>/$username/g;
1653 my $maxlength = $subfield->{maxlength};
1655 # search for itemcallnumber if applicable
1656 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1657 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1658 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1659 my $CNtag = substr( $itemcn_pref, 0, 3 );
1660 next unless my $field = $itemrecord->field($CNtag);
1661 my $CNsubfields = substr( $itemcn_pref, 3 );
1662 $CNsubfields = undef if $CNsubfields eq '';
1663 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1664 last if $defaultvalue;
1667 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1669 && $defaultvalues->{'callnumber'} ) {
1670 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1671 # if the item record exists, only use default value if the item has no callnumber
1672 $defaultvalue = $defaultvalues->{callnumber};
1673 } elsif ( !$itemrecord and $defaultvalues ) {
1674 # if the item record *doesn't* exists, always use the default value
1675 $defaultvalue = $defaultvalues->{callnumber};
1678 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1680 && $defaultvalues->{'branchcode'} ) {
1681 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1682 $defaultvalue = $defaultvalues->{branchcode};
1685 if ( ( $subfield->{kohafield} eq 'items.location' )
1687 && $defaultvalues->{'location'} ) {
1689 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1690 # if the item record exists, only use default value if the item has no locationr
1691 $defaultvalue = $defaultvalues->{location};
1692 } elsif ( !$itemrecord and $defaultvalues ) {
1693 # if the item record *doesn't* exists, always use the default value
1694 $defaultvalue = $defaultvalues->{location};
1697 if ( $subfield->{authorised_value} ) {
1698 my @authorised_values;
1701 # builds list, depending on authorised value...
1703 if ( $subfield->{'authorised_value'} eq "branches" ) {
1704 if ( ( C4::Context->preference("IndependentBranches") )
1705 && !C4::Context->IsSuperLibrarian() ) {
1706 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1707 $sth->execute( C4::Context->userenv->{branch} );
1708 push @authorised_values, ""
1709 unless ( $subfield->{mandatory} );
1710 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1711 push @authorised_values, $branchcode;
1712 $authorised_lib{$branchcode} = $branchname;
1715 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1717 push @authorised_values, ""
1718 unless ( $subfield->{mandatory} );
1719 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1720 push @authorised_values, $branchcode;
1721 $authorised_lib{$branchcode} = $branchname;
1725 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1726 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1727 $defaultvalue = $defaultvalues->{branchcode};
1731 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1732 my $itemtypes = Koha::ItemTypes->search_with_localization;
1733 push @authorised_values, "";
1734 while ( my $itemtype = $itemtypes->next ) {
1735 push @authorised_values, $itemtype->itemtype;
1736 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1738 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1739 $defaultvalue = $defaultvalues->{'itemtype'};
1743 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1744 push @authorised_values, "";
1746 my $class_sources = GetClassSources();
1747 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1749 foreach my $class_source (sort keys %$class_sources) {
1750 next unless $class_sources->{$class_source}->{'used'} or
1751 ($class_source eq $default_source);
1752 push @authorised_values, $class_source;
1753 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1756 $defaultvalue = $default_source;
1758 #---- "true" authorised value
1760 $authorised_values_sth->execute(
1761 $subfield->{authorised_value},
1762 $branch_limit ? $branch_limit : ()
1764 push @authorised_values, "";
1765 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1766 push @authorised_values, $value;
1767 $authorised_lib{$value} = $lib;
1770 $subfield_data{marc_value} = {
1772 values => \@authorised_values,
1773 default => $defaultvalue // q{},
1774 labels => \%authorised_lib,
1776 } elsif ( $subfield->{value_builder} ) {
1778 require Koha::FrameworkPlugin;
1779 my $plugin = Koha::FrameworkPlugin->new({
1780 name => $subfield->{value_builder},
1783 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1784 $plugin->build( $pars );
1785 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1786 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1788 if( !$plugin->errstr ) {
1789 #TODO Move html to template; see report 12176/13397
1790 my $tab= $plugin->noclick? '-1': '';
1791 my $class= $plugin->noclick? ' disabled': '';
1792 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1793 $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;
1795 warn $plugin->errstr;
1796 $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
1799 elsif ( $tag eq '' ) { # it's an hidden field
1800 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1802 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
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" />);
1805 elsif ( length($defaultvalue) > 100
1806 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1807 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1808 or (C4::Context->preference("marcflavour") eq "MARC21" and
1809 500 <= $tag && $tag < 600 )
1811 # oversize field (textarea)
1812 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1814 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1816 push( @loop_data, \%subfield_data );
1821 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1822 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1825 'itemtagfield' => $itemtagfield,
1826 'itemtagsubfield' => $itemtagsubfield,
1827 'itemnumber' => $itemnumber,
1828 'iteminformation' => \@loop_data
1832 sub ToggleNewStatus {
1833 my ( $params ) = @_;
1834 my @rules = @{ $params->{rules} };
1835 my $report_only = $params->{report_only};
1837 my $dbh = C4::Context->dbh;
1839 my @item_columns = map { "items.$_" } Koha::Items->columns;
1840 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1842 for my $rule ( @rules ) {
1843 my $age = $rule->{age};
1844 # Default to using items.dateaccessioned if there's an old item modification rule
1845 # missing an agefield value
1846 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1847 my $conditions = $rule->{conditions};
1848 my $substitutions = $rule->{substitutions};
1849 foreach ( @$substitutions ) {
1850 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1857 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1860 for my $condition ( @$conditions ) {
1862 grep { $_ eq $condition->{field} } @item_columns
1863 or grep { $_ eq $condition->{field} } @biblioitem_columns
1865 if ( $condition->{value} =~ /\|/ ) {
1866 my @values = split /\|/, $condition->{value};
1867 $query .= qq| AND $condition->{field} IN (|
1868 . join( ',', ('?') x scalar @values )
1870 push @params, @values;
1872 $query .= qq| AND $condition->{field} = ?|;
1873 push @params, $condition->{value};
1877 if ( defined $age ) {
1878 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1881 my $sth = $dbh->prepare($query);
1882 $sth->execute( @params );
1883 while ( my $values = $sth->fetchrow_hashref ) {
1884 my $biblionumber = $values->{biblionumber};
1885 my $itemnumber = $values->{itemnumber};
1886 my $item = Koha::Items->find($itemnumber);
1887 for my $substitution ( @$substitutions ) {
1888 my $field = $substitution->{item_field};
1889 my $value = $substitution->{value};
1890 next unless $substitution->{field};
1891 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1892 $item->$field($value);
1893 push @{ $report->{$itemnumber} }, $substitution;
1895 $item->store unless $report_only;