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);
37 get_hostitemnumbers_of
42 PrepareItemrecordDisplay
50 use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
52 use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
53 use C4::Log qw( logaction );
54 use List::MoreUtils qw( any );
55 use DateTime::Format::MySQL;
56 # debugging; so please don't remove this
58 use Koha::AuthorisedValues;
59 use Koha::DateUtils qw( dt_from_string );
63 use Koha::Biblioitems;
66 use Koha::SearchEngine;
67 use Koha::SearchEngine::Indexer;
68 use Koha::SearchEngine::Search;
73 C4::Items - item management functions
77 This module contains an API for manipulating item
78 records in Koha, and is used by cataloguing, circulation,
79 acquisitions, and serials management.
81 # FIXME This POD is not up-to-date
82 A Koha item record is stored in two places: the
83 items table and embedded in a MARC tag in the XML
84 version of the associated bib record in C<biblioitems.marcxml>.
85 This is done to allow the item information to be readily
86 indexed (e.g., by Zebra), but means that each item
87 modification transaction must keep the items table
88 and the MARC XML in sync at all times.
90 The items table will be considered authoritative. In other
91 words, if there is ever a discrepancy between the items
92 table and the MARC XML, the items table should be considered
95 =head1 HISTORICAL NOTE
97 Most of the functions in C<C4::Items> were originally in
98 the C<C4::Biblio> module.
100 =head1 CORE EXPORTED FUNCTIONS
102 The following functions are meant for use by users
109 CartToShelf($itemnumber);
111 Set the current shelving location of the item record
112 to its stored permanent shelving location. This is
113 primarily used to indicate when an item whose current
114 location is a special processing ('PROC') or shelving cart
115 ('CART') location is back in the stacks.
120 my ( $itemnumber ) = @_;
122 unless ( $itemnumber ) {
123 croak "FAILED CartToShelf() - no itemnumber supplied";
126 my $item = Koha::Items->find($itemnumber);
127 if ( $item->location eq 'CART' ) {
128 $item->location($item->permanent_location)->store;
132 =head2 AddItemFromMarc
134 my ($biblionumber, $biblioitemnumber, $itemnumber)
135 = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
137 Given a MARC::Record object containing an embedded item
138 record and a biblionumber, create a new item record.
140 The final optional parameter, C<$params>, may contain
141 'skip_record_index' key, which relayed down to Koha::Item/store,
142 there it prevents calling of index_records,
143 which takes most of the time in batch adds/deletes: index_records
144 to be called later in C<additem.pl> after the whole loop.
146 You may also optionally pass biblioitemnumber in the params hash to
147 boost performance of inserts by preventing a lookup in Koha::Item.
150 skip_record_index => 1|0
151 biblioitemnumber => $biblioitemnumber
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" );
165 my $localitemmarc = MARC::Record->new;
166 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
168 my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
169 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
170 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
171 $item_values->{biblionumber} = $biblionumber;
172 $item_values->{biblioitemnumber} = $params->{biblioitemnumber};
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({ record => $temp_item_marc, limit_table => '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({ record => $localitemmarc, limit_table => 'items' });
306 # When importing items we blank this column, we need to set it to the existing value
307 # to prevent it being blanked by set_or_blank
308 $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
310 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
311 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
313 # Retrieving the values for the fields that are not linked
314 my @mapped_fields = Koha::MarcSubfieldStructures->search(
316 frameworkcode => $frameworkcode,
317 kohafield => { -like => "items.%" }
319 )->get_column('kohafield');
320 for my $c ( $item_object->_result->result_source->columns ) {
321 next if grep { "items.$c" eq $_ } @mapped_fields;
322 $item->{$c} = $item_object->$c;
325 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
326 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
327 $item->{itemnumber} = $itemnumber;
328 $item->{biblionumber} = $biblionumber;
330 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
331 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
332 $item_object = $item_object->set_or_blank($item);
333 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
335 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
337 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
338 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
339 $item_object->store({ skip_record_index => $params->{skip_record_index} });
341 return $item_object->unblessed;
344 =head2 ModItemTransfer
346 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
348 Marks an item as being transferred from one branch to another and records the trigger.
350 The last optional parameter allows for passing skip_record_index through to the items store call.
354 sub ModItemTransfer {
355 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
357 my $dbh = C4::Context->dbh;
358 my $item = Koha::Items->find( $itemnumber );
360 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
361 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
362 # will have been preceded by a check of branch transfer limits)
363 my $to_library = Koha::Libraries->find($tobranch);
364 my $transfer = $item->request_transfer(
373 # Immediately set the item to in transit if it is checked in
374 if ( !$item->checkout ) {
375 $item->holdingbranch($frombranch)->store(
378 skip_record_index => $params->{skip_record_index}
387 =head2 ModDateLastSeen
389 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
391 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
392 C<$itemnumber> is the item number
393 C<$leave_item_lost> determines if a lost item will be found or remain lost
395 The last optional parameter allows for passing skip_record_index through to the items store call.
399 sub ModDateLastSeen {
400 my ( $itemnumber, $leave_item_lost, $params ) = @_;
402 my $item = Koha::Items->find($itemnumber);
403 $item->datelastseen(dt_from_string->ymd);
404 my $log = $item->itemlost && !$leave_item_lost ? 1 : 0; # If item was lost, record the change to the item
405 $item->itemlost(0) unless $leave_item_lost;
406 $item->store({ log_action => $log, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
409 =head2 CheckItemPreSave
411 my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
413 my %errors = CheckItemPreSave($item_ref);
414 if (exists $errors{'duplicate_barcode'}) {
415 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
416 } elsif (exists $errors{'invalid_homebranch'}) {
417 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
418 } elsif (exists $errors{'invalid_holdingbranch'}) {
419 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
424 Given a hashref containing item fields, determine if it can be
425 inserted or updated in the database. Specifically, checks for
426 database integrity issues, and returns a hash containing any
427 of the following keys, if applicable.
431 =item duplicate_barcode
433 Barcode, if it duplicates one already found in the database.
435 =item invalid_homebranch
437 Home branch, if not defined in branches table.
439 =item invalid_holdingbranch
441 Holding branch, if not defined in branches table.
445 This function does NOT implement any policy-related checks,
446 e.g., whether current operator is allowed to save an
447 item that has a given branch code.
451 sub CheckItemPreSave {
452 my $item_ref = shift;
456 # check for duplicate barcode
457 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
458 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
459 if ($existing_item) {
460 if (!exists $item_ref->{'itemnumber'} # new item
461 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
462 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
467 # check for valid home branch
468 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
469 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
470 unless (defined $home_library) {
471 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
475 # check for valid holding branch
476 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
477 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
478 unless (defined $holding_library) {
479 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
487 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
489 The following functions provide various ways of
490 getting an item record, a set of item records, or
491 lists of authorized values for certain item fields.
495 =head2 GetItemsForInventory
497 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
498 minlocation => $minlocation,
499 maxlocation => $maxlocation,
500 location => $location,
501 ignoreissued => $ignoreissued,
502 datelastseen => $datelastseen,
503 branchcode => $branchcode,
507 statushash => $statushash,
508 itemtypes => \@itemsarray,
511 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
513 The sub returns a reference to a list of hashes, each containing
514 itemnumber, author, title, barcode, item callnumber, and date last
515 seen. It is ordered by callnumber then title.
517 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
518 the datelastseen can be used to specify that you want to see items not seen since a past date only.
519 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
520 $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.
522 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
526 sub GetItemsForInventory {
527 my ( $parameters ) = @_;
528 my $minlocation = $parameters->{'minlocation'} // '';
529 my $maxlocation = $parameters->{'maxlocation'} // '';
530 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
531 my $location = $parameters->{'location'} // '';
532 my $itemtype = $parameters->{'itemtype'} // '';
533 my $ignoreissued = $parameters->{'ignoreissued'} // '';
534 my $datelastseen = $parameters->{'datelastseen'} // '';
535 my $branchcode = $parameters->{'branchcode'} // '';
536 my $branch = $parameters->{'branch'} // '';
537 my $offset = $parameters->{'offset'} // '';
538 my $size = $parameters->{'size'} // '';
539 my $statushash = $parameters->{'statushash'} // '';
540 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
541 my $itemtypes = $parameters->{'itemtypes'} || [];
542 my $ccode = $parameters->{'ccode'} // '';
544 my $dbh = C4::Context->dbh;
545 my ( @bind_params, @where_strings );
547 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
548 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
550 my $select_columns = q{
551 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
554 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
557 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
558 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
561 for my $authvfield (keys %$statushash){
562 if ( scalar @{$statushash->{$authvfield}} > 0 ){
563 my $joinedvals = join ',', @{$statushash->{$authvfield}};
564 push @where_strings, "$authvfield in (" . $joinedvals . ")";
570 push @where_strings, 'ccode = ?';
571 push @bind_params, $ccode;
575 push @where_strings, 'items.cn_sort >= ?';
576 push @bind_params, $min_cnsort;
580 push @where_strings, 'items.cn_sort <= ?';
581 push @bind_params, $max_cnsort;
585 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
586 push @bind_params, $datelastseen;
590 push @where_strings, 'items.location = ?';
591 push @bind_params, $location;
595 if($branch eq "homebranch"){
596 push @where_strings, 'items.homebranch = ?';
598 push @where_strings, 'items.holdingbranch = ?';
600 push @bind_params, $branchcode;
604 push @where_strings, 'biblioitems.itemtype = ?';
605 push @bind_params, $itemtype;
607 if ( $ignoreissued) {
608 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
609 push @where_strings, 'issues.date_due IS NULL';
612 if ( $ignore_waiting_holds ) {
613 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
614 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
618 my $itemtypes_str = join ', ', @$itemtypes;
619 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
622 if ( @where_strings ) {
624 $query .= join ' AND ', @where_strings;
626 my $count_query = $select_count . $query;
627 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
628 $query .= " LIMIT $offset, $size" if ($offset and $size);
629 $query = $select_columns . $query;
630 my $sth = $dbh->prepare($query);
631 $sth->execute( @bind_params );
634 my $tmpresults = $sth->fetchall_arrayref({});
635 $sth = $dbh->prepare( $count_query );
636 $sth->execute( @bind_params );
637 my ($iTotalRecords) = $sth->fetchrow_array();
639 my @avs = Koha::AuthorisedValues->search(
640 { 'marc_subfield_structures.kohafield' => { '>' => '' },
641 'me.authorised_value' => { '>' => '' },
643 { join => { category => 'marc_subfield_structures' },
644 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
645 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
646 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
650 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
652 foreach my $row (@$tmpresults) {
655 foreach (keys %$row) {
658 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
661 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
667 return (\@results, $iTotalRecords);
670 =head2 get_hostitemnumbers_of
672 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
674 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
676 Return a reference on a hash where key is a biblionumber and values are
677 references on array of itemnumbers.
682 sub get_hostitemnumbers_of {
683 my ($biblionumber) = @_;
685 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
689 my $biblio = Koha::Biblios->find($biblionumber);
690 my $marcrecord = $biblio->metadata->record;
691 return unless $marcrecord;
693 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
695 my $marcflavor = C4::Context->preference('marcflavour');
696 if ( $marcflavor eq 'MARC21' ) {
701 elsif ( $marcflavor eq 'UNIMARC' ) {
707 foreach my $hostfield ( $marcrecord->field($tag) ) {
708 my $hostbiblionumber = $hostfield->subfield($biblio_s);
709 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
710 my $linkeditemnumber = $hostfield->subfield($item_s);
711 if ( ! $linkeditemnumber ) {
712 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
715 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
716 push @returnhostitemnumbers, $linkeditemnumber
720 return @returnhostitemnumbers;
723 =head1 LIMITED USE FUNCTIONS
725 The following functions, while part of the public API,
726 are not exported. This is generally because they are
727 meant to be used by only one script for a specific
728 purpose, and should not be used in any other context
729 without careful thought.
735 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
737 Returns MARC::Record of the item passed in parameter.
738 This function is meant for use only in C<cataloguing/additem.pl>,
739 where it is needed to support that script's MARC-like
745 my ( $biblionumber, $itemnumber ) = @_;
747 # GetMarcItem has been revised so that it does the following:
748 # 1. Gets the item information from the items table.
749 # 2. Converts it to a MARC field for storage in the bib record.
751 # The previous behavior was:
752 # 1. Get the bib record.
753 # 2. Return the MARC tag corresponding to the item record.
755 # The difference is that one treats the items row as authoritative,
756 # while the other treats the MARC representation as authoritative
757 # under certain circumstances.
759 my $item = Koha::Items->find($itemnumber) or return;
761 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
762 # Also, don't emit a subfield if the underlying field is blank.
764 return Item2Marc($item->unblessed, $biblionumber);
768 my ($itemrecord,$biblionumber)=@_;
771 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
772 } keys %{ $itemrecord }
774 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
775 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
776 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
777 "items.itemnumber", $framework,
780 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
781 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
782 foreach my $field ($itemmarc->field($itemtag)){
783 $field->add_subfields(@$unlinked_item_subfields);
789 =head1 PRIVATE FUNCTIONS AND VARIABLES
791 The following functions are not meant to be called
792 directly, but are documented in order to explain
793 the inner workings of C<C4::Items>.
797 =head2 _marc_from_item_hash
799 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
801 Given an item hash representing a complete item record,
802 create a C<MARC::Record> object containing an embedded
803 tag representing that item.
805 The third, optional parameter C<$unlinked_item_subfields> is
806 an arrayref of subfields (not mapped to C<items> fields per the
807 framework) to be added to the MARC representation
812 sub _marc_from_item_hash {
814 my $frameworkcode = shift;
815 my $unlinked_item_subfields;
817 $unlinked_item_subfields = shift;
820 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
821 # Also, don't emit a subfield if the underlying field is blank.
822 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
823 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
824 : () } keys %{ $item } };
826 my $item_marc = MARC::Record->new();
827 foreach my $item_field ( keys %{$mungeditem} ) {
828 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
829 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
830 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
831 foreach my $value (@values){
832 if ( my $field = $item_marc->field($tag) ) {
833 $field->add_subfields( $subfield => $value );
835 my $add_subfields = [];
836 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
837 $add_subfields = $unlinked_item_subfields;
839 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
847 =head2 _repack_item_errors
849 Add an error message hash generated by C<CheckItemPreSave>
854 sub _repack_item_errors {
855 my $item_sequence_num = shift;
856 my $item_ref = shift;
857 my $error_ref = shift;
859 my @repacked_errors = ();
861 foreach my $error_code (sort keys %{ $error_ref }) {
862 my $repacked_error = {};
863 $repacked_error->{'item_sequence'} = $item_sequence_num;
864 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
865 $repacked_error->{'error_code'} = $error_code;
866 $repacked_error->{'error_information'} = $error_ref->{$error_code};
867 push @repacked_errors, $repacked_error;
870 return @repacked_errors;
873 =head2 _get_unlinked_item_subfields
875 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
879 sub _get_unlinked_item_subfields {
880 my $original_item_marc = shift;
881 my $frameworkcode = shift;
883 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
885 # assume that this record has only one field, and that that
886 # field contains only the item information
888 my @fields = $original_item_marc->fields();
890 my $field = $fields[0];
891 my $tag = $field->tag();
892 foreach my $subfield ($field->subfields()) {
893 if (defined $subfield->[1] and
894 $subfield->[1] ne '' and
895 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
896 push @$subfields, $subfield->[0] => $subfield->[1];
903 =head2 _get_unlinked_subfields_xml
905 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
909 sub _get_unlinked_subfields_xml {
910 my $unlinked_item_subfields = shift;
913 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
914 my $marc = MARC::Record->new();
915 # use of tag 999 is arbitrary, and doesn't need to match the item tag
916 # used in the framework
917 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
918 $marc->encoding("UTF-8");
919 $xml = $marc->as_xml("USMARC");
925 =head2 _parse_unlinked_item_subfields_from_xml
927 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
931 sub _parse_unlinked_item_subfields_from_xml {
934 return unless defined $xml and $xml ne "";
935 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
936 my $unlinked_subfields = [];
937 my @fields = $marc->fields();
939 foreach my $subfield ($fields[0]->subfields()) {
940 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
943 return $unlinked_subfields;
946 =head2 GetAnalyticsCount
948 $count= &GetAnalyticsCount($itemnumber)
950 counts Usage of itemnumber in Analytical bibliorecords.
954 sub GetAnalyticsCount {
955 my ($itemnumber) = @_;
957 if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
963 $query= "hi=".$itemnumber;
964 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
965 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
969 sub _SearchItems_build_where_fragment {
972 my $dbh = C4::Context->dbh;
975 if (exists($filter->{conjunction})) {
976 my (@where_strs, @where_args);
977 foreach my $f (@{ $filter->{filters} }) {
978 my $fragment = _SearchItems_build_where_fragment($f);
980 push @where_strs, $fragment->{str};
981 push @where_args, @{ $fragment->{args} };
986 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
989 args => \@where_args,
993 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
994 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
995 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
996 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
997 my @operators = qw(= != > < >= <= is like);
998 push @operators, 'not like';
999 my $field = $filter->{field} // q{};
1000 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1001 my $op = $filter->{operator};
1002 my $query = $filter->{query};
1003 my $ifnull = $filter->{ifnull};
1005 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1006 $op = '='; # default operator
1010 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1012 my $marcsubfield = $2;
1013 my ($kohafield) = $dbh->selectrow_array(q|
1014 SELECT kohafield FROM marc_subfield_structure
1015 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1016 |, undef, $marcfield, $marcsubfield);
1019 $column = $kohafield;
1021 # MARC field is not linked to a DB field so we need to use
1022 # ExtractValue on marcxml from biblio_metadata or
1023 # items.more_subfields_xml, depending on the MARC field.
1026 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1027 if ($marcfield eq $itemfield) {
1028 $sqlfield = 'more_subfields_xml';
1029 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1031 $sqlfield = 'metadata'; # From biblio_metadata
1032 if ($marcfield < 10) {
1033 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1035 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1038 $column = "ExtractValue($sqlfield, '$xpath')";
1041 elsif ($field eq 'isbn') {
1042 if ( C4::Context->preference("SearchWithISBNVariations") and $query ) {
1043 my @isbns = C4::Koha::GetVariationsOfISBN( $query );
1045 push @$query, @isbns;
1049 elsif ($field eq 'issn') {
1050 if ( C4::Context->preference("SearchWithISSNVariations") and $query ) {
1051 my @issns = C4::Koha::GetVariationsOfISSN( $query );
1053 push @$query, @issns;
1060 if ( defined $ifnull ) {
1061 $column = "COALESCE($column, ?)";
1064 if (ref $query eq 'ARRAY') {
1065 if ($op eq 'like') {
1067 str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")",
1074 } elsif ($op eq '!=') {
1078 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1082 } elsif ( $op eq 'is' ) {
1084 str => "$column $op $query",
1089 str => "$column $op ?",
1094 if ( defined $ifnull ) {
1095 unshift @{ $where_fragment->{args} }, $ifnull;
1100 return $where_fragment;
1105 my ($items, $total) = SearchItems($filter, $params);
1107 Perform a search among items
1109 $filter is a reference to a hash which can be a filter, or a combination of filters.
1111 A filter has the following keys:
1115 =item * field: the name of a SQL column in table items
1117 =item * query: the value to search in this column
1119 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1123 A combination of filters hash the following keys:
1127 =item * conjunction: 'AND' or 'OR'
1129 =item * filters: array ref of filters
1133 $params is a reference to a hash that can contain the following parameters:
1137 =item * rows: Number of items to return. 0 returns everything (default: 0)
1139 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1142 =item * sortby: A SQL column name in items table to sort on
1144 =item * sortorder: 'ASC' or 'DESC'
1151 my ($filter, $params) = @_;
1155 return unless ref $filter eq 'HASH';
1156 return unless ref $params eq 'HASH';
1158 # Default parameters
1159 $params->{rows} ||= 0;
1160 $params->{page} ||= 1;
1161 $params->{sortby} ||= 'itemnumber';
1162 $params->{sortorder} ||= 'ASC';
1164 my ($where_str, @where_args);
1165 my $where_fragment = _SearchItems_build_where_fragment($filter);
1166 if ($where_fragment) {
1167 $where_str = $where_fragment->{str};
1168 @where_args = @{ $where_fragment->{args} };
1171 my $dbh = C4::Context->dbh;
1173 SELECT SQL_CALC_FOUND_ROWS items.*
1175 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1176 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1177 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1178 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1181 if (defined $where_str and $where_str ne '') {
1182 $query .= qq{ AND $where_str };
1185 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1186 push @where_args, C4::Context->preference('marcflavour');
1188 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1189 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1190 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1191 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1193 if ( $params->{sortby} eq 'availability' ) {
1194 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1195 $query .= qq{ ORDER BY onloan $sortorder };
1197 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1198 ? $params->{sortby} : 'itemnumber';
1199 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1200 $query .= qq{ ORDER BY $sortby $sortorder };
1203 my $rows = $params->{rows};
1206 my $offset = $rows * ($params->{page}-1);
1207 $query .= qq { LIMIT ?, ? };
1208 push @limit_args, $offset, $rows;
1211 my $sth = $dbh->prepare($query);
1212 my $rv = $sth->execute(@where_args, @limit_args);
1214 return unless ($rv);
1215 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1217 return ($sth->fetchall_arrayref({}), $total_rows);
1221 =head1 OTHER FUNCTIONS
1225 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1227 Find the given $subfield in the given $tag in the given
1228 MARC::Record $record. If the subfield is found, returns
1229 the (indicators, value) pair; otherwise, (undef, undef) is
1233 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1234 I suggest we export it from this module.
1239 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1242 if ( $tagfield < 10 ) {
1243 if ( $record->field($tagfield) ) {
1244 push @result, $record->field($tagfield)->data();
1249 foreach my $field ( $record->field($tagfield) ) {
1250 my @subfields = $field->subfields();
1251 foreach my $subfield (@subfields) {
1252 if ( @$subfield[0] eq $insubfield ) {
1253 push @result, @$subfield[1];
1254 $indicator = $field->indicator(1) . $field->indicator(2);
1259 return ( $indicator, @result );
1263 =head2 PrepareItemrecordDisplay
1265 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1267 Returns a hash with all the fields for Display a given item data in a template
1269 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1271 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1275 sub PrepareItemrecordDisplay {
1277 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1279 my $dbh = C4::Context->dbh;
1280 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1281 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1283 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1284 # a shared data structure. No plugin (including custom ones) should change
1285 # its contents. See also GetMarcStructure.
1286 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1288 # Pick the default location from NewItemsDefaultLocation
1289 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1290 $defaultvalues //= {};
1291 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1294 # return nothing if we don't have found an existing framework.
1295 return q{} unless $tagslib;
1298 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1302 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1304 SELECT authorised_value,lib FROM authorised_values
1307 LEFT JOIN authorised_values_branches ON ( id = av_id )
1312 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1313 $query .= qq{ ORDER BY lib};
1314 my $authorised_values_sth = $dbh->prepare( $query );
1315 foreach my $tag ( sort keys %{$tagslib} ) {
1318 # loop through each subfield
1320 foreach my $subfield (
1321 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1322 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1323 values %{ $tagslib->{$tag} } )
1325 next unless ( $subfield->{'tab'} );
1326 next if ( $subfield->{'tab'} ne "10" );
1328 $subfield_data{tag} = $tag;
1329 $subfield_data{subfield} = $subfield->{subfield};
1330 $subfield_data{countsubfield} = $cntsubf++;
1331 $subfield_data{kohafield} = $subfield->{kohafield};
1332 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1334 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1335 $subfield_data{marc_lib} = $subfield->{lib};
1336 $subfield_data{mandatory} = $subfield->{mandatory};
1337 $subfield_data{repeatable} = $subfield->{repeatable};
1338 $subfield_data{hidden} = "display:none"
1339 if ( ( $subfield->{hidden} > 4 )
1340 || ( $subfield->{hidden} < -4 ) );
1341 my ( $x, $defaultvalue );
1343 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1345 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1346 if ( !defined $defaultvalue ) {
1347 $defaultvalue = q||;
1349 $defaultvalue =~ s/"/"/g;
1350 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1351 my $today_dt = dt_from_string;
1352 my $year = $today_dt->strftime('%Y');
1353 my $shortyear = $today_dt->strftime('%y');
1354 my $month = $today_dt->strftime('%m');
1355 my $day = $today_dt->strftime('%d');
1356 $defaultvalue =~ s/<<YYYY>>/$year/g;
1357 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1358 $defaultvalue =~ s/<<MM>>/$month/g;
1359 $defaultvalue =~ s/<<DD>>/$day/g;
1361 # And <<USER>> with surname (?)
1363 ( C4::Context->userenv
1364 ? C4::Context->userenv->{'surname'}
1365 : "superlibrarian" );
1366 $defaultvalue =~ s/<<USER>>/$username/g;
1369 my $maxlength = $subfield->{maxlength};
1371 # search for itemcallnumber if applicable
1372 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1373 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1374 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1375 my $CNtag = substr( $itemcn_pref, 0, 3 );
1376 next unless my $field = $itemrecord->field($CNtag);
1377 my $CNsubfields = substr( $itemcn_pref, 3 );
1378 $CNsubfields = undef if $CNsubfields eq '';
1379 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1380 last if $defaultvalue;
1383 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1385 && $defaultvalues->{'callnumber'} ) {
1386 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1387 # if the item record exists, only use default value if the item has no callnumber
1388 $defaultvalue = $defaultvalues->{callnumber};
1389 } elsif ( !$itemrecord and $defaultvalues ) {
1390 # if the item record *doesn't* exists, always use the default value
1391 $defaultvalue = $defaultvalues->{callnumber};
1394 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1396 && $defaultvalues->{'branchcode'} ) {
1397 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1398 $defaultvalue = $defaultvalues->{branchcode};
1401 if ( ( $subfield->{kohafield} eq 'items.location' )
1403 && $defaultvalues->{'location'} ) {
1405 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1406 # if the item record exists, only use default value if the item has no locationr
1407 $defaultvalue = $defaultvalues->{location};
1408 } elsif ( !$itemrecord and $defaultvalues ) {
1409 # if the item record *doesn't* exists, always use the default value
1410 $defaultvalue = $defaultvalues->{location};
1413 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1415 && $defaultvalues->{'ccode'} ) {
1417 if ( !$itemrecord and $defaultvalues ) {
1418 # if the item record *doesn't* exists, always use the default value
1419 $defaultvalue = $defaultvalues->{ccode};
1422 if ( $subfield->{authorised_value} ) {
1423 my @authorised_values;
1426 # builds list, depending on authorised value...
1428 if ( $subfield->{'authorised_value'} eq "branches" ) {
1429 if ( ( C4::Context->preference("IndependentBranches") )
1430 && !C4::Context->IsSuperLibrarian() ) {
1431 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1432 $sth->execute( C4::Context->userenv->{branch} );
1433 push @authorised_values, ""
1434 unless ( $subfield->{mandatory} );
1435 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1436 push @authorised_values, $branchcode;
1437 $authorised_lib{$branchcode} = $branchname;
1440 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1442 push @authorised_values, ""
1443 unless ( $subfield->{mandatory} );
1444 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1445 push @authorised_values, $branchcode;
1446 $authorised_lib{$branchcode} = $branchname;
1450 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1451 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1452 $defaultvalue = $defaultvalues->{branchcode};
1456 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1457 my $itemtypes = Koha::ItemTypes->search_with_localization;
1458 push @authorised_values, "";
1459 while ( my $itemtype = $itemtypes->next ) {
1460 push @authorised_values, $itemtype->itemtype;
1461 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1463 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1464 $defaultvalue = $defaultvalues->{'itemtype'};
1468 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1469 push @authorised_values, "";
1471 my $class_sources = GetClassSources();
1472 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1474 foreach my $class_source (sort keys %$class_sources) {
1475 next unless $class_sources->{$class_source}->{'used'} or
1476 ($class_source eq $default_source);
1477 push @authorised_values, $class_source;
1478 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1481 $defaultvalue = $default_source;
1483 #---- "true" authorised value
1485 $authorised_values_sth->execute(
1486 $subfield->{authorised_value},
1487 $branch_limit ? $branch_limit : ()
1489 push @authorised_values, "";
1490 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1491 push @authorised_values, $value;
1492 $authorised_lib{$value} = $lib;
1495 $subfield_data{marc_value} = {
1497 values => \@authorised_values,
1498 default => $defaultvalue // q{},
1499 labels => \%authorised_lib,
1501 } elsif ( $subfield->{value_builder} ) {
1503 require Koha::FrameworkPlugin;
1504 my $plugin = Koha::FrameworkPlugin->new({
1505 name => $subfield->{value_builder},
1508 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1509 $plugin->build( $pars );
1510 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1511 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1513 if( !$plugin->errstr ) {
1514 #TODO Move html to template; see report 12176/13397
1515 my $tab= $plugin->noclick? '-1': '';
1516 my $class= $plugin->noclick? ' disabled': '';
1517 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1518 $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;
1520 warn $plugin->errstr;
1521 $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
1524 elsif ( $tag eq '' ) { # it's an hidden field
1525 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1527 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1528 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1530 elsif ( length($defaultvalue) > 100
1531 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1532 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1533 or (C4::Context->preference("marcflavour") eq "MARC21" and
1534 500 <= $tag && $tag < 600 )
1536 # oversize field (textarea)
1537 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1539 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1541 push( @loop_data, \%subfield_data );
1546 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1547 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1550 'itemtagfield' => $itemtagfield,
1551 'itemtagsubfield' => $itemtagsubfield,
1552 'itemnumber' => $itemnumber,
1553 'iteminformation' => \@loop_data
1557 sub ToggleNewStatus {
1558 my ( $params ) = @_;
1559 my @rules = @{ $params->{rules} };
1560 my $report_only = $params->{report_only};
1562 my $dbh = C4::Context->dbh;
1564 my @item_columns = map { "items.$_" } Koha::Items->columns;
1565 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1567 for my $rule ( @rules ) {
1568 my $age = $rule->{age};
1569 # Default to using items.dateaccessioned if there's an old item modification rule
1570 # missing an agefield value
1571 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1572 my $conditions = $rule->{conditions};
1573 my $substitutions = $rule->{substitutions};
1574 foreach ( @$substitutions ) {
1575 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1582 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1585 for my $condition ( @$conditions ) {
1586 next unless $condition->{field};
1588 grep { $_ eq $condition->{field} } @item_columns
1589 or grep { $_ eq $condition->{field} } @biblioitem_columns
1591 if ( $condition->{value} =~ /\|/ ) {
1592 my @values = split /\|/, $condition->{value};
1593 $query .= qq| AND $condition->{field} IN (|
1594 . join( ',', ('?') x scalar @values )
1596 push @params, @values;
1598 $query .= qq| AND $condition->{field} = ?|;
1599 push @params, $condition->{value};
1603 if ( defined $age ) {
1604 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1607 my $sth = $dbh->prepare($query);
1608 $sth->execute( @params );
1609 while ( my $values = $sth->fetchrow_hashref ) {
1610 my $biblionumber = $values->{biblionumber};
1611 my $itemnumber = $values->{itemnumber};
1612 my $item = Koha::Items->find($itemnumber);
1613 for my $substitution ( @$substitutions ) {
1614 my $field = $substitution->{item_field};
1615 my $value = $substitution->{value};
1616 next unless $substitution->{field};
1617 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1618 $item->$field($value);
1619 push @{ $report->{$itemnumber} }, $substitution;
1621 $item->store unless $report_only;