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({ skip_holds_queue => 1 });
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 # When importing and replacing items we should not remove the dateacquired so we should set it
311 # to the existing value
312 $item->{dateaccessioned} = $item_object->dateaccessioned
313 if ( $item_object->dateaccessioned && !defined $item->{dateaccessioned} );
315 my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
316 my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
318 # Retrieving the values for the fields that are not linked
319 my @mapped_fields = Koha::MarcSubfieldStructures->search(
321 frameworkcode => $frameworkcode,
322 kohafield => { -like => "items.%" }
324 )->get_column('kohafield');
325 for my $c ( $item_object->_result->result_source->columns ) {
326 next if grep { "items.$c" eq $_ } @mapped_fields;
327 $item->{$c} = $item_object->$c;
330 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
331 delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
332 $item->{itemnumber} = $itemnumber;
333 $item->{biblionumber} = $biblionumber;
335 my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
336 # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
337 $item_object = $item_object->set_or_blank($item);
338 $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
340 $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
342 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
343 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
344 $item_object->store({ skip_record_index => $params->{skip_record_index} });
346 return $item_object->unblessed;
349 =head2 ModItemTransfer
351 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
353 Marks an item as being transferred from one branch to another and records the trigger.
355 The last optional parameter allows for passing skip_record_index through to the items store call.
359 sub ModItemTransfer {
360 my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
362 my $dbh = C4::Context->dbh;
363 my $item = Koha::Items->find( $itemnumber );
365 # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
366 # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
367 # will have been preceded by a check of branch transfer limits)
368 my $to_library = Koha::Libraries->find($tobranch);
369 my $transfer = $item->request_transfer(
378 # Immediately set the item to in transit if it is checked in
379 if ( !$item->checkout ) {
380 $item->holdingbranch($frombranch)->store(
383 skip_record_index => 1, # avoid indexing duplication, let ->transit handle it
386 $transfer->transit({ skip_record_index => $params->{skip_record_index} });
392 =head2 ModDateLastSeen
394 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
396 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
397 C<$itemnumber> is the item number
398 C<$leave_item_lost> determines if a lost item will be found or remain lost
400 The last optional parameter allows for passing skip_record_index through to the items store call.
404 sub ModDateLastSeen {
405 my ( $itemnumber, $leave_item_lost, $params ) = @_;
407 my $item = Koha::Items->find($itemnumber);
408 $item->datelastseen(dt_from_string);
409 my $log = $item->itemlost && !$leave_item_lost ? 1 : 0; # If item was lost, record the change to the item
410 $item->itemlost(0) unless $leave_item_lost;
411 $item->store({ log_action => $log, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
414 =head2 CheckItemPreSave
416 my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
418 my %errors = CheckItemPreSave($item_ref);
419 if (exists $errors{'duplicate_barcode'}) {
420 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
421 } elsif (exists $errors{'invalid_homebranch'}) {
422 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
423 } elsif (exists $errors{'invalid_holdingbranch'}) {
424 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
429 Given a hashref containing item fields, determine if it can be
430 inserted or updated in the database. Specifically, checks for
431 database integrity issues, and returns a hash containing any
432 of the following keys, if applicable.
436 =item duplicate_barcode
438 Barcode, if it duplicates one already found in the database.
440 =item invalid_homebranch
442 Home branch, if not defined in branches table.
444 =item invalid_holdingbranch
446 Holding branch, if not defined in branches table.
450 This function does NOT implement any policy-related checks,
451 e.g., whether current operator is allowed to save an
452 item that has a given branch code.
456 sub CheckItemPreSave {
457 my $item_ref = shift;
461 # check for duplicate barcode
462 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
463 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
464 if ($existing_item) {
465 if (!exists $item_ref->{'itemnumber'} # new item
466 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
467 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
472 # check for valid home branch
473 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
474 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
475 unless (defined $home_library) {
476 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
480 # check for valid holding branch
481 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
482 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
483 unless (defined $holding_library) {
484 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
492 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
494 The following functions provide various ways of
495 getting an item record, a set of item records, or
496 lists of authorized values for certain item fields.
500 =head2 GetItemsForInventory
502 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
503 minlocation => $minlocation,
504 maxlocation => $maxlocation,
505 location => $location,
506 ignoreissued => $ignoreissued,
507 datelastseen => $datelastseen,
508 branchcode => $branchcode,
512 statushash => $statushash,
513 itemtypes => \@itemsarray,
516 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
518 The sub returns a reference to a list of hashes, each containing
519 itemnumber, author, title, barcode, item callnumber, and date last
520 seen. It is ordered by callnumber then title.
522 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
523 the datelastseen can be used to specify that you want to see items not seen since a past date only.
524 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
525 $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.
527 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
531 sub GetItemsForInventory {
532 my ( $parameters ) = @_;
533 my $minlocation = $parameters->{'minlocation'} // '';
534 my $maxlocation = $parameters->{'maxlocation'} // '';
535 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
536 my $location = $parameters->{'location'} // '';
537 my $itemtype = $parameters->{'itemtype'} // '';
538 my $ignoreissued = $parameters->{'ignoreissued'} // '';
539 my $datelastseen = $parameters->{'datelastseen'} // '';
540 my $branchcode = $parameters->{'branchcode'} // '';
541 my $branch = $parameters->{'branch'} // '';
542 my $offset = $parameters->{'offset'} // '';
543 my $size = $parameters->{'size'} // '';
544 my $statushash = $parameters->{'statushash'} // '';
545 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
546 my $itemtypes = $parameters->{'itemtypes'} || [];
547 my $ccode = $parameters->{'ccode'} // '';
549 my $dbh = C4::Context->dbh;
550 my ( @bind_params, @where_strings );
552 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
553 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
555 my $select_columns = q{
556 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
559 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
562 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
563 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
566 for my $authvfield (keys %$statushash){
567 if ( scalar @{$statushash->{$authvfield}} > 0 ){
568 my $joinedvals = join ',', @{$statushash->{$authvfield}};
569 push @where_strings, "$authvfield in (" . $joinedvals . ")";
575 push @where_strings, 'ccode = ?';
576 push @bind_params, $ccode;
580 push @where_strings, 'items.cn_sort >= ?';
581 push @bind_params, $min_cnsort;
585 push @where_strings, 'items.cn_sort <= ?';
586 push @bind_params, $max_cnsort;
590 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
591 push @bind_params, $datelastseen;
595 push @where_strings, 'items.location = ?';
596 push @bind_params, $location;
600 if($branch eq "homebranch"){
601 push @where_strings, 'items.homebranch = ?';
603 push @where_strings, 'items.holdingbranch = ?';
605 push @bind_params, $branchcode;
609 push @where_strings, 'biblioitems.itemtype = ?';
610 push @bind_params, $itemtype;
612 if ( $ignoreissued) {
613 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
614 push @where_strings, 'issues.date_due IS NULL';
617 if ( $ignore_waiting_holds ) {
618 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
619 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
623 my $itemtypes_str = join ', ', @$itemtypes;
624 push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
627 if ( @where_strings ) {
629 $query .= join ' AND ', @where_strings;
631 my $count_query = $select_count . $query;
632 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
633 $query .= " LIMIT $offset, $size" if ($offset and $size);
634 $query = $select_columns . $query;
635 my $sth = $dbh->prepare($query);
636 $sth->execute( @bind_params );
639 my $tmpresults = $sth->fetchall_arrayref({});
640 $sth = $dbh->prepare( $count_query );
641 $sth->execute( @bind_params );
642 my ($iTotalRecords) = $sth->fetchrow_array();
644 my @avs = Koha::AuthorisedValues->search(
645 { 'marc_subfield_structures.kohafield' => { '>' => '' },
646 'me.authorised_value' => { '>' => '' },
648 { join => { category => 'marc_subfield_structures' },
649 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
650 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
651 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
655 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
657 foreach my $row (@$tmpresults) {
660 foreach (keys %$row) {
663 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
666 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
672 return (\@results, $iTotalRecords);
675 =head2 get_hostitemnumbers_of
677 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
679 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
681 Return a reference on a hash where key is a biblionumber and values are
682 references on array of itemnumbers.
687 sub get_hostitemnumbers_of {
688 my ($biblionumber) = @_;
690 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
694 my $biblio = Koha::Biblios->find($biblionumber);
695 my $marcrecord = $biblio->metadata->record;
696 return unless $marcrecord;
698 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
700 my $marcflavor = C4::Context->preference('marcflavour');
701 if ( $marcflavor eq 'MARC21' ) {
706 elsif ( $marcflavor eq 'UNIMARC' ) {
712 foreach my $hostfield ( $marcrecord->field($tag) ) {
713 my $hostbiblionumber = $hostfield->subfield($biblio_s);
714 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
715 my $linkeditemnumber = $hostfield->subfield($item_s);
716 if ( ! $linkeditemnumber ) {
717 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
720 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
721 push @returnhostitemnumbers, $linkeditemnumber
725 return @returnhostitemnumbers;
728 =head1 LIMITED USE FUNCTIONS
730 The following functions, while part of the public API,
731 are not exported. This is generally because they are
732 meant to be used by only one script for a specific
733 purpose, and should not be used in any other context
734 without careful thought.
740 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
742 Returns MARC::Record of the item passed in parameter.
743 This function is meant for use only in C<cataloguing/additem.pl>,
744 where it is needed to support that script's MARC-like
750 my ( $biblionumber, $itemnumber ) = @_;
752 # GetMarcItem has been revised so that it does the following:
753 # 1. Gets the item information from the items table.
754 # 2. Converts it to a MARC field for storage in the bib record.
756 # The previous behavior was:
757 # 1. Get the bib record.
758 # 2. Return the MARC tag corresponding to the item record.
760 # The difference is that one treats the items row as authoritative,
761 # while the other treats the MARC representation as authoritative
762 # under certain circumstances.
764 my $item = Koha::Items->find($itemnumber) or return;
766 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
767 # Also, don't emit a subfield if the underlying field is blank.
769 return Item2Marc($item->unblessed, $biblionumber);
773 my ($itemrecord,$biblionumber)=@_;
776 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
777 } keys %{ $itemrecord }
779 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
780 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
781 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
783 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
784 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
785 foreach my $field ($itemmarc->field($itemtag)){
786 $field->add_subfields(@$unlinked_item_subfields);
792 =head1 PRIVATE FUNCTIONS AND VARIABLES
794 The following functions are not meant to be called
795 directly, but are documented in order to explain
796 the inner workings of C<C4::Items>.
800 =head2 _marc_from_item_hash
802 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
804 Given an item hash representing a complete item record,
805 create a C<MARC::Record> object containing an embedded
806 tag representing that item.
808 The third, optional parameter C<$unlinked_item_subfields> is
809 an arrayref of subfields (not mapped to C<items> fields per the
810 framework) to be added to the MARC representation
815 sub _marc_from_item_hash {
817 my $frameworkcode = shift;
818 my $unlinked_item_subfields;
820 $unlinked_item_subfields = shift;
823 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
824 # Also, don't emit a subfield if the underlying field is blank.
825 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
826 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
827 : () } keys %{ $item } };
829 my $item_marc = MARC::Record->new();
830 foreach my $item_field ( keys %{$mungeditem} ) {
831 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
832 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
833 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
834 foreach my $value (@values){
835 if ( my $field = $item_marc->field($tag) ) {
836 $field->add_subfields( $subfield => $value );
838 my $add_subfields = [];
839 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
840 $add_subfields = $unlinked_item_subfields;
842 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
850 =head2 _repack_item_errors
852 Add an error message hash generated by C<CheckItemPreSave>
857 sub _repack_item_errors {
858 my $item_sequence_num = shift;
859 my $item_ref = shift;
860 my $error_ref = shift;
862 my @repacked_errors = ();
864 foreach my $error_code (sort keys %{ $error_ref }) {
865 my $repacked_error = {};
866 $repacked_error->{'item_sequence'} = $item_sequence_num;
867 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
868 $repacked_error->{'error_code'} = $error_code;
869 $repacked_error->{'error_information'} = $error_ref->{$error_code};
870 push @repacked_errors, $repacked_error;
873 return @repacked_errors;
876 =head2 _get_unlinked_item_subfields
878 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
882 sub _get_unlinked_item_subfields {
883 my $original_item_marc = shift;
884 my $frameworkcode = shift;
886 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
888 # assume that this record has only one field, and that that
889 # field contains only the item information
891 my @fields = $original_item_marc->fields();
893 my $field = $fields[0];
894 my $tag = $field->tag();
895 foreach my $subfield ($field->subfields()) {
896 if (defined $subfield->[1] and
897 $subfield->[1] ne '' and
898 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
899 push @$subfields, $subfield->[0] => $subfield->[1];
906 =head2 _get_unlinked_subfields_xml
908 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
912 sub _get_unlinked_subfields_xml {
913 my $unlinked_item_subfields = shift;
916 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
917 my $marc = MARC::Record->new();
918 # use of tag 999 is arbitrary, and doesn't need to match the item tag
919 # used in the framework
920 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
921 $marc->encoding("UTF-8");
922 $xml = $marc->as_xml("USMARC");
928 =head2 _parse_unlinked_item_subfields_from_xml
930 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
934 sub _parse_unlinked_item_subfields_from_xml {
937 return unless defined $xml and $xml ne "";
938 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
939 my $unlinked_subfields = [];
940 my @fields = $marc->fields();
942 foreach my $subfield ($fields[0]->subfields()) {
943 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
946 return $unlinked_subfields;
949 =head2 GetAnalyticsCount
951 $count= &GetAnalyticsCount($itemnumber)
953 counts Usage of itemnumber in Analytical bibliorecords.
957 sub GetAnalyticsCount {
958 my ($itemnumber) = @_;
960 if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
966 $query= "hi=".$itemnumber;
967 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
968 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
972 sub _SearchItems_build_where_fragment {
975 my $dbh = C4::Context->dbh;
978 if (exists($filter->{conjunction})) {
979 my (@where_strs, @where_args);
980 foreach my $f (@{ $filter->{filters} }) {
981 my $fragment = _SearchItems_build_where_fragment($f);
983 push @where_strs, $fragment->{str};
984 push @where_args, @{ $fragment->{args} };
989 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
992 args => \@where_args,
996 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
997 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
998 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
999 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1000 my @operators = qw(= != > < >= <= is like);
1001 push @operators, 'not like';
1002 my $field = $filter->{field} // q{};
1003 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1004 my $op = $filter->{operator};
1005 my $query = $filter->{query};
1006 my $ifnull = $filter->{ifnull};
1008 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1009 $op = '='; # default operator
1013 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1015 my $marcsubfield = $2;
1016 my ($kohafield) = $dbh->selectrow_array(q|
1017 SELECT kohafield FROM marc_subfield_structure
1018 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1019 |, undef, $marcfield, $marcsubfield);
1022 $column = $kohafield;
1024 # MARC field is not linked to a DB field so we need to use
1025 # ExtractValue on marcxml from biblio_metadata or
1026 # items.more_subfields_xml, depending on the MARC field.
1029 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1030 if ($marcfield eq $itemfield) {
1031 $sqlfield = 'more_subfields_xml';
1032 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1034 $sqlfield = 'metadata'; # From biblio_metadata
1035 if ($marcfield < 10) {
1036 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1038 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1041 $column = "ExtractValue($sqlfield, '$xpath')";
1044 elsif ($field eq 'isbn') {
1045 if ( C4::Context->preference("SearchWithISBNVariations") and $query ) {
1046 my @isbns = C4::Koha::GetVariationsOfISBN( $query );
1048 push @$query, @isbns;
1052 elsif ($field eq 'issn') {
1053 if ( C4::Context->preference("SearchWithISSNVariations") and $query ) {
1054 my @issns = C4::Koha::GetVariationsOfISSN( $query );
1056 push @$query, @issns;
1063 if ( defined $ifnull ) {
1064 $column = "COALESCE($column, ?)";
1067 if (ref $query eq 'ARRAY') {
1068 if ($op eq 'like') {
1070 str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")",
1077 } elsif ($op eq '!=') {
1081 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1085 } elsif ( $op eq 'is' ) {
1087 str => "$column $op $query",
1092 str => "$column $op ?",
1097 if ( defined $ifnull ) {
1098 unshift @{ $where_fragment->{args} }, $ifnull;
1103 return $where_fragment;
1108 my ($items, $total) = SearchItems($filter, $params);
1110 Perform a search among items
1112 $filter is a reference to a hash which can be a filter, or a combination of filters.
1114 A filter has the following keys:
1118 =item * field: the name of a SQL column in table items
1120 =item * query: the value to search in this column
1122 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1126 A combination of filters hash the following keys:
1130 =item * conjunction: 'AND' or 'OR'
1132 =item * filters: array ref of filters
1136 $params is a reference to a hash that can contain the following parameters:
1140 =item * rows: Number of items to return. 0 returns everything (default: 0)
1142 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1145 =item * sortby: A SQL column name in items table to sort on
1147 =item * sortorder: 'ASC' or 'DESC'
1154 my ($filter, $params) = @_;
1158 return unless ref $filter eq 'HASH';
1159 return unless ref $params eq 'HASH';
1161 # Default parameters
1162 $params->{rows} ||= 0;
1163 $params->{page} ||= 1;
1164 $params->{sortby} ||= 'itemnumber';
1165 $params->{sortorder} ||= 'ASC';
1167 my ($where_str, @where_args);
1168 my $where_fragment = _SearchItems_build_where_fragment($filter);
1169 if ($where_fragment) {
1170 $where_str = $where_fragment->{str};
1171 @where_args = @{ $where_fragment->{args} };
1174 my $dbh = C4::Context->dbh;
1176 SELECT SQL_CALC_FOUND_ROWS items.*
1178 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1179 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1180 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1181 LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1184 if (defined $where_str and $where_str ne '') {
1185 $query .= qq{ AND $where_str };
1188 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1189 push @where_args, C4::Context->preference('marcflavour');
1191 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1192 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1193 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1194 push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1196 if ( $params->{sortby} eq 'availability' ) {
1197 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1198 $query .= qq{ ORDER BY onloan $sortorder };
1200 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1201 ? $params->{sortby} : 'itemnumber';
1202 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1203 $query .= qq{ ORDER BY $sortby $sortorder };
1206 my $rows = $params->{rows};
1209 my $offset = $rows * ($params->{page}-1);
1210 $query .= qq { LIMIT ?, ? };
1211 push @limit_args, $offset, $rows;
1214 my $sth = $dbh->prepare($query);
1215 my $rv = $sth->execute(@where_args, @limit_args);
1217 return unless ($rv);
1218 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1220 return ($sth->fetchall_arrayref({}), $total_rows);
1224 =head1 OTHER FUNCTIONS
1228 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1230 Find the given $subfield in the given $tag in the given
1231 MARC::Record $record. If the subfield is found, returns
1232 the (indicators, value) pair; otherwise, (undef, undef) is
1236 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1237 I suggest we export it from this module.
1242 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1245 if ( $tagfield < 10 ) {
1246 if ( $record->field($tagfield) ) {
1247 push @result, $record->field($tagfield)->data();
1252 foreach my $field ( $record->field($tagfield) ) {
1253 my @subfields = $field->subfields();
1254 foreach my $subfield (@subfields) {
1255 if ( @$subfield[0] eq $insubfield ) {
1256 push @result, @$subfield[1];
1257 $indicator = $field->indicator(1) . $field->indicator(2);
1262 return ( $indicator, @result );
1266 =head2 PrepareItemrecordDisplay
1268 PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1270 Returns a hash with all the fields for Display a given item data in a template
1272 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1274 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1278 sub PrepareItemrecordDisplay {
1280 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1282 my $dbh = C4::Context->dbh;
1283 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1284 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1286 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1287 # a shared data structure. No plugin (including custom ones) should change
1288 # its contents. See also GetMarcStructure.
1289 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1291 # Pick the default location from NewItemsDefaultLocation
1292 if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1293 $defaultvalues //= {};
1294 $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1297 # return nothing if we don't have found an existing framework.
1298 return q{} unless $tagslib;
1301 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1305 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1307 SELECT authorised_value,lib FROM authorised_values
1310 LEFT JOIN authorised_values_branches ON ( id = av_id )
1315 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1316 $query .= qq{ ORDER BY lib};
1317 my $authorised_values_sth = $dbh->prepare( $query );
1318 foreach my $tag ( sort keys %{$tagslib} ) {
1321 # loop through each subfield
1323 foreach my $subfield (
1324 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1325 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1326 values %{ $tagslib->{$tag} } )
1328 next unless ( $subfield->{'tab'} );
1329 next if ( $subfield->{'tab'} ne "10" );
1331 $subfield_data{tag} = $tag;
1332 $subfield_data{subfield} = $subfield->{subfield};
1333 $subfield_data{countsubfield} = $cntsubf++;
1334 $subfield_data{kohafield} = $subfield->{kohafield};
1335 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1337 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1338 $subfield_data{marc_lib} = $subfield->{lib};
1339 $subfield_data{mandatory} = $subfield->{mandatory};
1340 $subfield_data{repeatable} = $subfield->{repeatable};
1341 $subfield_data{hidden} = "display:none"
1342 if ( ( $subfield->{hidden} > 4 )
1343 || ( $subfield->{hidden} < -4 ) );
1344 my ( $x, $defaultvalue );
1346 ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1348 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1349 if ( !defined $defaultvalue ) {
1350 $defaultvalue = q||;
1352 $defaultvalue =~ s/"/"/g;
1353 # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1354 my $today_dt = dt_from_string;
1355 my $year = $today_dt->strftime('%Y');
1356 my $shortyear = $today_dt->strftime('%y');
1357 my $month = $today_dt->strftime('%m');
1358 my $day = $today_dt->strftime('%d');
1359 $defaultvalue =~ s/<<YYYY>>/$year/g;
1360 $defaultvalue =~ s/<<YY>>/$shortyear/g;
1361 $defaultvalue =~ s/<<MM>>/$month/g;
1362 $defaultvalue =~ s/<<DD>>/$day/g;
1364 # And <<USER>> with surname (?)
1366 ( C4::Context->userenv
1367 ? C4::Context->userenv->{'surname'}
1368 : "superlibrarian" );
1369 $defaultvalue =~ s/<<USER>>/$username/g;
1372 my $maxlength = $subfield->{maxlength};
1374 # search for itemcallnumber if applicable
1375 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1376 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1377 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1378 my $CNtag = substr( $itemcn_pref, 0, 3 );
1379 next unless my $field = $itemrecord->field($CNtag);
1380 my $CNsubfields = substr( $itemcn_pref, 3 );
1381 $CNsubfields = undef if $CNsubfields eq '';
1382 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1383 last if $defaultvalue;
1386 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1388 && $defaultvalues->{'callnumber'} ) {
1389 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1390 # if the item record exists, only use default value if the item has no callnumber
1391 $defaultvalue = $defaultvalues->{callnumber};
1392 } elsif ( !$itemrecord and $defaultvalues ) {
1393 # if the item record *doesn't* exists, always use the default value
1394 $defaultvalue = $defaultvalues->{callnumber};
1397 if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1399 && $defaultvalues->{'branchcode'} ) {
1400 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1401 $defaultvalue = $defaultvalues->{branchcode};
1404 if ( ( $subfield->{kohafield} eq 'items.location' )
1406 && $defaultvalues->{'location'} ) {
1408 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1409 # if the item record exists, only use default value if the item has no locationr
1410 $defaultvalue = $defaultvalues->{location};
1411 } elsif ( !$itemrecord and $defaultvalues ) {
1412 # if the item record *doesn't* exists, always use the default value
1413 $defaultvalue = $defaultvalues->{location};
1416 if ( ( $subfield->{kohafield} eq 'items.ccode' )
1418 && $defaultvalues->{'ccode'} ) {
1420 if ( !$itemrecord and $defaultvalues ) {
1421 # if the item record *doesn't* exists, always use the default value
1422 $defaultvalue = $defaultvalues->{ccode};
1425 if ( $subfield->{authorised_value} ) {
1426 my @authorised_values;
1429 # builds list, depending on authorised value...
1431 if ( $subfield->{'authorised_value'} eq "branches" ) {
1432 if ( ( C4::Context->preference("IndependentBranches") )
1433 && !C4::Context->IsSuperLibrarian() ) {
1434 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1435 $sth->execute( C4::Context->userenv->{branch} );
1436 push @authorised_values, ""
1437 unless ( $subfield->{mandatory} );
1438 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1439 push @authorised_values, $branchcode;
1440 $authorised_lib{$branchcode} = $branchname;
1443 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1445 push @authorised_values, ""
1446 unless ( $subfield->{mandatory} );
1447 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1448 push @authorised_values, $branchcode;
1449 $authorised_lib{$branchcode} = $branchname;
1453 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1454 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1455 $defaultvalue = $defaultvalues->{branchcode};
1459 } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1460 my $itemtypes = Koha::ItemTypes->search_with_localization;
1461 push @authorised_values, "";
1462 while ( my $itemtype = $itemtypes->next ) {
1463 push @authorised_values, $itemtype->itemtype;
1464 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1466 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1467 $defaultvalue = $defaultvalues->{'itemtype'};
1471 } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1472 push @authorised_values, "";
1474 my $class_sources = GetClassSources();
1475 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1477 foreach my $class_source (sort keys %$class_sources) {
1478 next unless $class_sources->{$class_source}->{'used'} or
1479 ($class_source eq $default_source);
1480 push @authorised_values, $class_source;
1481 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1484 $defaultvalue = $default_source;
1486 #---- "true" authorised value
1488 $authorised_values_sth->execute(
1489 $subfield->{authorised_value},
1490 $branch_limit ? $branch_limit : ()
1492 push @authorised_values, "";
1493 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1494 push @authorised_values, $value;
1495 $authorised_lib{$value} = $lib;
1498 $subfield_data{marc_value} = {
1500 values => \@authorised_values,
1501 default => $defaultvalue // q{},
1502 labels => \%authorised_lib,
1504 } elsif ( $subfield->{value_builder} ) {
1506 require Koha::FrameworkPlugin;
1507 my $plugin = Koha::FrameworkPlugin->new({
1508 name => $subfield->{value_builder},
1511 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1512 $plugin->build( $pars );
1513 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1514 $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1516 if( !$plugin->errstr ) {
1517 #TODO Move html to template; see report 12176/13397
1518 my $tab= $plugin->noclick? '-1': '';
1519 my $class= $plugin->noclick? ' disabled': '';
1520 my $title= $plugin->noclick? 'No popup': 'Tag editor';
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" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1523 warn $plugin->errstr;
1524 $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
1527 elsif ( $tag eq '' ) { # it's an hidden field
1528 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1530 elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1531 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1533 elsif ( length($defaultvalue) > 100
1534 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1535 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1536 or (C4::Context->preference("marcflavour") eq "MARC21" and
1537 500 <= $tag && $tag < 600 )
1539 # oversize field (textarea)
1540 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1542 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1544 push( @loop_data, \%subfield_data );
1549 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1550 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1553 'itemtagfield' => $itemtagfield,
1554 'itemtagsubfield' => $itemtagsubfield,
1555 'itemnumber' => $itemnumber,
1556 'iteminformation' => \@loop_data
1560 sub ToggleNewStatus {
1562 my @rules = @{ $params->{rules} };
1563 my $report_only = $params->{report_only};
1565 my $dbh = C4::Context->dbh;
1567 my @item_columns = map { "items.$_" } Koha::Items->columns;
1568 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1569 my @biblio_columns = map { "biblio.$_" } Koha::Biblios->columns;
1571 for my $rule (@rules) {
1572 my $age = $rule->{age};
1574 # Default to using items.dateaccessioned if there's an old item modification rule
1575 # missing an agefield value
1576 my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1577 my $conditions = $rule->{conditions};
1578 my $substitutions = $rule->{substitutions};
1579 foreach (@$substitutions) {
1580 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1587 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1588 LEFT JOIN biblio ON biblio.biblionumber = biblioitems.biblionumber
1591 for my $condition (@$conditions) {
1592 next unless $condition->{field};
1593 if ( grep { $_ eq $condition->{field} } @item_columns
1594 or grep { $_ eq $condition->{field} } @biblioitem_columns
1595 or grep { $_ eq $condition->{field} } @biblio_columns )
1597 if ( $condition->{value} =~ /\|/ ) {
1598 my @values = split /\|/, $condition->{value};
1599 $query .= qq| AND $condition->{field} IN (| . join( ',', ('?') x scalar @values ) . q|)|;
1600 push @params, @values;
1602 $query .= qq| AND $condition->{field} = ?|;
1603 push @params, $condition->{value};
1607 if ( defined $age ) {
1608 $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1611 my $sth = $dbh->prepare($query);
1612 $sth->execute(@params);
1613 while ( my $values = $sth->fetchrow_hashref ) {
1614 my $biblionumber = $values->{biblionumber};
1615 my $itemnumber = $values->{itemnumber};
1616 my $item = Koha::Items->find($itemnumber);
1617 for my $substitution (@$substitutions) {
1618 my $field = $substitution->{item_field};
1619 my $value = $substitution->{value};
1620 next unless $substitution->{field};
1622 if ( defined $values->{ $substitution->{item_field} }
1623 and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1624 $item->$field($value);
1625 push @{ $report->{$itemnumber} }, $substitution;
1627 $item->store unless $report_only;