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);
404 $item->itemlost(0) unless $leave_item_lost;
405 $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
408 =head2 CheckItemPreSave
410 my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
412 my %errors = CheckItemPreSave($item_ref);
413 if (exists $errors{'duplicate_barcode'}) {
414 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
415 } elsif (exists $errors{'invalid_homebranch'}) {
416 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
417 } elsif (exists $errors{'invalid_holdingbranch'}) {
418 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
423 Given a hashref containing item fields, determine if it can be
424 inserted or updated in the database. Specifically, checks for
425 database integrity issues, and returns a hash containing any
426 of the following keys, if applicable.
430 =item duplicate_barcode
432 Barcode, if it duplicates one already found in the database.
434 =item invalid_homebranch
436 Home branch, if not defined in branches table.
438 =item invalid_holdingbranch
440 Holding branch, if not defined in branches table.
444 This function does NOT implement any policy-related checks,
445 e.g., whether current operator is allowed to save an
446 item that has a given branch code.
450 sub CheckItemPreSave {
451 my $item_ref = shift;
455 # check for duplicate barcode
456 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
457 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
458 if ($existing_item) {
459 if (!exists $item_ref->{'itemnumber'} # new item
460 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
461 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
466 # check for valid home branch
467 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
468 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
469 unless (defined $home_library) {
470 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
474 # check for valid holding branch
475 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
476 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
477 unless (defined $holding_library) {
478 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
486 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
488 The following functions provide various ways of
489 getting an item record, a set of item records, or
490 lists of authorized values for certain item fields.
494 =head2 GetItemsForInventory
496 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
497 minlocation => $minlocation,
498 maxlocation => $maxlocation,
499 location => $location,
500 ignoreissued => $ignoreissued,
501 datelastseen => $datelastseen,
502 branchcode => $branchcode,
506 statushash => $statushash,
507 itemtypes => \@itemsarray,
510 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
512 The sub returns a reference to a list of hashes, each containing
513 itemnumber, author, title, barcode, item callnumber, and date last
514 seen. It is ordered by callnumber then title.
516 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
517 the datelastseen can be used to specify that you want to see items not seen since a past date only.
518 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
519 $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.
521 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
525 sub GetItemsForInventory {
526 my ( $parameters ) = @_;
527 my $minlocation = $parameters->{'minlocation'} // '';
528 my $maxlocation = $parameters->{'maxlocation'} // '';
529 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
530 my $location = $parameters->{'location'} // '';
531 my $itemtype = $parameters->{'itemtype'} // '';
532 my $ignoreissued = $parameters->{'ignoreissued'} // '';
533 my $datelastseen = $parameters->{'datelastseen'} // '';
534 my $branchcode = $parameters->{'branchcode'} // '';
535 my $branch = $parameters->{'branch'} // '';
536 my $offset = $parameters->{'offset'} // '';
537 my $size = $parameters->{'size'} // '';
538 my $statushash = $parameters->{'statushash'} // '';
539 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
540 my $itemtypes = $parameters->{'itemtypes'} || [];
541 my $ccode = $parameters->{'ccode'} // '';
543 my $dbh = C4::Context->dbh;
544 my ( @bind_params, @where_strings );
546 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
547 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
549 my $select_columns = q{
550 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
553 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
556 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
557 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
560 for my $authvfield (keys %$statushash){
561 if ( scalar @{$statushash->{$authvfield}} > 0 ){
562 my $joinedvals = join ',', @{$statushash->{$authvfield}};
563 push @where_strings, "$authvfield in (" . $joinedvals . ")";
569 push @where_strings, 'ccode = ?';
570 push @bind_params, $ccode;
574 push @where_strings, 'items.cn_sort >= ?';
575 push @bind_params, $min_cnsort;
579 push @where_strings, 'items.cn_sort <= ?';
580 push @bind_params, $max_cnsort;
584 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
585 push @bind_params, $datelastseen;
589 push @where_strings, 'items.location = ?';
590 push @bind_params, $location;
594 if($branch eq "homebranch"){
595 push @where_strings, 'items.homebranch = ?';
597 push @where_strings, 'items.holdingbranch = ?';
599 push @bind_params, $branchcode;
603 push @where_strings, 'biblioitems.itemtype = ?';
604 push @bind_params, $itemtype;
606 if ( $ignoreissued) {
607 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
608 push @where_strings, 'issues.date_due IS NULL';
611 if ( $ignore_waiting_holds ) {
612 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
613 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
617 my $itemtypes_str = join ', ', @$itemtypes;
618 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
621 if ( @where_strings ) {
623 $query .= join ' AND ', @where_strings;
625 my $count_query = $select_count . $query;
626 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
627 $query .= " LIMIT $offset, $size" if ($offset and $size);
628 $query = $select_columns . $query;
629 my $sth = $dbh->prepare($query);
630 $sth->execute( @bind_params );
633 my $tmpresults = $sth->fetchall_arrayref({});
634 $sth = $dbh->prepare( $count_query );
635 $sth->execute( @bind_params );
636 my ($iTotalRecords) = $sth->fetchrow_array();
638 my @avs = Koha::AuthorisedValues->search(
639 { 'marc_subfield_structures.kohafield' => { '>' => '' },
640 'me.authorised_value' => { '>' => '' },
642 { join => { category => 'marc_subfield_structures' },
643 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
644 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
645 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
649 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
651 foreach my $row (@$tmpresults) {
654 foreach (keys %$row) {
657 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
660 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
666 return (\@results, $iTotalRecords);
669 =head2 get_hostitemnumbers_of
671 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
673 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
675 Return a reference on a hash where key is a biblionumber and values are
676 references on array of itemnumbers.
681 sub get_hostitemnumbers_of {
682 my ($biblionumber) = @_;
684 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
688 my $biblio = Koha::Biblios->find($biblionumber);
689 my $marcrecord = $biblio->metadata->record;
690 return unless $marcrecord;
692 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
694 my $marcflavor = C4::Context->preference('marcflavour');
695 if ( $marcflavor eq 'MARC21' ) {
700 elsif ( $marcflavor eq 'UNIMARC' ) {
706 foreach my $hostfield ( $marcrecord->field($tag) ) {
707 my $hostbiblionumber = $hostfield->subfield($biblio_s);
708 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
709 my $linkeditemnumber = $hostfield->subfield($item_s);
710 if ( ! $linkeditemnumber ) {
711 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
714 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
715 push @returnhostitemnumbers, $linkeditemnumber
719 return @returnhostitemnumbers;
722 =head1 LIMITED USE FUNCTIONS
724 The following functions, while part of the public API,
725 are not exported. This is generally because they are
726 meant to be used by only one script for a specific
727 purpose, and should not be used in any other context
728 without careful thought.
734 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
736 Returns MARC::Record of the item passed in parameter.
737 This function is meant for use only in C<cataloguing/additem.pl>,
738 where it is needed to support that script's MARC-like
744 my ( $biblionumber, $itemnumber ) = @_;
746 # GetMarcItem has been revised so that it does the following:
747 # 1. Gets the item information from the items table.
748 # 2. Converts it to a MARC field for storage in the bib record.
750 # The previous behavior was:
751 # 1. Get the bib record.
752 # 2. Return the MARC tag corresponding to the item record.
754 # The difference is that one treats the items row as authoritative,
755 # while the other treats the MARC representation as authoritative
756 # under certain circumstances.
758 my $item = Koha::Items->find($itemnumber) or return;
760 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
761 # Also, don't emit a subfield if the underlying field is blank.
763 return Item2Marc($item->unblessed, $biblionumber);
767 my ($itemrecord,$biblionumber)=@_;
770 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
771 } keys %{ $itemrecord }
773 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
774 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
775 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
776 "items.itemnumber", $framework,
779 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
780 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
781 foreach my $field ($itemmarc->field($itemtag)){
782 $field->add_subfields(@$unlinked_item_subfields);
788 =head1 PRIVATE FUNCTIONS AND VARIABLES
790 The following functions are not meant to be called
791 directly, but are documented in order to explain
792 the inner workings of C<C4::Items>.
796 =head2 _marc_from_item_hash
798 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
800 Given an item hash representing a complete item record,
801 create a C<MARC::Record> object containing an embedded
802 tag representing that item.
804 The third, optional parameter C<$unlinked_item_subfields> is
805 an arrayref of subfields (not mapped to C<items> fields per the
806 framework) to be added to the MARC representation
811 sub _marc_from_item_hash {
813 my $frameworkcode = shift;
814 my $unlinked_item_subfields;
816 $unlinked_item_subfields = shift;
819 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
820 # Also, don't emit a subfield if the underlying field is blank.
821 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
822 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
823 : () } keys %{ $item } };
825 my $item_marc = MARC::Record->new();
826 foreach my $item_field ( keys %{$mungeditem} ) {
827 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
828 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
829 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
830 foreach my $value (@values){
831 if ( my $field = $item_marc->field($tag) ) {
832 $field->add_subfields( $subfield => $value );
834 my $add_subfields = [];
835 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
836 $add_subfields = $unlinked_item_subfields;
838 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
846 =head2 _repack_item_errors
848 Add an error message hash generated by C<CheckItemPreSave>
853 sub _repack_item_errors {
854 my $item_sequence_num = shift;
855 my $item_ref = shift;
856 my $error_ref = shift;
858 my @repacked_errors = ();
860 foreach my $error_code (sort keys %{ $error_ref }) {
861 my $repacked_error = {};
862 $repacked_error->{'item_sequence'} = $item_sequence_num;
863 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
864 $repacked_error->{'error_code'} = $error_code;
865 $repacked_error->{'error_information'} = $error_ref->{$error_code};
866 push @repacked_errors, $repacked_error;
869 return @repacked_errors;
872 =head2 _get_unlinked_item_subfields
874 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
878 sub _get_unlinked_item_subfields {
879 my $original_item_marc = shift;
880 my $frameworkcode = shift;
882 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
884 # assume that this record has only one field, and that that
885 # field contains only the item information
887 my @fields = $original_item_marc->fields();
889 my $field = $fields[0];
890 my $tag = $field->tag();
891 foreach my $subfield ($field->subfields()) {
892 if (defined $subfield->[1] and
893 $subfield->[1] ne '' and
894 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
895 push @$subfields, $subfield->[0] => $subfield->[1];
902 =head2 _get_unlinked_subfields_xml
904 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
908 sub _get_unlinked_subfields_xml {
909 my $unlinked_item_subfields = shift;
912 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
913 my $marc = MARC::Record->new();
914 # use of tag 999 is arbitrary, and doesn't need to match the item tag
915 # used in the framework
916 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
917 $marc->encoding("UTF-8");
918 $xml = $marc->as_xml("USMARC");
924 =head2 _parse_unlinked_item_subfields_from_xml
926 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
930 sub _parse_unlinked_item_subfields_from_xml {
933 return unless defined $xml and $xml ne "";
934 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
935 my $unlinked_subfields = [];
936 my @fields = $marc->fields();
938 foreach my $subfield ($fields[0]->subfields()) {
939 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
942 return $unlinked_subfields;
945 =head2 GetAnalyticsCount
947 $count= &GetAnalyticsCount($itemnumber)
949 counts Usage of itemnumber in Analytical bibliorecords.
953 sub GetAnalyticsCount {
954 my ($itemnumber) = @_;
956 if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
962 $query= "hi=".$itemnumber;
963 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
964 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
968 sub _SearchItems_build_where_fragment {
971 my $dbh = C4::Context->dbh;
974 if (exists($filter->{conjunction})) {
975 my (@where_strs, @where_args);
976 foreach my $f (@{ $filter->{filters} }) {
977 my $fragment = _SearchItems_build_where_fragment($f);
979 push @where_strs, $fragment->{str};
980 push @where_args, @{ $fragment->{args} };
985 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
988 args => \@where_args,
992 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
993 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
994 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
995 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
996 my @operators = qw(= != > < >= <= is like);
997 push @operators, 'not like';
998 my $field = $filter->{field} // q{};
999 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1000 my $op = $filter->{operator};
1001 my $query = $filter->{query};
1002 my $ifnull = $filter->{ifnull};
1004 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1005 $op = '='; # default operator
1009 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1011 my $marcsubfield = $2;
1012 my ($kohafield) = $dbh->selectrow_array(q|
1013 SELECT kohafield FROM marc_subfield_structure
1014 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1015 |, undef, $marcfield, $marcsubfield);
1018 $column = $kohafield;
1020 # MARC field is not linked to a DB field so we need to use
1021 # ExtractValue on marcxml from biblio_metadata or
1022 # items.more_subfields_xml, depending on the MARC field.
1025 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1026 if ($marcfield eq $itemfield) {
1027 $sqlfield = 'more_subfields_xml';
1028 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1030 $sqlfield = 'metadata'; # From biblio_metadata
1031 if ($marcfield < 10) {
1032 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1034 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1037 $column = "ExtractValue($sqlfield, '$xpath')";
1040 elsif ($field eq 'isbn') {
1041 if ( C4::Context->preference("SearchWithISBNVariations") and $query ) {
1042 my @isbns = C4::Koha::GetVariationsOfISBN( $query );
1044 push @$query, @isbns;
1048 elsif ($field eq 'issn') {
1049 if ( C4::Context->preference("SearchWithISSNVariations") and $query ) {
1050 my @issns = C4::Koha::GetVariationsOfISSN( $query );
1052 push @$query, @issns;
1059 if ( defined $ifnull ) {
1060 $column = "COALESCE($column, ?)";
1063 if (ref $query eq 'ARRAY') {
1064 if ($op eq 'like') {
1066 str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")",
1073 } elsif ($op eq '!=') {
1077 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1081 } elsif ( $op eq 'is' ) {
1083 str => "$column $op $query",
1088 str => "$column $op ?",
1093 if ( defined $ifnull ) {
1094 unshift @{ $where_fragment->{args} }, $ifnull;
1099 return $where_fragment;
1104 my ($items, $total) = SearchItems($filter, $params);
1106 Perform a search among items
1108 $filter is a reference to a hash which can be a filter, or a combination of filters.
1110 A filter has the following keys:
1114 =item * field: the name of a SQL column in table items
1116 =item * query: the value to search in this column
1118 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1122 A combination of filters hash the following keys:
1126 =item * conjunction: 'AND' or 'OR'
1128 =item * filters: array ref of filters
1132 $params is a reference to a hash that can contain the following parameters:
1136 =item * rows: Number of items to return. 0 returns everything (default: 0)
1138 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1141 =item * sortby: A SQL column name in items table to sort on
1143 =item * sortorder: 'ASC' or 'DESC'
1150 my ($filter, $params) = @_;
1154 return unless ref $filter eq 'HASH';
1155 return unless ref $params eq 'HASH';
1157 # Default parameters
1158 $params->{rows} ||= 0;
1159 $params->{page} ||= 1;
1160 $params->{sortby} ||= 'itemnumber';
1161 $params->{sortorder} ||= 'ASC';
1163 my ($where_str, @where_args);
1164 my $where_fragment = _SearchItems_build_where_fragment($filter);
1165 if ($where_fragment) {
1166 $where_str = $where_fragment->{str};
1167 @where_args = @{ $where_fragment->{args} };
1170 my $dbh = C4::Context->dbh;
1172 SELECT SQL_CALC_FOUND_ROWS items.*
1174 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1175 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1176 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1177 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1180 if (defined $where_str and $where_str ne '') {
1181 $query .= qq{ AND $where_str };
1184 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1185 push @where_args, C4::Context->preference('marcflavour');
1187 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1188 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1189 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1190 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1192 if ( $params->{sortby} eq 'availability' ) {
1193 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1194 $query .= qq{ ORDER BY onloan $sortorder };
1196 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1197 ? $params->{sortby} : 'itemnumber';
1198 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1199 $query .= qq{ ORDER BY $sortby $sortorder };
1202 my $rows = $params->{rows};
1205 my $offset = $rows * ($params->{page}-1);
1206 $query .= qq { LIMIT ?, ? };
1207 push @limit_args, $offset, $rows;
1210 my $sth = $dbh->prepare($query);
1211 my $rv = $sth->execute(@where_args, @limit_args);
1213 return unless ($rv);
1214 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1216 return ($sth->fetchall_arrayref({}), $total_rows);
1220 =head1 OTHER FUNCTIONS
1224 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1226 Find the given $subfield in the given $tag in the given
1227 MARC::Record $record. If the subfield is found, returns
1228 the (indicators, value) pair; otherwise, (undef, undef) is
1232 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1233 I suggest we export it from this module.
1238 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1241 if ( $tagfield < 10 ) {
1242 if ( $record->field($tagfield) ) {
1243 push @result, $record->field($tagfield)->data();
1248 foreach my $field ( $record->field($tagfield) ) {
1249 my @subfields = $field->subfields();
1250 foreach my $subfield (@subfields) {
1251 if ( @$subfield[0] eq $insubfield ) {
1252 push @result, @$subfield[1];
1253 $indicator = $field->indicator(1) . $field->indicator(2);
1258 return ( $indicator, @result );
1262 =head2 PrepareItemrecordDisplay
1264 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1266 Returns a hash with all the fields for Display a given item data in a template
1268 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1270 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1274 sub PrepareItemrecordDisplay {
1276 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1278 my $dbh = C4::Context->dbh;
1279 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1280 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1282 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1283 # a shared data structure. No plugin (including custom ones) should change
1284 # its contents. See also GetMarcStructure.
1285 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1287 # Pick the default location from NewItemsDefaultLocation
1288 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1289 $defaultvalues //= {};
1290 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1293 # return nothing if we don't have found an existing framework.
1294 return q{} unless $tagslib;
1297 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1301 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1303 SELECT authorised_value,lib FROM authorised_values
1306 LEFT JOIN authorised_values_branches ON ( id = av_id )
1311 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1312 $query .= qq{ ORDER BY lib};
1313 my $authorised_values_sth = $dbh->prepare( $query );
1314 foreach my $tag ( sort keys %{$tagslib} ) {
1317 # loop through each subfield
1319 foreach my $subfield (
1320 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1321 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1322 values %{ $tagslib->{$tag} } )
1324 next unless ( $subfield->{'tab'} );
1325 next if ( $subfield->{'tab'} ne "10" );
1327 $subfield_data{tag} = $tag;
1328 $subfield_data{subfield} = $subfield->{subfield};
1329 $subfield_data{countsubfield} = $cntsubf++;
1330 $subfield_data{kohafield} = $subfield->{kohafield};
1331 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1333 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1334 $subfield_data{marc_lib} = $subfield->{lib};
1335 $subfield_data{mandatory} = $subfield->{mandatory};
1336 $subfield_data{repeatable} = $subfield->{repeatable};
1337 $subfield_data{hidden} = "display:none"
1338 if ( ( $subfield->{hidden} > 4 )
1339 || ( $subfield->{hidden} < -4 ) );
1340 my ( $x, $defaultvalue );
1342 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1344 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1345 if ( !defined $defaultvalue ) {
1346 $defaultvalue = q||;
1348 $defaultvalue =~ s/"/"/g;
1349 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1350 my $today_dt = dt_from_string;
1351 my $year = $today_dt->strftime('%Y');
1352 my $shortyear = $today_dt->strftime('%y');
1353 my $month = $today_dt->strftime('%m');
1354 my $day = $today_dt->strftime('%d');
1355 $defaultvalue =~ s/<<YYYY>>/$year/g;
1356 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1357 $defaultvalue =~ s/<<MM>>/$month/g;
1358 $defaultvalue =~ s/<<DD>>/$day/g;
1360 # And <<USER>> with surname (?)
1362 ( C4::Context->userenv
1363 ? C4::Context->userenv->{'surname'}
1364 : "superlibrarian" );
1365 $defaultvalue =~ s/<<USER>>/$username/g;
1368 my $maxlength = $subfield->{maxlength};
1370 # search for itemcallnumber if applicable
1371 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1372 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1373 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1374 my $CNtag = substr( $itemcn_pref, 0, 3 );
1375 next unless my $field = $itemrecord->field($CNtag);
1376 my $CNsubfields = substr( $itemcn_pref, 3 );
1377 $CNsubfields = undef if $CNsubfields eq '';
1378 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1379 last if $defaultvalue;
1382 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1384 && $defaultvalues->{'callnumber'} ) {
1385 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1386 # if the item record exists, only use default value if the item has no callnumber
1387 $defaultvalue = $defaultvalues->{callnumber};
1388 } elsif ( !$itemrecord and $defaultvalues ) {
1389 # if the item record *doesn't* exists, always use the default value
1390 $defaultvalue = $defaultvalues->{callnumber};
1393 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1395 && $defaultvalues->{'branchcode'} ) {
1396 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1397 $defaultvalue = $defaultvalues->{branchcode};
1400 if ( ( $subfield->{kohafield} eq 'items.location' )
1402 && $defaultvalues->{'location'} ) {
1404 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1405 # if the item record exists, only use default value if the item has no locationr
1406 $defaultvalue = $defaultvalues->{location};
1407 } elsif ( !$itemrecord and $defaultvalues ) {
1408 # if the item record *doesn't* exists, always use the default value
1409 $defaultvalue = $defaultvalues->{location};
1412 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1414 && $defaultvalues->{'ccode'} ) {
1416 if ( !$itemrecord and $defaultvalues ) {
1417 # if the item record *doesn't* exists, always use the default value
1418 $defaultvalue = $defaultvalues->{ccode};
1421 if ( $subfield->{authorised_value} ) {
1422 my @authorised_values;
1425 # builds list, depending on authorised value...
1427 if ( $subfield->{'authorised_value'} eq "branches" ) {
1428 if ( ( C4::Context->preference("IndependentBranches") )
1429 && !C4::Context->IsSuperLibrarian() ) {
1430 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1431 $sth->execute( C4::Context->userenv->{branch} );
1432 push @authorised_values, ""
1433 unless ( $subfield->{mandatory} );
1434 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1435 push @authorised_values, $branchcode;
1436 $authorised_lib{$branchcode} = $branchname;
1439 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1441 push @authorised_values, ""
1442 unless ( $subfield->{mandatory} );
1443 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1444 push @authorised_values, $branchcode;
1445 $authorised_lib{$branchcode} = $branchname;
1449 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1450 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1451 $defaultvalue = $defaultvalues->{branchcode};
1455 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1456 my $itemtypes = Koha::ItemTypes->search_with_localization;
1457 push @authorised_values, "";
1458 while ( my $itemtype = $itemtypes->next ) {
1459 push @authorised_values, $itemtype->itemtype;
1460 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1462 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1463 $defaultvalue = $defaultvalues->{'itemtype'};
1467 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1468 push @authorised_values, "";
1470 my $class_sources = GetClassSources();
1471 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1473 foreach my $class_source (sort keys %$class_sources) {
1474 next unless $class_sources->{$class_source}->{'used'} or
1475 ($class_source eq $default_source);
1476 push @authorised_values, $class_source;
1477 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1480 $defaultvalue = $default_source;
1482 #---- "true" authorised value
1484 $authorised_values_sth->execute(
1485 $subfield->{authorised_value},
1486 $branch_limit ? $branch_limit : ()
1488 push @authorised_values, "";
1489 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1490 push @authorised_values, $value;
1491 $authorised_lib{$value} = $lib;
1494 $subfield_data{marc_value} = {
1496 values => \@authorised_values,
1497 default => $defaultvalue // q{},
1498 labels => \%authorised_lib,
1500 } elsif ( $subfield->{value_builder} ) {
1502 require Koha::FrameworkPlugin;
1503 my $plugin = Koha::FrameworkPlugin->new({
1504 name => $subfield->{value_builder},
1507 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1508 $plugin->build( $pars );
1509 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1510 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1512 if( !$plugin->errstr ) {
1513 #TODO Move html to template; see report 12176/13397
1514 my $tab= $plugin->noclick? '-1': '';
1515 my $class= $plugin->noclick? ' disabled': '';
1516 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1517 $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;
1519 warn $plugin->errstr;
1520 $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
1523 elsif ( $tag eq '' ) { # it's an hidden field
1524 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1526 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1527 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1529 elsif ( length($defaultvalue) > 100
1530 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1531 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1532 or (C4::Context->preference("marcflavour") eq "MARC21" and
1533 500 <= $tag && $tag < 600 )
1535 # oversize field (textarea)
1536 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1538 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1540 push( @loop_data, \%subfield_data );
1545 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1546 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1549 'itemtagfield' => $itemtagfield,
1550 'itemtagsubfield' => $itemtagsubfield,
1551 'itemnumber' => $itemnumber,
1552 'iteminformation' => \@loop_data
1556 sub ToggleNewStatus {
1557 my ( $params ) = @_;
1558 my @rules = @{ $params->{rules} };
1559 my $report_only = $params->{report_only};
1561 my $dbh = C4::Context->dbh;
1563 my @item_columns = map { "items.$_" } Koha::Items->columns;
1564 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1566 for my $rule ( @rules ) {
1567 my $age = $rule->{age};
1568 # Default to using items.dateaccessioned if there's an old item modification rule
1569 # missing an agefield value
1570 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1571 my $conditions = $rule->{conditions};
1572 my $substitutions = $rule->{substitutions};
1573 foreach ( @$substitutions ) {
1574 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1581 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1584 for my $condition ( @$conditions ) {
1585 next unless $condition->{field};
1587 grep { $_ eq $condition->{field} } @item_columns
1588 or grep { $_ eq $condition->{field} } @biblioitem_columns
1590 if ( $condition->{value} =~ /\|/ ) {
1591 my @values = split /\|/, $condition->{value};
1592 $query .= qq| AND $condition->{field} IN (|
1593 . join( ',', ('?') x scalar @values )
1595 push @params, @values;
1597 $query .= qq| AND $condition->{field} = ?|;
1598 push @params, $condition->{value};
1602 if ( defined $age ) {
1603 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1606 my $sth = $dbh->prepare($query);
1607 $sth->execute( @params );
1608 while ( my $values = $sth->fetchrow_hashref ) {
1609 my $biblionumber = $values->{biblionumber};
1610 my $itemnumber = $values->{itemnumber};
1611 my $item = Koha::Items->find($itemnumber);
1612 for my $substitution ( @$substitutions ) {
1613 my $field = $substitution->{item_field};
1614 my $value = $substitution->{value};
1615 next unless $substitution->{field};
1616 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1617 $item->$field($value);
1618 push @{ $report->{$itemnumber} }, $substitution;
1620 $item->store unless $report_only;