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 );
68 use Koha::Biblioitems;
71 use Koha::SearchEngine;
72 use Koha::SearchEngine::Indexer;
73 use Koha::SearchEngine::Search;
78 C4::Items - item management functions
82 This module contains an API for manipulating item
83 records in Koha, and is used by cataloguing, circulation,
84 acquisitions, and serials management.
86 # FIXME This POD is not up-to-date
87 A Koha item record is stored in two places: the
88 items table and embedded in a MARC tag in the XML
89 version of the associated bib record in C<biblioitems.marcxml>.
90 This is done to allow the item information to be readily
91 indexed (e.g., by Zebra), but means that each item
92 modification transaction must keep the items table
93 and the MARC XML in sync at all times.
95 The items table will be considered authoritative. In other
96 words, if there is ever a discrepancy between the items
97 table and the MARC XML, the items table should be considered
100 =head1 HISTORICAL NOTE
102 Most of the functions in C<C4::Items> were originally in
103 the C<C4::Biblio> module.
105 =head1 CORE EXPORTED FUNCTIONS
107 The following functions are meant for use by users
114 CartToShelf($itemnumber);
116 Set the current shelving location of the item record
117 to its stored permanent shelving location. This is
118 primarily used to indicate when an item whose current
119 location is a special processing ('PROC') or shelving cart
120 ('CART') location is back in the stacks.
125 my ( $itemnumber ) = @_;
127 unless ( $itemnumber ) {
128 croak "FAILED CartToShelf() - no itemnumber supplied";
131 my $item = Koha::Items->find($itemnumber);
132 if ( $item->location eq 'CART' ) {
133 $item->location($item->permanent_location)->store;
137 =head2 AddItemFromMarc
139 my ($biblionumber, $biblioitemnumber, $itemnumber)
140 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
142 Given a MARC::Record object containing an embedded item
143 record and a biblionumber, create a new item record.
145 The final optional parameter, C<$params>, expected to contain
146 'skip_record_index' key, which relayed down to Koha::Item/store,
147 there it prevents calling of index_records,
148 which takes most of the time in batch adds/deletes: index_records
149 to be called later in C<additem.pl> after the whole loop.
152 skip_record_index => 1|0
156 sub AddItemFromMarc {
157 my $source_item_marc = shift;
158 my $biblionumber = shift;
159 my $params = @_ ? shift : {};
161 my $dbh = C4::Context->dbh;
163 # parse item hash from MARC
164 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
165 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
167 my $localitemmarc = MARC::Record->new;
168 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
170 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
171 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
172 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
173 $item_values->{biblionumber} = $biblionumber;
174 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
175 $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
176 my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
177 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
180 =head2 AddItemBatchFromMarc
182 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
183 $biblionumber, $biblioitemnumber, $frameworkcode);
185 Efficiently create item records from a MARC biblio record with
186 embedded item fields. This routine is suitable for batch jobs.
188 This API assumes that the bib record has already been
189 saved to the C<biblio> and C<biblioitems> tables. It does
190 not expect that C<biblio_metadata.metadata> is populated, but it
191 will do so via a call to ModBibiloMarc.
193 The goal of this API is to have a similar effect to using AddBiblio
194 and AddItems in succession, but without inefficient repeated
195 parsing of the MARC XML bib record.
197 This function returns an arrayref of new itemsnumbers and an arrayref of item
198 errors encountered during the processing. Each entry in the errors
199 list is a hashref containing the following keys:
205 Sequence number of original item tag in the MARC record.
209 Item barcode, provide to assist in the construction of
210 useful error messages.
214 Code representing the error condition. Can be 'duplicate_barcode',
215 'invalid_homebranch', or 'invalid_holdingbranch'.
217 =item error_information
219 Additional information appropriate to the error condition.
225 sub AddItemBatchFromMarc {
226 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
227 my @itemnumbers = ();
229 my $dbh = C4::Context->dbh;
231 # We modify the record, so lets work on a clone so we don't change the
233 $record = $record->clone();
234 # loop through the item tags and start creating items
235 my @bad_item_fields = ();
236 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
237 my $item_sequence_num = 0;
238 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
239 $item_sequence_num++;
240 # we take the item field and stick it into a new
241 # MARC record -- this is required so far because (FIXME)
242 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
243 # and there is no TransformMarcFieldToKoha
244 my $temp_item_marc = MARC::Record->new();
245 $temp_item_marc->append_fields($item_field);
247 # add biblionumber and biblioitemnumber
248 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
249 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
250 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
251 $item->{'biblionumber'} = $biblionumber;
252 $item->{'biblioitemnumber'} = $biblioitemnumber;
253 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
254 $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
256 # check for duplicate barcode
257 my %item_errors = CheckItemPreSave($item);
259 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
260 push @bad_item_fields, $item_field;
264 my $item_object = Koha::Item->new($item)->store;
265 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
267 logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
269 my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
270 $item_field->replace_with($new_item_marc->field($itemtag));
273 # remove any MARC item fields for rejected items
274 foreach my $item_field (@bad_item_fields) {
275 $record->delete_field($item_field);
278 return (\@itemnumbers, \@errors);
281 =head2 ModItemFromMarc
283 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
285 The final optional parameter, C<$params>, expected to contain
286 'skip_record_index' key, which relayed down to Koha::Item/store,
287 there it prevents calling of index_records,
288 which takes most of the time in batch adds/deletes: index_records better
289 to be called later in C<additem.pl> after the whole loop.
292 skip_record_index => 1|0
296 sub ModItemFromMarc {
297 my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
299 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
300 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
302 my $localitemmarc = MARC::Record->new;
303 $localitemmarc->append_fields( $item_marc->field($itemtag) );
304 my $item_object = Koha::Items->find($itemnumber);
305 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
307 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
308 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
310 # Retrieving the values for the fields that are not linked
311 my @mapped_fields = Koha::MarcSubfieldStructures->search(
313 frameworkcode => $frameworkcode,
314 kohafield => { -like => "items.%" }
316 )->get_column('kohafield');
317 for my $c ( $item_object->_result->result_source->columns ) {
318 next if grep { "items.$c" eq $_ } @mapped_fields;
319 $item->{$c} = $item_object->$c;
322 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
323 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
324 $item->{itemnumber} = $itemnumber;
325 $item->{biblionumber} = $biblionumber;
327 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
328 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
329 $item_object = $item_object->set_or_blank($item);
330 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
332 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
334 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
335 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
336 $item_object->store({ skip_record_index => $params->{skip_record_index} });
338 return $item_object->unblessed;
341 =head2 ModItemTransfer
343 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
345 Marks an item as being transferred from one branch to another and records the trigger.
347 The last optional parameter allows for passing skip_record_index through to the items store call.
351 sub ModItemTransfer {
352 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
354 my $dbh = C4::Context->dbh;
355 my $item = Koha::Items->find( $itemnumber );
357 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
358 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
359 # will have been preceded by a check of branch transfer limits)
360 my $to_library = Koha::Libraries->find($tobranch);
361 my $transfer = $item->request_transfer(
370 # Immediately set the item to in transit if it is checked in
371 if ( !$item->checkout ) {
372 $item->holdingbranch($frombranch)->store(
375 skip_record_index => $params->{skip_record_index}
384 =head2 ModDateLastSeen
386 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
388 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
389 C<$itemnumber> is the item number
390 C<$leave_item_lost> determines if a lost item will be found or remain lost
392 The last optional parameter allows for passing skip_record_index through to the items store call.
396 sub ModDateLastSeen {
397 my ( $itemnumber, $leave_item_lost, $params ) = @_;
399 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
401 my $item = Koha::Items->find($itemnumber);
402 $item->datelastseen($today);
403 $item->itemlost(0) unless $leave_item_lost;
404 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
407 =head2 CheckItemPreSave
409 my $item_ref = TransformMarcToKoha($marc, 'items');
411 my %errors = CheckItemPreSave($item_ref);
412 if (exists $errors{'duplicate_barcode'}) {
413 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
414 } elsif (exists $errors{'invalid_homebranch'}) {
415 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
416 } elsif (exists $errors{'invalid_holdingbranch'}) {
417 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
422 Given a hashref containing item fields, determine if it can be
423 inserted or updated in the database. Specifically, checks for
424 database integrity issues, and returns a hash containing any
425 of the following keys, if applicable.
429 =item duplicate_barcode
431 Barcode, if it duplicates one already found in the database.
433 =item invalid_homebranch
435 Home branch, if not defined in branches table.
437 =item invalid_holdingbranch
439 Holding branch, if not defined in branches table.
443 This function does NOT implement any policy-related checks,
444 e.g., whether current operator is allowed to save an
445 item that has a given branch code.
449 sub CheckItemPreSave {
450 my $item_ref = shift;
454 # check for duplicate barcode
455 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
456 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
457 if ($existing_item) {
458 if (!exists $item_ref->{'itemnumber'} # new item
459 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
460 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
465 # check for valid home branch
466 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
467 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
468 unless (defined $home_library) {
469 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
473 # check for valid holding branch
474 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
475 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
476 unless (defined $holding_library) {
477 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
485 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
487 The following functions provide various ways of
488 getting an item record, a set of item records, or
489 lists of authorized values for certain item fields.
493 =head2 GetItemsForInventory
495 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
496 minlocation => $minlocation,
497 maxlocation => $maxlocation,
498 location => $location,
499 ignoreissued => $ignoreissued,
500 datelastseen => $datelastseen,
501 branchcode => $branchcode,
505 statushash => $statushash,
506 itemtypes => \@itemsarray,
509 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
511 The sub returns a reference to a list of hashes, each containing
512 itemnumber, author, title, barcode, item callnumber, and date last
513 seen. It is ordered by callnumber then title.
515 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
516 the datelastseen can be used to specify that you want to see items not seen since a past date only.
517 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
518 $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.
520 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
524 sub GetItemsForInventory {
525 my ( $parameters ) = @_;
526 my $minlocation = $parameters->{'minlocation'} // '';
527 my $maxlocation = $parameters->{'maxlocation'} // '';
528 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
529 my $location = $parameters->{'location'} // '';
530 my $itemtype = $parameters->{'itemtype'} // '';
531 my $ignoreissued = $parameters->{'ignoreissued'} // '';
532 my $datelastseen = $parameters->{'datelastseen'} // '';
533 my $branchcode = $parameters->{'branchcode'} // '';
534 my $branch = $parameters->{'branch'} // '';
535 my $offset = $parameters->{'offset'} // '';
536 my $size = $parameters->{'size'} // '';
537 my $statushash = $parameters->{'statushash'} // '';
538 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
539 my $itemtypes = $parameters->{'itemtypes'} || [];
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
550 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
553 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
554 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
557 for my $authvfield (keys %$statushash){
558 if ( scalar @{$statushash->{$authvfield}} > 0 ){
559 my $joinedvals = join ',', @{$statushash->{$authvfield}};
560 push @where_strings, "$authvfield in (" . $joinedvals . ")";
566 push @where_strings, 'items.cn_sort >= ?';
567 push @bind_params, $min_cnsort;
571 push @where_strings, 'items.cn_sort <= ?';
572 push @bind_params, $max_cnsort;
576 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
577 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
578 push @bind_params, $datelastseen;
582 push @where_strings, 'items.location = ?';
583 push @bind_params, $location;
587 if($branch eq "homebranch"){
588 push @where_strings, 'items.homebranch = ?';
590 push @where_strings, 'items.holdingbranch = ?';
592 push @bind_params, $branchcode;
596 push @where_strings, 'biblioitems.itemtype = ?';
597 push @bind_params, $itemtype;
599 if ( $ignoreissued) {
600 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
601 push @where_strings, 'issues.date_due IS NULL';
604 if ( $ignore_waiting_holds ) {
605 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
606 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
610 my $itemtypes_str = join ', ', @$itemtypes;
611 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
614 if ( @where_strings ) {
616 $query .= join ' AND ', @where_strings;
618 my $count_query = $select_count . $query;
619 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
620 $query .= " LIMIT $offset, $size" if ($offset and $size);
621 $query = $select_columns . $query;
622 my $sth = $dbh->prepare($query);
623 $sth->execute( @bind_params );
626 my $tmpresults = $sth->fetchall_arrayref({});
627 $sth = $dbh->prepare( $count_query );
628 $sth->execute( @bind_params );
629 my ($iTotalRecords) = $sth->fetchrow_array();
631 my @avs = Koha::AuthorisedValues->search(
632 { 'marc_subfield_structures.kohafield' => { '>' => '' },
633 'me.authorised_value' => { '>' => '' },
635 { join => { category => 'marc_subfield_structures' },
636 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
637 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
638 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
642 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
644 foreach my $row (@$tmpresults) {
647 foreach (keys %$row) {
650 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
653 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
659 return (\@results, $iTotalRecords);
664 @results = GetItemsInfo($biblionumber);
666 Returns information about items with the given biblionumber.
668 C<GetItemsInfo> returns a list of references-to-hash. Each element
669 contains a number of keys. Most of them are attributes from the
670 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
671 Koha database. Other keys include:
675 =item C<$data-E<gt>{branchname}>
677 The name (not the code) of the branch to which the book belongs.
679 =item C<$data-E<gt>{datelastseen}>
681 This is simply C<items.datelastseen>, except that while the date is
682 stored in YYYY-MM-DD format in the database, here it is converted to
683 DD/MM/YYYY format. A NULL date is returned as C<//>.
685 =item C<$data-E<gt>{datedue}>
687 =item C<$data-E<gt>{class}>
689 This is the concatenation of C<biblioitems.classification>, the book's
690 Dewey code, and C<biblioitems.subclass>.
692 =item C<$data-E<gt>{ocount}>
694 I think this is the number of copies of the book available.
696 =item C<$data-E<gt>{order}>
698 If this is set, it is set to C<One Order>.
705 my ( $biblionumber ) = @_;
706 my $dbh = C4::Context->dbh;
707 require C4::Languages;
708 my $language = C4::Languages::getlanguage();
714 biblioitems.itemtype,
717 biblioitems.publicationyear,
718 biblioitems.publishercode,
719 biblioitems.volumedate,
720 biblioitems.volumedesc,
723 items.notforloan as itemnotforloan,
724 issues.borrowernumber,
725 issues.date_due as datedue,
726 issues.onsite_checkout,
727 borrowers.cardnumber,
730 borrowers.branchcode as bcode,
732 serial.publisheddate,
733 itemtypes.description,
734 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
735 itemtypes.notforloan as notforloan_per_itemtype,
739 holding.opac_info as holding_branch_opac_info,
740 home.opac_info as home_branch_opac_info,
741 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
743 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
744 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
745 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
746 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
747 LEFT JOIN issues USING (itemnumber)
748 LEFT JOIN borrowers USING (borrowernumber)
749 LEFT JOIN serialitems USING (itemnumber)
750 LEFT JOIN serial USING (serialid)
751 LEFT JOIN itemtypes ON itemtypes.itemtype = "
752 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
754 LEFT JOIN tmp_holdsqueue USING (itemnumber)
755 LEFT JOIN localization ON itemtypes.itemtype = localization.code
756 AND localization.entity = 'itemtypes'
757 AND localization.lang = ?
760 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
761 my $sth = $dbh->prepare($query);
762 $sth->execute($language, $biblionumber);
767 my $userenv = C4::Context->userenv;
768 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
769 while ( my $data = $sth->fetchrow_hashref ) {
770 if ( $data->{borrowernumber} && $want_not_same_branch) {
771 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
774 $serial ||= $data->{'serial'};
777 # get notforloan complete status if applicable
778 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
779 $data->{notforloanvalue} = $descriptions->{lib} // '';
780 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
782 # get restricted status and description if applicable
783 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
784 $data->{restrictedvalue} = $descriptions->{lib} // '';
785 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
787 # my stack procedures
788 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
789 $data->{stack} = $descriptions->{lib} // '';
791 # Find the last 3 people who borrowed this item.
792 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
794 AND old_issues.borrowernumber = borrowers.borrowernumber
795 ORDER BY returndate DESC
797 $sth2->execute($data->{'itemnumber'});
799 while (my $data2 = $sth2->fetchrow_hashref()) {
800 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
801 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
802 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
806 $results[$i] = $data;
811 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'} || "") cmp ($a->{'publisheddate'} || $a->{'enumchron'} || "") } @results
815 =head2 GetItemsLocationInfo
817 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
819 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
821 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
825 =item C<$data-E<gt>{homebranch}>
827 Branch Name of the item's homebranch
829 =item C<$data-E<gt>{holdingbranch}>
831 Branch Name of the item's holdingbranch
833 =item C<$data-E<gt>{location}>
835 Item's shelving location code
837 =item C<$data-E<gt>{location_intranet}>
839 The intranet description for the Shelving Location as set in authorised_values 'LOC'
841 =item C<$data-E<gt>{location_opac}>
843 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
846 =item C<$data-E<gt>{itemcallnumber}>
848 Item's itemcallnumber
850 =item C<$data-E<gt>{cn_sort}>
852 Item's call number normalized for sorting
858 sub GetItemsLocationInfo {
859 my $biblionumber = shift;
862 my $dbh = C4::Context->dbh;
863 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
864 location, itemcallnumber, cn_sort
865 FROM items, branches as a, branches as b
866 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
868 ORDER BY cn_sort ASC";
869 my $sth = $dbh->prepare($query);
870 $sth->execute($biblionumber);
872 while ( my $data = $sth->fetchrow_hashref ) {
873 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
874 $av = $av->count ? $av->next : undef;
875 $data->{location_intranet} = $av ? $av->lib : '';
876 $data->{location_opac} = $av ? $av->opac_description : '';
877 push @results, $data;
882 =head2 GetHostItemsInfo
884 $hostiteminfo = GetHostItemsInfo($hostfield);
885 Returns the iteminfo for items linked to records via a host field
889 sub GetHostItemsInfo {
893 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
894 return @returnitemsInfo;
898 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
899 C4::Context->preference('marcflavour') eq 'NORMARC') {
900 @fields = $record->field('773');
901 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
902 @fields = $record->field('461');
905 foreach my $hostfield ( @fields ) {
906 my $hostbiblionumber = $hostfield->subfield("0");
907 my $linkeditemnumber = $hostfield->subfield("9");
908 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
909 foreach my $hostitemInfo (@hostitemInfos) {
910 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
911 push @returnitemsInfo, $hostitemInfo;
916 return @returnitemsInfo;
919 =head2 get_hostitemnumbers_of
921 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
923 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
925 Return a reference on a hash where key is a biblionumber and values are
926 references on array of itemnumbers.
931 sub get_hostitemnumbers_of {
932 my ($biblionumber) = @_;
934 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
938 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
939 return unless $marcrecord;
941 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
943 my $marcflavor = C4::Context->preference('marcflavour');
944 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
949 elsif ( $marcflavor eq 'UNIMARC' ) {
955 foreach my $hostfield ( $marcrecord->field($tag) ) {
956 my $hostbiblionumber = $hostfield->subfield($biblio_s);
957 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
958 my $linkeditemnumber = $hostfield->subfield($item_s);
959 if ( ! $linkeditemnumber ) {
960 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
963 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
964 push @returnhostitemnumbers, $linkeditemnumber
968 return @returnhostitemnumbers;
971 =head2 GetHiddenItemnumbers
973 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
975 Given a list of items it checks which should be hidden from the OPAC given
976 the current configuration. Returns a list of itemnumbers corresponding to
977 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
982 sub GetHiddenItemnumbers {
984 my $items = $params->{items};
985 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
986 foreach my $except (split(/\|/, $exceptions)){
987 if ($params->{'borcat'} eq $except){
988 return; # we don't hide anything for this borrower category
994 my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
999 my $dbh = C4::Context->dbh;
1002 foreach my $item (@$items) {
1004 # We check each rule
1005 foreach my $field (keys %$hidingrules) {
1007 if (exists $item->{$field}) {
1008 $val = $item->{$field};
1011 my $query = "SELECT $field from items where itemnumber = ?";
1012 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1014 $val = '' unless defined $val;
1016 # If the results matches the values in the yaml file
1017 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1019 # We add the itemnumber to the list
1020 push @resultitems, $item->{'itemnumber'};
1022 # If at least one rule matched for an item, no need to test the others
1027 return @resultitems;
1030 =head1 LIMITED USE FUNCTIONS
1032 The following functions, while part of the public API,
1033 are not exported. This is generally because they are
1034 meant to be used by only one script for a specific
1035 purpose, and should not be used in any other context
1036 without careful thought.
1042 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1044 Returns MARC::Record of the item passed in parameter.
1045 This function is meant for use only in C<cataloguing/additem.pl>,
1046 where it is needed to support that script's MARC-like
1052 my ( $biblionumber, $itemnumber ) = @_;
1054 # GetMarcItem has been revised so that it does the following:
1055 # 1. Gets the item information from the items table.
1056 # 2. Converts it to a MARC field for storage in the bib record.
1058 # The previous behavior was:
1059 # 1. Get the bib record.
1060 # 2. Return the MARC tag corresponding to the item record.
1062 # The difference is that one treats the items row as authoritative,
1063 # while the other treats the MARC representation as authoritative
1064 # under certain circumstances.
1066 my $item = Koha::Items->find($itemnumber) or return;
1068 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1069 # Also, don't emit a subfield if the underlying field is blank.
1071 return Item2Marc($item->unblessed, $biblionumber);
1075 my ($itemrecord,$biblionumber)=@_;
1078 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1079 } keys %{ $itemrecord }
1081 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1082 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
1083 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1084 "items.itemnumber", $framework,
1087 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1088 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1089 foreach my $field ($itemmarc->field($itemtag)){
1090 $field->add_subfields(@$unlinked_item_subfields);
1096 =head1 PRIVATE FUNCTIONS AND VARIABLES
1098 The following functions are not meant to be called
1099 directly, but are documented in order to explain
1100 the inner workings of C<C4::Items>.
1104 =head2 _marc_from_item_hash
1106 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1108 Given an item hash representing a complete item record,
1109 create a C<MARC::Record> object containing an embedded
1110 tag representing that item.
1112 The third, optional parameter C<$unlinked_item_subfields> is
1113 an arrayref of subfields (not mapped to C<items> fields per the
1114 framework) to be added to the MARC representation
1119 sub _marc_from_item_hash {
1121 my $frameworkcode = shift;
1122 my $unlinked_item_subfields;
1124 $unlinked_item_subfields = shift;
1127 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1128 # Also, don't emit a subfield if the underlying field is blank.
1129 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1130 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1131 : () } keys %{ $item } };
1133 my $item_marc = MARC::Record->new();
1134 foreach my $item_field ( keys %{$mungeditem} ) {
1135 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1136 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1137 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1138 foreach my $value (@values){
1139 if ( my $field = $item_marc->field($tag) ) {
1140 $field->add_subfields( $subfield => $value );
1142 my $add_subfields = [];
1143 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1144 $add_subfields = $unlinked_item_subfields;
1146 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1154 =head2 _repack_item_errors
1156 Add an error message hash generated by C<CheckItemPreSave>
1157 to a list of errors.
1161 sub _repack_item_errors {
1162 my $item_sequence_num = shift;
1163 my $item_ref = shift;
1164 my $error_ref = shift;
1166 my @repacked_errors = ();
1168 foreach my $error_code (sort keys %{ $error_ref }) {
1169 my $repacked_error = {};
1170 $repacked_error->{'item_sequence'} = $item_sequence_num;
1171 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1172 $repacked_error->{'error_code'} = $error_code;
1173 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1174 push @repacked_errors, $repacked_error;
1177 return @repacked_errors;
1180 =head2 _get_unlinked_item_subfields
1182 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1186 sub _get_unlinked_item_subfields {
1187 my $original_item_marc = shift;
1188 my $frameworkcode = shift;
1190 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1192 # assume that this record has only one field, and that that
1193 # field contains only the item information
1195 my @fields = $original_item_marc->fields();
1196 if ($#fields > -1) {
1197 my $field = $fields[0];
1198 my $tag = $field->tag();
1199 foreach my $subfield ($field->subfields()) {
1200 if (defined $subfield->[1] and
1201 $subfield->[1] ne '' and
1202 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1203 push @$subfields, $subfield->[0] => $subfield->[1];
1210 =head2 _get_unlinked_subfields_xml
1212 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1216 sub _get_unlinked_subfields_xml {
1217 my $unlinked_item_subfields = shift;
1220 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1221 my $marc = MARC::Record->new();
1222 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1223 # used in the framework
1224 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1225 $marc->encoding("UTF-8");
1226 $xml = $marc->as_xml("USMARC");
1232 =head2 _parse_unlinked_item_subfields_from_xml
1234 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1238 sub _parse_unlinked_item_subfields_from_xml {
1240 require C4::Charset;
1241 return unless defined $xml and $xml ne "";
1242 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1243 my $unlinked_subfields = [];
1244 my @fields = $marc->fields();
1245 if ($#fields > -1) {
1246 foreach my $subfield ($fields[0]->subfields()) {
1247 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1250 return $unlinked_subfields;
1253 =head2 GetAnalyticsCount
1255 $count= &GetAnalyticsCount($itemnumber)
1257 counts Usage of itemnumber in Analytical bibliorecords.
1261 sub GetAnalyticsCount {
1262 my ($itemnumber) = @_;
1264 ### ZOOM search here
1266 $query= "hi=".$itemnumber;
1267 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1268 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1272 sub _SearchItems_build_where_fragment {
1275 my $dbh = C4::Context->dbh;
1278 if (exists($filter->{conjunction})) {
1279 my (@where_strs, @where_args);
1280 foreach my $f (@{ $filter->{filters} }) {
1281 my $fragment = _SearchItems_build_where_fragment($f);
1283 push @where_strs, $fragment->{str};
1284 push @where_args, @{ $fragment->{args} };
1289 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1292 args => \@where_args,
1296 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1297 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1298 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1299 my @operators = qw(= != > < >= <= like);
1300 push @operators, 'not like';
1301 my $field = $filter->{field} // q{};
1302 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1303 my $op = $filter->{operator};
1304 my $query = $filter->{query};
1305 my $ifnull = $filter->{ifnull};
1307 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1308 $op = '='; # default operator
1312 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1314 my $marcsubfield = $2;
1315 my ($kohafield) = $dbh->selectrow_array(q|
1316 SELECT kohafield FROM marc_subfield_structure
1317 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1318 |, undef, $marcfield, $marcsubfield);
1321 $column = $kohafield;
1323 # MARC field is not linked to a DB field so we need to use
1324 # ExtractValue on marcxml from biblio_metadata or
1325 # items.more_subfields_xml, depending on the MARC field.
1328 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1329 if ($marcfield eq $itemfield) {
1330 $sqlfield = 'more_subfields_xml';
1331 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1333 $sqlfield = 'metadata'; # From biblio_metadata
1334 if ($marcfield < 10) {
1335 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1337 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1340 $column = "ExtractValue($sqlfield, '$xpath')";
1346 if ( defined $ifnull ) {
1347 $column = "COALESCE($column, ?)";
1350 if (ref $query eq 'ARRAY') {
1353 } elsif ($op eq '!=') {
1357 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1362 str => "$column $op ?",
1367 if ( defined $ifnull ) {
1368 unshift @{ $where_fragment->{args} }, $ifnull;
1373 return $where_fragment;
1378 my ($items, $total) = SearchItems($filter, $params);
1380 Perform a search among items
1382 $filter is a reference to a hash which can be a filter, or a combination of filters.
1384 A filter has the following keys:
1388 =item * field: the name of a SQL column in table items
1390 =item * query: the value to search in this column
1392 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
1396 A combination of filters hash the following keys:
1400 =item * conjunction: 'AND' or 'OR'
1402 =item * filters: array ref of filters
1406 $params is a reference to a hash that can contain the following parameters:
1410 =item * rows: Number of items to return. 0 returns everything (default: 0)
1412 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1415 =item * sortby: A SQL column name in items table to sort on
1417 =item * sortorder: 'ASC' or 'DESC'
1424 my ($filter, $params) = @_;
1428 return unless ref $filter eq 'HASH';
1429 return unless ref $params eq 'HASH';
1431 # Default parameters
1432 $params->{rows} ||= 0;
1433 $params->{page} ||= 1;
1434 $params->{sortby} ||= 'itemnumber';
1435 $params->{sortorder} ||= 'ASC';
1437 my ($where_str, @where_args);
1438 my $where_fragment = _SearchItems_build_where_fragment($filter);
1439 if ($where_fragment) {
1440 $where_str = $where_fragment->{str};
1441 @where_args = @{ $where_fragment->{args} };
1444 my $dbh = C4::Context->dbh;
1446 SELECT SQL_CALC_FOUND_ROWS items.*
1448 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1449 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1450 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1453 if (defined $where_str and $where_str ne '') {
1454 $query .= qq{ AND $where_str };
1457 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1458 push @where_args, C4::Context->preference('marcflavour');
1460 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1461 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1462 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1463 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1464 ? $params->{sortby} : 'itemnumber';
1465 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1466 $query .= qq{ ORDER BY $sortby $sortorder };
1468 my $rows = $params->{rows};
1471 my $offset = $rows * ($params->{page}-1);
1472 $query .= qq { LIMIT ?, ? };
1473 push @limit_args, $offset, $rows;
1476 my $sth = $dbh->prepare($query);
1477 my $rv = $sth->execute(@where_args, @limit_args);
1479 return unless ($rv);
1480 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1482 return ($sth->fetchall_arrayref({}), $total_rows);
1486 =head1 OTHER FUNCTIONS
1490 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1492 Find the given $subfield in the given $tag in the given
1493 MARC::Record $record. If the subfield is found, returns
1494 the (indicators, value) pair; otherwise, (undef, undef) is
1498 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1499 I suggest we export it from this module.
1504 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1507 if ( $tagfield < 10 ) {
1508 if ( $record->field($tagfield) ) {
1509 push @result, $record->field($tagfield)->data();
1514 foreach my $field ( $record->field($tagfield) ) {
1515 my @subfields = $field->subfields();
1516 foreach my $subfield (@subfields) {
1517 if ( @$subfield[0] eq $insubfield ) {
1518 push @result, @$subfield[1];
1519 $indicator = $field->indicator(1) . $field->indicator(2);
1524 return ( $indicator, @result );
1528 =head2 PrepareItemrecordDisplay
1530 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1532 Returns a hash with all the fields for Display a given item data in a template
1534 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1538 sub PrepareItemrecordDisplay {
1540 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1542 my $dbh = C4::Context->dbh;
1543 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1544 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1546 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1547 # a shared data structure. No plugin (including custom ones) should change
1548 # its contents. See also GetMarcStructure.
1549 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1551 # Pick the default location from NewItemsDefaultLocation
1552 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1553 $defaultvalues //= {};
1554 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1557 # return nothing if we don't have found an existing framework.
1558 return q{} unless $tagslib;
1561 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1565 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1567 SELECT authorised_value,lib FROM authorised_values
1570 LEFT JOIN authorised_values_branches ON ( id = av_id )
1575 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1576 $query .= qq{ ORDER BY lib};
1577 my $authorised_values_sth = $dbh->prepare( $query );
1578 foreach my $tag ( sort keys %{$tagslib} ) {
1581 # loop through each subfield
1583 foreach my $subfield (
1584 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1585 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1586 values %{ $tagslib->{$tag} } )
1588 next unless ( $subfield->{'tab'} );
1589 next if ( $subfield->{'tab'} ne "10" );
1591 $subfield_data{tag} = $tag;
1592 $subfield_data{subfield} = $subfield->{subfield};
1593 $subfield_data{countsubfield} = $cntsubf++;
1594 $subfield_data{kohafield} = $subfield->{kohafield};
1595 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1597 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1598 $subfield_data{marc_lib} = $subfield->{lib};
1599 $subfield_data{mandatory} = $subfield->{mandatory};
1600 $subfield_data{repeatable} = $subfield->{repeatable};
1601 $subfield_data{hidden} = "display:none"
1602 if ( ( $subfield->{hidden} > 4 )
1603 || ( $subfield->{hidden} < -4 ) );
1604 my ( $x, $defaultvalue );
1606 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1608 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1609 if ( !defined $defaultvalue ) {
1610 $defaultvalue = q||;
1612 $defaultvalue =~ s/"/"/g;
1613 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1614 my $today_dt = dt_from_string;
1615 my $year = $today_dt->strftime('%Y');
1616 my $shortyear = $today_dt->strftime('%y');
1617 my $month = $today_dt->strftime('%m');
1618 my $day = $today_dt->strftime('%d');
1619 $defaultvalue =~ s/<<YYYY>>/$year/g;
1620 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1621 $defaultvalue =~ s/<<MM>>/$month/g;
1622 $defaultvalue =~ s/<<DD>>/$day/g;
1624 # And <<USER>> with surname (?)
1626 ( C4::Context->userenv
1627 ? C4::Context->userenv->{'surname'}
1628 : "superlibrarian" );
1629 $defaultvalue =~ s/<<USER>>/$username/g;
1632 my $maxlength = $subfield->{maxlength};
1634 # search for itemcallnumber if applicable
1635 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1636 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1637 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1638 my $CNtag = substr( $itemcn_pref, 0, 3 );
1639 next unless my $field = $itemrecord->field($CNtag);
1640 my $CNsubfields = substr( $itemcn_pref, 3 );
1641 $CNsubfields = undef if $CNsubfields eq '';
1642 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1643 last if $defaultvalue;
1646 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1648 && $defaultvalues->{'callnumber'} ) {
1649 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1650 # if the item record exists, only use default value if the item has no callnumber
1651 $defaultvalue = $defaultvalues->{callnumber};
1652 } elsif ( !$itemrecord and $defaultvalues ) {
1653 # if the item record *doesn't* exists, always use the default value
1654 $defaultvalue = $defaultvalues->{callnumber};
1657 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1659 && $defaultvalues->{'branchcode'} ) {
1660 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1661 $defaultvalue = $defaultvalues->{branchcode};
1664 if ( ( $subfield->{kohafield} eq 'items.location' )
1666 && $defaultvalues->{'location'} ) {
1668 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1669 # if the item record exists, only use default value if the item has no locationr
1670 $defaultvalue = $defaultvalues->{location};
1671 } elsif ( !$itemrecord and $defaultvalues ) {
1672 # if the item record *doesn't* exists, always use the default value
1673 $defaultvalue = $defaultvalues->{location};
1676 if ( $subfield->{authorised_value} ) {
1677 my @authorised_values;
1680 # builds list, depending on authorised value...
1682 if ( $subfield->{'authorised_value'} eq "branches" ) {
1683 if ( ( C4::Context->preference("IndependentBranches") )
1684 && !C4::Context->IsSuperLibrarian() ) {
1685 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1686 $sth->execute( C4::Context->userenv->{branch} );
1687 push @authorised_values, ""
1688 unless ( $subfield->{mandatory} );
1689 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1690 push @authorised_values, $branchcode;
1691 $authorised_lib{$branchcode} = $branchname;
1694 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1696 push @authorised_values, ""
1697 unless ( $subfield->{mandatory} );
1698 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1699 push @authorised_values, $branchcode;
1700 $authorised_lib{$branchcode} = $branchname;
1704 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1705 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1706 $defaultvalue = $defaultvalues->{branchcode};
1710 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1711 my $itemtypes = Koha::ItemTypes->search_with_localization;
1712 push @authorised_values, "";
1713 while ( my $itemtype = $itemtypes->next ) {
1714 push @authorised_values, $itemtype->itemtype;
1715 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1717 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1718 $defaultvalue = $defaultvalues->{'itemtype'};
1722 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1723 push @authorised_values, "";
1725 my $class_sources = GetClassSources();
1726 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1728 foreach my $class_source (sort keys %$class_sources) {
1729 next unless $class_sources->{$class_source}->{'used'} or
1730 ($class_source eq $default_source);
1731 push @authorised_values, $class_source;
1732 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1735 $defaultvalue = $default_source;
1737 #---- "true" authorised value
1739 $authorised_values_sth->execute(
1740 $subfield->{authorised_value},
1741 $branch_limit ? $branch_limit : ()
1743 push @authorised_values, "";
1744 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1745 push @authorised_values, $value;
1746 $authorised_lib{$value} = $lib;
1749 $subfield_data{marc_value} = {
1751 values => \@authorised_values,
1752 default => $defaultvalue // q{},
1753 labels => \%authorised_lib,
1755 } elsif ( $subfield->{value_builder} ) {
1757 require Koha::FrameworkPlugin;
1758 my $plugin = Koha::FrameworkPlugin->new({
1759 name => $subfield->{value_builder},
1762 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1763 $plugin->build( $pars );
1764 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1765 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1767 if( !$plugin->errstr ) {
1768 #TODO Move html to template; see report 12176/13397
1769 my $tab= $plugin->noclick? '-1': '';
1770 my $class= $plugin->noclick? ' disabled': '';
1771 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1772 $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;
1774 warn $plugin->errstr;
1775 $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
1778 elsif ( $tag eq '' ) { # it's an hidden field
1779 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1781 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1782 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1784 elsif ( length($defaultvalue) > 100
1785 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1786 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1787 or (C4::Context->preference("marcflavour") eq "MARC21" and
1788 500 <= $tag && $tag < 600 )
1790 # oversize field (textarea)
1791 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
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" />);
1795 push( @loop_data, \%subfield_data );
1800 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1801 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1804 'itemtagfield' => $itemtagfield,
1805 'itemtagsubfield' => $itemtagsubfield,
1806 'itemnumber' => $itemnumber,
1807 'iteminformation' => \@loop_data
1811 sub ToggleNewStatus {
1812 my ( $params ) = @_;
1813 my @rules = @{ $params->{rules} };
1814 my $report_only = $params->{report_only};
1816 my $dbh = C4::Context->dbh;
1818 my @item_columns = map { "items.$_" } Koha::Items->columns;
1819 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1821 for my $rule ( @rules ) {
1822 my $age = $rule->{age};
1823 my $conditions = $rule->{conditions};
1824 my $substitutions = $rule->{substitutions};
1825 foreach ( @$substitutions ) {
1826 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1833 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1836 for my $condition ( @$conditions ) {
1838 grep { $_ eq $condition->{field} } @item_columns
1839 or grep { $_ eq $condition->{field} } @biblioitem_columns
1841 if ( $condition->{value} =~ /\|/ ) {
1842 my @values = split /\|/, $condition->{value};
1843 $query .= qq| AND $condition->{field} IN (|
1844 . join( ',', ('?') x scalar @values )
1846 push @params, @values;
1848 $query .= qq| AND $condition->{field} = ?|;
1849 push @params, $condition->{value};
1853 if ( defined $age ) {
1854 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1857 my $sth = $dbh->prepare($query);
1858 $sth->execute( @params );
1859 while ( my $values = $sth->fetchrow_hashref ) {
1860 my $biblionumber = $values->{biblionumber};
1861 my $itemnumber = $values->{itemnumber};
1862 my $item = Koha::Items->find($itemnumber);
1863 for my $substitution ( @$substitutions ) {
1864 my $field = $substitution->{item_field};
1865 my $value = $substitution->{value};
1866 next unless $substitution->{field};
1867 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1868 $item->$field($value);
1869 push @{ $report->{$itemnumber} }, $substitution;
1871 $item->store unless $report_only;