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 use vars qw(@ISA @EXPORT);
42 get_hostitemnumbers_of
50 PrepareItemrecordDisplay
63 use List::MoreUtils qw(any);
65 use DateTime::Format::MySQL;
66 use Data::Dumper; # used as part of logging item record changes, not just for
67 # debugging; so please don't remove this
69 use Koha::AuthorisedValues;
70 use Koha::DateUtils qw(dt_from_string);
73 use Koha::Biblioitems;
76 use Koha::SearchEngine;
77 use Koha::SearchEngine::Search;
82 C4::Items - item management functions
86 This module contains an API for manipulating item
87 records in Koha, and is used by cataloguing, circulation,
88 acquisitions, and serials management.
90 # FIXME This POD is not up-to-date
91 A Koha item record is stored in two places: the
92 items table and embedded in a MARC tag in the XML
93 version of the associated bib record in C<biblioitems.marcxml>.
94 This is done to allow the item information to be readily
95 indexed (e.g., by Zebra), but means that each item
96 modification transaction must keep the items table
97 and the MARC XML in sync at all times.
99 The items table will be considered authoritative. In other
100 words, if there is ever a discrepancy between the items
101 table and the MARC XML, the items table should be considered
104 =head1 HISTORICAL NOTE
106 Most of the functions in C<C4::Items> were originally in
107 the C<C4::Biblio> module.
109 =head1 CORE EXPORTED FUNCTIONS
111 The following functions are meant for use by users
118 CartToShelf($itemnumber);
120 Set the current shelving location of the item record
121 to its stored permanent shelving location. This is
122 primarily used to indicate when an item whose current
123 location is a special processing ('PROC') or shelving cart
124 ('CART') location is back in the stacks.
129 my ( $itemnumber ) = @_;
131 unless ( $itemnumber ) {
132 croak "FAILED CartToShelf() - no itemnumber supplied";
135 my $item = Koha::Items->find($itemnumber);
136 if ( $item->location eq 'CART' ) {
137 ModItem({ location => $item->permanent_location}, undef, $itemnumber);
141 =head2 AddItemFromMarc
143 my ($biblionumber, $biblioitemnumber, $itemnumber)
144 = AddItemFromMarc($source_item_marc, $biblionumber);
146 Given a MARC::Record object containing an embedded item
147 record and a biblionumber, create a new item record.
151 sub AddItemFromMarc {
152 my ( $source_item_marc, $biblionumber ) = @_;
153 my $dbh = C4::Context->dbh;
155 # parse item hash from MARC
156 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
157 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
159 my $localitemmarc = MARC::Record->new;
160 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
163 my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
164 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
165 return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
167 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
168 $item_values->{biblionumber} = $biblionumber;
169 # FIXME RM my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
170 my $item = Koha::Item->new( $item_values ); # FIXME Handle $unlinked_item_subfields
171 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
176 my ($biblionumber, $biblioitemnumber, $itemnumber)
177 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
179 Given a hash containing item column names as keys,
180 create a new Koha item record.
182 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
183 do not need to be supplied for general use; they exist
184 simply to allow them to be picked up from AddItemFromMarc.
186 The final optional parameter, C<$unlinked_item_subfields>, contains
187 an arrayref containing subfields present in the original MARC
188 representation of the item (e.g., from the item editor) that are
189 not mapped to C<items> columns directly but should instead
190 be stored in C<items.more_subfields_xml> and included in
191 the biblio items tag for display and indexing.
197 my $biblionumber = shift;
199 my $dbh = @_ ? shift : C4::Context->dbh;
200 my $unlinked_item_subfields;
202 $unlinked_item_subfields = shift;
205 _set_derived_columns_for_add($item);
206 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
208 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
211 $item->{'itemnumber'} = $itemnumber;
213 C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
215 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
216 if C4::Context->preference("CataloguingLog");
218 _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
220 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
223 =head2 AddItemBatchFromMarc
225 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
226 $biblionumber, $biblioitemnumber, $frameworkcode);
228 Efficiently create item records from a MARC biblio record with
229 embedded item fields. This routine is suitable for batch jobs.
231 This API assumes that the bib record has already been
232 saved to the C<biblio> and C<biblioitems> tables. It does
233 not expect that C<biblio_metadata.metadata> is populated, but it
234 will do so via a call to ModBibiloMarc.
236 The goal of this API is to have a similar effect to using AddBiblio
237 and AddItems in succession, but without inefficient repeated
238 parsing of the MARC XML bib record.
240 This function returns an arrayref of new itemsnumbers and an arrayref of item
241 errors encountered during the processing. Each entry in the errors
242 list is a hashref containing the following keys:
248 Sequence number of original item tag in the MARC record.
252 Item barcode, provide to assist in the construction of
253 useful error messages.
257 Code representing the error condition. Can be 'duplicate_barcode',
258 'invalid_homebranch', or 'invalid_holdingbranch'.
260 =item error_information
262 Additional information appropriate to the error condition.
268 sub AddItemBatchFromMarc {
269 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
271 my @itemnumbers = ();
273 my $dbh = C4::Context->dbh;
275 # We modify the record, so lets work on a clone so we don't change the
277 $record = $record->clone();
278 # loop through the item tags and start creating items
279 my @bad_item_fields = ();
280 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
281 my $item_sequence_num = 0;
282 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
283 $item_sequence_num++;
284 # we take the item field and stick it into a new
285 # MARC record -- this is required so far because (FIXME)
286 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
287 # and there is no TransformMarcFieldToKoha
288 my $temp_item_marc = MARC::Record->new();
289 $temp_item_marc->append_fields($item_field);
291 # add biblionumber and biblioitemnumber
292 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
293 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
294 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
295 $item->{'biblionumber'} = $biblionumber;
296 $item->{'biblioitemnumber'} = $biblioitemnumber;
298 # check for duplicate barcode
299 my %item_errors = CheckItemPreSave($item);
301 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
302 push @bad_item_fields, $item_field;
306 _set_derived_columns_for_add($item);
307 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
308 warn $error if $error;
309 push @itemnumbers, $itemnumber; # FIXME not checking error
310 $item->{'itemnumber'} = $itemnumber;
312 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
314 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
315 $item_field->replace_with($new_item_marc->field($itemtag));
318 # remove any MARC item fields for rejected items
319 foreach my $item_field (@bad_item_fields) {
320 $record->delete_field($item_field);
323 # update the MARC biblio
324 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
326 return (\@itemnumbers, \@errors);
329 =head2 ModItemFromMarc
331 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
333 This function updates an item record based on a supplied
334 C<MARC::Record> object containing an embedded item field.
335 This API is meant for the use of C<additem.pl>; for
336 other purposes, C<ModItem> should be used.
338 This function uses the hash %default_values_for_mod_from_marc,
339 which contains default values for item fields to
340 apply when modifying an item. This is needed because
341 if an item field's value is cleared, TransformMarcToKoha
342 does not include the column in the
343 hash that's passed to ModItem, which without
344 use of this hash makes it impossible to clear
345 an item field's value. See bug 2466.
347 Note that only columns that can be directly
348 changed from the cataloging and serials
349 item editors are included in this hash.
355 sub _build_default_values_for_mod_marc {
356 # Has no framework parameter anymore, since Default is authoritative
357 # for Koha to MARC mappings.
359 my $cache = Koha::Caches->get_instance();
360 my $cache_key = "default_value_for_mod_marc-";
361 my $cached = $cache->get_from_cache($cache_key);
362 return $cached if $cached;
364 my $default_values = {
366 booksellerid => undef,
368 'items.cn_source' => undef,
369 coded_location_qualifier => undef,
373 holdingbranch => undef,
375 itemcallnumber => undef,
378 itemnotes_nonpublic => undef,
381 permanent_location => undef,
386 replacementprice => undef,
387 replacementpricedate => undef,
390 stocknumber => undef,
394 my %default_values_for_mod_from_marc;
395 while ( my ( $field, $default_value ) = each %$default_values ) {
396 my $kohafield = $field;
397 $kohafield =~ s|^([^\.]+)$|items.$1|;
398 $default_values_for_mod_from_marc{$field} = $default_value
399 if C4::Biblio::GetMarcFromKohaField( $kohafield );
402 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
403 return \%default_values_for_mod_from_marc;
406 sub ModItemFromMarc {
407 my $item_marc = shift;
408 my $biblionumber = shift;
409 my $itemnumber = shift;
411 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
412 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
414 my $localitemmarc = MARC::Record->new;
415 $localitemmarc->append_fields( $item_marc->field($itemtag) );
416 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
417 my $default_values = _build_default_values_for_mod_marc();
418 foreach my $item_field ( keys %$default_values ) {
419 $item->{$item_field} = $default_values->{$item_field}
420 unless exists $item->{$item_field};
422 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
424 ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
431 { column => $newvalue },
435 [ unlinked_item_subfields => $unlinked_item_subfields, ]
440 Change one or more columns in an item record.
442 The first argument is a hashref mapping from item column
443 names to the new values. The second and third arguments
444 are the biblionumber and itemnumber, respectively.
445 The fourth, optional parameter (additional_params) may contain the keys
446 unlinked_item_subfields and log_action.
448 C<$unlinked_item_subfields> contains an arrayref containing
449 subfields present in the original MARC
450 representation of the item (e.g., from the item editor) that are
451 not mapped to C<items> columns directly but should instead
452 be stored in C<items.more_subfields_xml> and included in
453 the biblio items tag for display and indexing.
455 If one of the changed columns is used to calculate
456 the derived value of a column such as C<items.cn_sort>,
457 this routine will perform the necessary calculation
460 If log_action is set to false, the action will not be logged.
461 If log_action is true or undefined, the action will be logged.
466 my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
467 my $log_action = $additional_params->{log_action} // 1;
468 my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
470 return unless %$item;
471 $item->{'itemnumber'} = $itemnumber or return;
473 # if $biblionumber is undefined, get it from the current item
474 unless (defined $biblionumber) {
475 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
478 if ($unlinked_item_subfields) {
479 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
482 my @fields = qw( itemlost withdrawn damaged );
484 # Only retrieve the item if we need to set an "on" date field
485 if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
486 my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
487 for my $field (@fields) {
488 if ( defined( $item->{$field} )
489 and not $pre_mod_item->$field
490 and $item->{$field} )
492 $item->{ $field . '_on' } =
493 DateTime::Format::MySQL->format_datetime( dt_from_string() );
498 # If the field is defined but empty, we are removing and,
499 # and thus need to clear out the 'on' field as well
500 for my $field (@fields) {
501 if ( defined( $item->{$field} ) && !$item->{$field} ) {
502 $item->{ $field . '_on' } = undef;
507 _set_derived_columns_for_mod($item);
508 _do_column_fixes_for_mod($item);
511 # attempt to change itemnumber
512 # attempt to change biblionumber (if we want
513 # an API to relink an item to a different bib,
514 # it should be a separate function)
517 _koha_modify_item($item);
519 # request that bib be reindexed so that searching on current
520 # item status is possible
521 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
523 _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
525 logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
526 if $log_action && C4::Context->preference("CataloguingLog");
529 =head2 ModItemTransfer
531 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
533 Marks an item as being transferred from one branch to another and records the trigger.
537 sub ModItemTransfer {
538 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
540 my $dbh = C4::Context->dbh;
541 my $item = Koha::Items->find( $itemnumber );
543 # Remove the 'shelving cart' location status if it is being used.
544 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
546 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
548 #new entry in branchtransfers....
549 my $sth = $dbh->prepare(
550 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
551 VALUES (?, ?, NOW(), ?, ?)");
552 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
554 ModItem({ holdingbranch => $frombranch }, undef, $itemnumber, { log_action => 0 });
555 ModDateLastSeen($itemnumber);
559 =head2 ModDateLastSeen
561 ModDateLastSeen( $itemnumber, $leave_item_lost );
563 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
564 C<$itemnumber> is the item number
565 C<$leave_item_lost> determines if a lost item will be found or remain lost
569 sub ModDateLastSeen {
570 my ( $itemnumber, $leave_item_lost ) = @_;
572 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
575 $params->{datelastseen} = $today;
576 $params->{itemlost} = 0 unless $leave_item_lost;
578 ModItem( $params, undef, $itemnumber, { log_action => 0 } );
583 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
585 Exported function (core API) for deleting an item record in Koha.
592 my $itemnumber = $params->{itemnumber};
593 my $biblionumber = $params->{biblionumber};
595 unless ($biblionumber) {
596 my $item = Koha::Items->find( $itemnumber );
597 $biblionumber = $item ? $item->biblio->biblionumber : undef;
600 # If there is no biblionumber for the given itemnumber, there is nothing to delete
601 return 0 unless $biblionumber;
603 # FIXME check the item has no current issues
604 my $deleted = _koha_delete_item( $itemnumber );
606 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
608 _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
610 #search item field code
611 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
615 =head2 CheckItemPreSave
617 my $item_ref = TransformMarcToKoha($marc, 'items');
619 my %errors = CheckItemPreSave($item_ref);
620 if (exists $errors{'duplicate_barcode'}) {
621 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
622 } elsif (exists $errors{'invalid_homebranch'}) {
623 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
624 } elsif (exists $errors{'invalid_holdingbranch'}) {
625 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
630 Given a hashref containing item fields, determine if it can be
631 inserted or updated in the database. Specifically, checks for
632 database integrity issues, and returns a hash containing any
633 of the following keys, if applicable.
637 =item duplicate_barcode
639 Barcode, if it duplicates one already found in the database.
641 =item invalid_homebranch
643 Home branch, if not defined in branches table.
645 =item invalid_holdingbranch
647 Holding branch, if not defined in branches table.
651 This function does NOT implement any policy-related checks,
652 e.g., whether current operator is allowed to save an
653 item that has a given branch code.
657 sub CheckItemPreSave {
658 my $item_ref = shift;
662 # check for duplicate barcode
663 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
664 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
665 if ($existing_item) {
666 if (!exists $item_ref->{'itemnumber'} # new item
667 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
668 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
673 # check for valid home branch
674 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
675 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
676 unless (defined $home_library) {
677 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
681 # check for valid holding branch
682 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
683 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
684 unless (defined $holding_library) {
685 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
693 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
695 The following functions provide various ways of
696 getting an item record, a set of item records, or
697 lists of authorized values for certain item fields.
701 =head2 GetItemsForInventory
703 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
704 minlocation => $minlocation,
705 maxlocation => $maxlocation,
706 location => $location,
707 itemtype => $itemtype,
708 ignoreissued => $ignoreissued,
709 datelastseen => $datelastseen,
710 branchcode => $branchcode,
714 statushash => $statushash,
717 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
719 The sub returns a reference to a list of hashes, each containing
720 itemnumber, author, title, barcode, item callnumber, and date last
721 seen. It is ordered by callnumber then title.
723 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
724 the datelastseen can be used to specify that you want to see items not seen since a past date only.
725 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
726 $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.
728 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
732 sub GetItemsForInventory {
733 my ( $parameters ) = @_;
734 my $minlocation = $parameters->{'minlocation'} // '';
735 my $maxlocation = $parameters->{'maxlocation'} // '';
736 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
737 my $location = $parameters->{'location'} // '';
738 my $itemtype = $parameters->{'itemtype'} // '';
739 my $ignoreissued = $parameters->{'ignoreissued'} // '';
740 my $datelastseen = $parameters->{'datelastseen'} // '';
741 my $branchcode = $parameters->{'branchcode'} // '';
742 my $branch = $parameters->{'branch'} // '';
743 my $offset = $parameters->{'offset'} // '';
744 my $size = $parameters->{'size'} // '';
745 my $statushash = $parameters->{'statushash'} // '';
746 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
748 my $dbh = C4::Context->dbh;
749 my ( @bind_params, @where_strings );
751 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
752 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
754 my $select_columns = q{
755 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
757 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
760 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
761 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
764 for my $authvfield (keys %$statushash){
765 if ( scalar @{$statushash->{$authvfield}} > 0 ){
766 my $joinedvals = join ',', @{$statushash->{$authvfield}};
767 push @where_strings, "$authvfield in (" . $joinedvals . ")";
773 push @where_strings, 'items.cn_sort >= ?';
774 push @bind_params, $min_cnsort;
778 push @where_strings, 'items.cn_sort <= ?';
779 push @bind_params, $max_cnsort;
783 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
784 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
785 push @bind_params, $datelastseen;
789 push @where_strings, 'items.location = ?';
790 push @bind_params, $location;
794 if($branch eq "homebranch"){
795 push @where_strings, 'items.homebranch = ?';
797 push @where_strings, 'items.holdingbranch = ?';
799 push @bind_params, $branchcode;
803 push @where_strings, 'biblioitems.itemtype = ?';
804 push @bind_params, $itemtype;
807 if ( $ignoreissued) {
808 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
809 push @where_strings, 'issues.date_due IS NULL';
812 if ( $ignore_waiting_holds ) {
813 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
814 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
817 if ( @where_strings ) {
819 $query .= join ' AND ', @where_strings;
821 my $count_query = $select_count . $query;
822 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
823 $query .= " LIMIT $offset, $size" if ($offset and $size);
824 $query = $select_columns . $query;
825 my $sth = $dbh->prepare($query);
826 $sth->execute( @bind_params );
829 my $tmpresults = $sth->fetchall_arrayref({});
830 $sth = $dbh->prepare( $count_query );
831 $sth->execute( @bind_params );
832 my ($iTotalRecords) = $sth->fetchrow_array();
834 my @avs = Koha::AuthorisedValues->search(
835 { 'marc_subfield_structures.kohafield' => { '>' => '' },
836 'me.authorised_value' => { '>' => '' },
838 { join => { category => 'marc_subfield_structures' },
839 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
840 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
841 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
845 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
847 foreach my $row (@$tmpresults) {
850 foreach (keys %$row) {
853 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
856 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
862 return (\@results, $iTotalRecords);
867 @results = GetItemsInfo($biblionumber);
869 Returns information about items with the given biblionumber.
871 C<GetItemsInfo> returns a list of references-to-hash. Each element
872 contains a number of keys. Most of them are attributes from the
873 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
874 Koha database. Other keys include:
878 =item C<$data-E<gt>{branchname}>
880 The name (not the code) of the branch to which the book belongs.
882 =item C<$data-E<gt>{datelastseen}>
884 This is simply C<items.datelastseen>, except that while the date is
885 stored in YYYY-MM-DD format in the database, here it is converted to
886 DD/MM/YYYY format. A NULL date is returned as C<//>.
888 =item C<$data-E<gt>{datedue}>
890 =item C<$data-E<gt>{class}>
892 This is the concatenation of C<biblioitems.classification>, the book's
893 Dewey code, and C<biblioitems.subclass>.
895 =item C<$data-E<gt>{ocount}>
897 I think this is the number of copies of the book available.
899 =item C<$data-E<gt>{order}>
901 If this is set, it is set to C<One Order>.
908 my ( $biblionumber ) = @_;
909 my $dbh = C4::Context->dbh;
910 require C4::Languages;
911 my $language = C4::Languages::getlanguage();
917 biblioitems.itemtype,
920 biblioitems.publicationyear,
921 biblioitems.publishercode,
922 biblioitems.volumedate,
923 biblioitems.volumedesc,
926 items.notforloan as itemnotforloan,
927 issues.borrowernumber,
928 issues.date_due as datedue,
929 issues.onsite_checkout,
930 borrowers.cardnumber,
933 borrowers.branchcode as bcode,
935 serial.publisheddate,
936 itemtypes.description,
937 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
938 itemtypes.notforloan as notforloan_per_itemtype,
942 holding.opac_info as holding_branch_opac_info,
943 home.opac_info as home_branch_opac_info,
944 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
946 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
947 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
948 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
949 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
950 LEFT JOIN issues USING (itemnumber)
951 LEFT JOIN borrowers USING (borrowernumber)
952 LEFT JOIN serialitems USING (itemnumber)
953 LEFT JOIN serial USING (serialid)
954 LEFT JOIN itemtypes ON itemtypes.itemtype = "
955 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
957 LEFT JOIN tmp_holdsqueue USING (itemnumber)
958 LEFT JOIN localization ON itemtypes.itemtype = localization.code
959 AND localization.entity = 'itemtypes'
960 AND localization.lang = ?
963 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
964 my $sth = $dbh->prepare($query);
965 $sth->execute($language, $biblionumber);
970 my $userenv = C4::Context->userenv;
971 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
972 while ( my $data = $sth->fetchrow_hashref ) {
973 if ( $data->{borrowernumber} && $want_not_same_branch) {
974 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
977 $serial ||= $data->{'serial'};
980 # get notforloan complete status if applicable
981 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
982 $data->{notforloanvalue} = $descriptions->{lib} // '';
983 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
985 # get restricted status and description if applicable
986 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
987 $data->{restrictedvalue} = $descriptions->{lib} // '';
988 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
990 # my stack procedures
991 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
992 $data->{stack} = $descriptions->{lib} // '';
994 # Find the last 3 people who borrowed this item.
995 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
997 AND old_issues.borrowernumber = borrowers.borrowernumber
998 ORDER BY returndate DESC
1000 $sth2->execute($data->{'itemnumber'});
1002 while (my $data2 = $sth2->fetchrow_hashref()) {
1003 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1004 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1005 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1009 $results[$i] = $data;
1014 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1018 =head2 GetItemsLocationInfo
1020 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1022 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1024 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1028 =item C<$data-E<gt>{homebranch}>
1030 Branch Name of the item's homebranch
1032 =item C<$data-E<gt>{holdingbranch}>
1034 Branch Name of the item's holdingbranch
1036 =item C<$data-E<gt>{location}>
1038 Item's shelving location code
1040 =item C<$data-E<gt>{location_intranet}>
1042 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1044 =item C<$data-E<gt>{location_opac}>
1046 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1049 =item C<$data-E<gt>{itemcallnumber}>
1051 Item's itemcallnumber
1053 =item C<$data-E<gt>{cn_sort}>
1055 Item's call number normalized for sorting
1061 sub GetItemsLocationInfo {
1062 my $biblionumber = shift;
1065 my $dbh = C4::Context->dbh;
1066 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1067 location, itemcallnumber, cn_sort
1068 FROM items, branches as a, branches as b
1069 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1070 AND biblionumber = ?
1071 ORDER BY cn_sort ASC";
1072 my $sth = $dbh->prepare($query);
1073 $sth->execute($biblionumber);
1075 while ( my $data = $sth->fetchrow_hashref ) {
1076 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1077 $av = $av->count ? $av->next : undef;
1078 $data->{location_intranet} = $av ? $av->lib : '';
1079 $data->{location_opac} = $av ? $av->opac_description : '';
1080 push @results, $data;
1085 =head2 GetHostItemsInfo
1087 $hostiteminfo = GetHostItemsInfo($hostfield);
1088 Returns the iteminfo for items linked to records via a host field
1092 sub GetHostItemsInfo {
1094 my @returnitemsInfo;
1096 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1097 return @returnitemsInfo;
1101 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1102 C4::Context->preference('marcflavour') eq 'NORMARC') {
1103 @fields = $record->field('773');
1104 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1105 @fields = $record->field('461');
1108 foreach my $hostfield ( @fields ) {
1109 my $hostbiblionumber = $hostfield->subfield("0");
1110 my $linkeditemnumber = $hostfield->subfield("9");
1111 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1112 foreach my $hostitemInfo (@hostitemInfos) {
1113 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1114 push @returnitemsInfo, $hostitemInfo;
1119 return @returnitemsInfo;
1122 =head2 get_hostitemnumbers_of
1124 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1126 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1128 Return a reference on a hash where key is a biblionumber and values are
1129 references on array of itemnumbers.
1134 sub get_hostitemnumbers_of {
1135 my ($biblionumber) = @_;
1137 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1141 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1142 return unless $marcrecord;
1144 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1146 my $marcflavor = C4::Context->preference('marcflavour');
1147 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1152 elsif ( $marcflavor eq 'UNIMARC' ) {
1158 foreach my $hostfield ( $marcrecord->field($tag) ) {
1159 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1160 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1161 my $linkeditemnumber = $hostfield->subfield($item_s);
1162 if ( ! $linkeditemnumber ) {
1163 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1166 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1167 push @returnhostitemnumbers, $linkeditemnumber
1171 return @returnhostitemnumbers;
1174 =head2 GetHiddenItemnumbers
1176 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1178 Given a list of items it checks which should be hidden from the OPAC given
1179 the current configuration. Returns a list of itemnumbers corresponding to
1180 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1185 sub GetHiddenItemnumbers {
1187 my $items = $params->{items};
1188 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1189 foreach my $except (split(/\|/, $exceptions)){
1190 if ($params->{'borcat'} eq $except){
1191 return; # we don't hide anything for this borrower category
1197 my $yaml = C4::Context->preference('OpacHiddenItems');
1198 return () if (! $yaml =~ /\S/ );
1199 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1202 $hidingrules = YAML::Load($yaml);
1205 warn "Unable to parse OpacHiddenItems syspref : $@";
1208 my $dbh = C4::Context->dbh;
1211 foreach my $item (@$items) {
1213 # We check each rule
1214 foreach my $field (keys %$hidingrules) {
1216 if (exists $item->{$field}) {
1217 $val = $item->{$field};
1220 my $query = "SELECT $field from items where itemnumber = ?";
1221 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1223 $val = '' unless defined $val;
1225 # If the results matches the values in the yaml file
1226 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1228 # We add the itemnumber to the list
1229 push @resultitems, $item->{'itemnumber'};
1231 # If at least one rule matched for an item, no need to test the others
1236 return @resultitems;
1239 =head1 LIMITED USE FUNCTIONS
1241 The following functions, while part of the public API,
1242 are not exported. This is generally because they are
1243 meant to be used by only one script for a specific
1244 purpose, and should not be used in any other context
1245 without careful thought.
1251 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1253 Returns MARC::Record of the item passed in parameter.
1254 This function is meant for use only in C<cataloguing/additem.pl>,
1255 where it is needed to support that script's MARC-like
1261 my ( $biblionumber, $itemnumber ) = @_;
1263 # GetMarcItem has been revised so that it does the following:
1264 # 1. Gets the item information from the items table.
1265 # 2. Converts it to a MARC field for storage in the bib record.
1267 # The previous behavior was:
1268 # 1. Get the bib record.
1269 # 2. Return the MARC tag corresponding to the item record.
1271 # The difference is that one treats the items row as authoritative,
1272 # while the other treats the MARC representation as authoritative
1273 # under certain circumstances.
1275 my $item = Koha::Items->find($itemnumber) or return;
1277 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1278 # Also, don't emit a subfield if the underlying field is blank.
1280 return Item2Marc($item->unblessed, $biblionumber);
1284 my ($itemrecord,$biblionumber)=@_;
1287 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1288 } keys %{ $itemrecord }
1290 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1291 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1292 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1293 "items.itemnumber", $framework,
1296 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1297 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1298 foreach my $field ($itemmarc->field($itemtag)){
1299 $field->add_subfields(@$unlinked_item_subfields);
1305 =head1 PRIVATE FUNCTIONS AND VARIABLES
1307 The following functions are not meant to be called
1308 directly, but are documented in order to explain
1309 the inner workings of C<C4::Items>.
1313 =head2 %derived_columns
1315 This hash keeps track of item columns that
1316 are strictly derived from other columns in
1317 the item record and are not meant to be set
1320 Each key in the hash should be the name of a
1321 column (as named by TransformMarcToKoha). Each
1322 value should be hashref whose keys are the
1323 columns on which the derived column depends. The
1324 hashref should also contain a 'BUILDER' key
1325 that is a reference to a sub that calculates
1330 my %derived_columns = (
1331 'items.cn_sort' => {
1332 'itemcallnumber' => 1,
1333 'items.cn_source' => 1,
1334 'BUILDER' => \&_calc_items_cn_sort,
1338 =head2 _set_derived_columns_for_add
1340 _set_derived_column_for_add($item);
1342 Given an item hash representing a new item to be added,
1343 calculate any derived columns. Currently the only
1344 such column is C<items.cn_sort>.
1348 sub _set_derived_columns_for_add {
1351 foreach my $column (keys %derived_columns) {
1352 my $builder = $derived_columns{$column}->{'BUILDER'};
1353 my $source_values = {};
1354 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1355 next if $source_column eq 'BUILDER';
1356 $source_values->{$source_column} = $item->{$source_column};
1358 $builder->($item, $source_values);
1362 =head2 _set_derived_columns_for_mod
1364 _set_derived_column_for_mod($item);
1366 Given an item hash representing a new item to be modified.
1367 calculate any derived columns. Currently the only
1368 such column is C<items.cn_sort>.
1370 This routine differs from C<_set_derived_columns_for_add>
1371 in that it needs to handle partial item records. In other
1372 words, the caller of C<ModItem> may have supplied only one
1373 or two columns to be changed, so this function needs to
1374 determine whether any of the columns to be changed affect
1375 any of the derived columns. Also, if a derived column
1376 depends on more than one column, but the caller is not
1377 changing all of then, this routine retrieves the unchanged
1378 values from the database in order to ensure a correct
1383 sub _set_derived_columns_for_mod {
1386 foreach my $column (keys %derived_columns) {
1387 my $builder = $derived_columns{$column}->{'BUILDER'};
1388 my $source_values = {};
1389 my %missing_sources = ();
1390 my $must_recalc = 0;
1391 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1392 next if $source_column eq 'BUILDER';
1393 if (exists $item->{$source_column}) {
1395 $source_values->{$source_column} = $item->{$source_column};
1397 $missing_sources{$source_column} = 1;
1401 foreach my $source_column (keys %missing_sources) {
1402 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1404 $builder->($item, $source_values);
1409 =head2 _do_column_fixes_for_mod
1411 _do_column_fixes_for_mod($item);
1413 Given an item hashref containing one or more
1414 columns to modify, fix up certain values.
1415 Specifically, set to 0 any passed value
1416 of C<notforloan>, C<damaged>, C<itemlost>, or
1417 C<withdrawn> that is either undefined or
1418 contains the empty string.
1422 sub _do_column_fixes_for_mod {
1425 if (exists $item->{'notforloan'} and
1426 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1427 $item->{'notforloan'} = 0;
1429 if (exists $item->{'damaged'} and
1430 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1431 $item->{'damaged'} = 0;
1433 if (exists $item->{'itemlost'} and
1434 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1435 $item->{'itemlost'} = 0;
1437 if (exists $item->{'withdrawn'} and
1438 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1439 $item->{'withdrawn'} = 0;
1442 exists $item->{location}
1443 and ( !defined $item->{location}
1444 || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
1445 and not $item->{permanent_location}
1448 $item->{'permanent_location'} = $item->{'location'};
1450 if (exists $item->{'timestamp'}) {
1451 delete $item->{'timestamp'};
1455 =head2 _get_single_item_column
1457 _get_single_item_column($column, $itemnumber);
1459 Retrieves the value of a single column from an C<items>
1460 row specified by C<$itemnumber>.
1464 sub _get_single_item_column {
1466 my $itemnumber = shift;
1468 my $dbh = C4::Context->dbh;
1469 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1470 $sth->execute($itemnumber);
1471 my ($value) = $sth->fetchrow();
1475 =head2 _calc_items_cn_sort
1477 _calc_items_cn_sort($item, $source_values);
1479 Helper routine to calculate C<items.cn_sort>.
1483 sub _calc_items_cn_sort {
1485 my $source_values = shift;
1487 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1490 =head2 _koha_new_item
1492 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1494 Perform the actual insert into the C<items> table.
1498 sub _koha_new_item {
1499 my ( $item, $barcode ) = @_;
1500 my $dbh=C4::Context->dbh;
1502 $item->{permanent_location} //= $item->{location};
1503 _mod_item_dates( $item );
1505 "INSERT INTO items SET
1507 biblioitemnumber = ?,
1509 dateaccessioned = ?,
1513 replacementprice = ?,
1514 replacementpricedate = ?,
1515 datelastborrowed = ?,
1523 coded_location_qualifier = ?,
1526 itemnotes_nonpublic = ?,
1529 permanent_location = ?,
1541 more_subfields_xml = ?,
1546 my $sth = $dbh->prepare($query);
1547 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1549 $item->{'biblionumber'},
1550 $item->{'biblioitemnumber'},
1552 $item->{'dateaccessioned'},
1553 $item->{'booksellerid'},
1554 $item->{'homebranch'},
1556 $item->{'replacementprice'},
1557 $item->{'replacementpricedate'} || $today,
1558 $item->{datelastborrowed},
1559 $item->{datelastseen} || $today,
1561 $item->{'notforloan'},
1563 $item->{'itemlost'},
1564 $item->{'withdrawn'},
1565 $item->{'itemcallnumber'},
1566 $item->{'coded_location_qualifier'},
1567 $item->{'restricted'},
1568 $item->{'itemnotes'},
1569 $item->{'itemnotes_nonpublic'},
1570 $item->{'holdingbranch'},
1571 $item->{'location'},
1572 $item->{'permanent_location'},
1575 $item->{'renewals'},
1576 $item->{'reserves'},
1577 $item->{'items.cn_source'},
1578 $item->{'items.cn_sort'},
1581 $item->{'materials'},
1583 $item->{'enumchron'},
1584 $item->{'more_subfields_xml'},
1585 $item->{'copynumber'},
1586 $item->{'stocknumber'},
1587 $item->{'new_status'},
1591 if ( defined $sth->errstr ) {
1592 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1595 $itemnumber = $dbh->{'mysql_insertid'};
1598 return ( $itemnumber, $error );
1601 =head2 MoveItemFromBiblio
1603 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1605 Moves an item from a biblio to another
1607 Returns undef if the move failed or the biblionumber of the destination record otherwise
1611 sub MoveItemFromBiblio {
1612 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1613 my $dbh = C4::Context->dbh;
1614 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1615 SELECT biblioitemnumber
1617 WHERE biblionumber = ?
1618 |, undef, $tobiblio );
1619 my $return = $dbh->do(q|
1621 SET biblioitemnumber = ?,
1623 WHERE itemnumber = ?
1624 AND biblionumber = ?
1625 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1627 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1628 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1629 # Checking if the item we want to move is in an order
1630 require C4::Acquisition;
1631 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1633 # Replacing the biblionumber within the order if necessary
1634 $order->{'biblionumber'} = $tobiblio;
1635 C4::Acquisition::ModOrder($order);
1638 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1639 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1642 SET biblionumber = ?
1643 WHERE itemnumber = ?
1644 |, undef, $tobiblio, $itemnumber );
1651 =head2 ItemSafeToDelete
1653 ItemSafeToDelete( $biblionumber, $itemnumber);
1655 Exported function (core API) for checking whether an item record is safe to delete.
1657 returns 1 if the item is safe to delete,
1659 "book_on_loan" if the item is checked out,
1661 "not_same_branch" if the item is blocked by independent branches,
1663 "book_reserved" if the there are holds aganst the item, or
1665 "linked_analytics" if the item has linked analytic records.
1669 sub ItemSafeToDelete {
1670 my ( $biblionumber, $itemnumber ) = @_;
1672 my $dbh = C4::Context->dbh;
1676 my $countanalytics = GetAnalyticsCount($itemnumber);
1678 my $item = Koha::Items->find($itemnumber) or return;
1680 if ($item->checkout) {
1681 $status = "book_on_loan";
1683 elsif ( defined C4::Context->userenv
1684 and !C4::Context->IsSuperLibrarian()
1685 and C4::Context->preference("IndependentBranches")
1686 and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1688 $status = "not_same_branch";
1691 # check it doesn't have a waiting reserve
1692 my $sth = $dbh->prepare(
1694 SELECT COUNT(*) FROM reserves
1695 WHERE (found = 'W' OR found = 'T')
1699 $sth->execute($itemnumber);
1700 my ($reserve) = $sth->fetchrow;
1702 $status = "book_reserved";
1704 elsif ( $countanalytics > 0 ) {
1705 $status = "linked_analytics";
1716 DelItemCheck( $biblionumber, $itemnumber);
1718 Exported function (core API) for deleting an item record in Koha if there no current issue.
1720 DelItemCheck wraps ItemSafeToDelete around DelItem.
1725 my ( $biblionumber, $itemnumber ) = @_;
1726 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1728 if ( $status == 1 ) {
1731 biblionumber => $biblionumber,
1732 itemnumber => $itemnumber
1739 =head2 _koha_modify_item
1741 my ($itemnumber,$error) =_koha_modify_item( $item );
1743 Perform the actual update of the C<items> row. Note that this
1744 routine accepts a hashref specifying the columns to update.
1748 sub _koha_modify_item {
1750 my $dbh=C4::Context->dbh;
1753 my $query = "UPDATE items SET ";
1755 _mod_item_dates( $item );
1756 for my $key ( keys %$item ) {
1757 next if ( $key eq 'itemnumber' );
1759 push @bind, $item->{$key};
1762 $query .= " WHERE itemnumber=?";
1763 push @bind, $item->{'itemnumber'};
1764 my $sth = $dbh->prepare($query);
1765 $sth->execute(@bind);
1767 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1770 return ($item->{'itemnumber'},$error);
1773 sub _mod_item_dates { # date formatting for date fields in item hash
1775 return if !$item || ref($item) ne 'HASH';
1778 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1780 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1781 # NOTE: We do not (yet) have items fields ending with datetime
1782 # Fields with _on$ have been handled already
1784 foreach my $key ( @keys ) {
1785 next if !defined $item->{$key}; # skip undefs
1786 my $dt = eval { dt_from_string( $item->{$key} ) };
1787 # eval: dt_from_string will die on us if we pass illegal dates
1790 if( defined $dt && ref($dt) eq 'DateTime' ) {
1791 if( $key =~ /datetime/ ) {
1792 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1794 $newstr = DateTime::Format::MySQL->format_date($dt);
1797 $item->{$key} = $newstr; # might be undef to clear garbage
1801 =head2 _koha_delete_item
1803 _koha_delete_item( $itemnum );
1805 Internal function to delete an item record from the koha tables
1809 sub _koha_delete_item {
1810 my ( $itemnum ) = @_;
1812 my $dbh = C4::Context->dbh;
1813 # save the deleted item to deleteditems table
1814 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1815 $sth->execute($itemnum);
1816 my $data = $sth->fetchrow_hashref();
1818 # There is no item to delete
1819 return 0 unless $data;
1821 my $query = "INSERT INTO deleteditems SET ";
1823 foreach my $key ( keys %$data ) {
1824 next if ( $key eq 'timestamp' ); # timestamp will be set by db
1825 $query .= "$key = ?,";
1826 push( @bind, $data->{$key} );
1829 $sth = $dbh->prepare($query);
1830 $sth->execute(@bind);
1832 # delete from items table
1833 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1834 my $deleted = $sth->execute($itemnum);
1835 return ( $deleted == 1 ) ? 1 : 0;
1838 =head2 _marc_from_item_hash
1840 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1842 Given an item hash representing a complete item record,
1843 create a C<MARC::Record> object containing an embedded
1844 tag representing that item.
1846 The third, optional parameter C<$unlinked_item_subfields> is
1847 an arrayref of subfields (not mapped to C<items> fields per the
1848 framework) to be added to the MARC representation
1853 sub _marc_from_item_hash {
1855 my $frameworkcode = shift;
1856 my $unlinked_item_subfields;
1858 $unlinked_item_subfields = shift;
1861 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1862 # Also, don't emit a subfield if the underlying field is blank.
1863 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1864 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1865 : () } keys %{ $item } };
1867 my $item_marc = MARC::Record->new();
1868 foreach my $item_field ( keys %{$mungeditem} ) {
1869 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1870 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1871 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1872 foreach my $value (@values){
1873 if ( my $field = $item_marc->field($tag) ) {
1874 $field->add_subfields( $subfield => $value );
1876 my $add_subfields = [];
1877 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1878 $add_subfields = $unlinked_item_subfields;
1880 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1888 =head2 _repack_item_errors
1890 Add an error message hash generated by C<CheckItemPreSave>
1891 to a list of errors.
1895 sub _repack_item_errors {
1896 my $item_sequence_num = shift;
1897 my $item_ref = shift;
1898 my $error_ref = shift;
1900 my @repacked_errors = ();
1902 foreach my $error_code (sort keys %{ $error_ref }) {
1903 my $repacked_error = {};
1904 $repacked_error->{'item_sequence'} = $item_sequence_num;
1905 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1906 $repacked_error->{'error_code'} = $error_code;
1907 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1908 push @repacked_errors, $repacked_error;
1911 return @repacked_errors;
1914 =head2 _get_unlinked_item_subfields
1916 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1920 sub _get_unlinked_item_subfields {
1921 my $original_item_marc = shift;
1922 my $frameworkcode = shift;
1924 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1926 # assume that this record has only one field, and that that
1927 # field contains only the item information
1929 my @fields = $original_item_marc->fields();
1930 if ($#fields > -1) {
1931 my $field = $fields[0];
1932 my $tag = $field->tag();
1933 foreach my $subfield ($field->subfields()) {
1934 if (defined $subfield->[1] and
1935 $subfield->[1] ne '' and
1936 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1937 push @$subfields, $subfield->[0] => $subfield->[1];
1944 =head2 _get_unlinked_subfields_xml
1946 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1950 sub _get_unlinked_subfields_xml {
1951 my $unlinked_item_subfields = shift;
1954 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1955 my $marc = MARC::Record->new();
1956 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1957 # used in the framework
1958 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1959 $marc->encoding("UTF-8");
1960 $xml = $marc->as_xml("USMARC");
1966 =head2 _parse_unlinked_item_subfields_from_xml
1968 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1972 sub _parse_unlinked_item_subfields_from_xml {
1974 require C4::Charset;
1975 return unless defined $xml and $xml ne "";
1976 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1977 my $unlinked_subfields = [];
1978 my @fields = $marc->fields();
1979 if ($#fields > -1) {
1980 foreach my $subfield ($fields[0]->subfields()) {
1981 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1984 return $unlinked_subfields;
1987 =head2 GetAnalyticsCount
1989 $count= &GetAnalyticsCount($itemnumber)
1991 counts Usage of itemnumber in Analytical bibliorecords.
1995 sub GetAnalyticsCount {
1996 my ($itemnumber) = @_;
1998 ### ZOOM search here
2000 $query= "hi=".$itemnumber;
2001 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2002 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2006 sub _SearchItems_build_where_fragment {
2009 my $dbh = C4::Context->dbh;
2012 if (exists($filter->{conjunction})) {
2013 my (@where_strs, @where_args);
2014 foreach my $f (@{ $filter->{filters} }) {
2015 my $fragment = _SearchItems_build_where_fragment($f);
2017 push @where_strs, $fragment->{str};
2018 push @where_args, @{ $fragment->{args} };
2023 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2026 args => \@where_args,
2030 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2031 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2032 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2033 my @operators = qw(= != > < >= <= like);
2034 my $field = $filter->{field} // q{};
2035 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2036 my $op = $filter->{operator};
2037 my $query = $filter->{query};
2039 if (!$op or (0 == grep { $_ eq $op } @operators)) {
2040 $op = '='; # default operator
2044 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2046 my $marcsubfield = $2;
2047 my ($kohafield) = $dbh->selectrow_array(q|
2048 SELECT kohafield FROM marc_subfield_structure
2049 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2050 |, undef, $marcfield, $marcsubfield);
2053 $column = $kohafield;
2055 # MARC field is not linked to a DB field so we need to use
2056 # ExtractValue on marcxml from biblio_metadata or
2057 # items.more_subfields_xml, depending on the MARC field.
2060 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2061 if ($marcfield eq $itemfield) {
2062 $sqlfield = 'more_subfields_xml';
2063 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2065 $sqlfield = 'metadata'; # From biblio_metadata
2066 if ($marcfield < 10) {
2067 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2069 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2072 $column = "ExtractValue($sqlfield, '$xpath')";
2074 } elsif ($field eq 'issues') {
2075 # Consider NULL as 0 for issues count
2076 $column = 'COALESCE(issues,0)';
2081 if (ref $query eq 'ARRAY') {
2084 } elsif ($op eq '!=') {
2088 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2093 str => "$column $op ?",
2100 return $where_fragment;
2105 my ($items, $total) = SearchItems($filter, $params);
2107 Perform a search among items
2109 $filter is a reference to a hash which can be a filter, or a combination of filters.
2111 A filter has the following keys:
2115 =item * field: the name of a SQL column in table items
2117 =item * query: the value to search in this column
2119 =item * operator: comparison operator. Can be one of = != > < >= <= like
2123 A combination of filters hash the following keys:
2127 =item * conjunction: 'AND' or 'OR'
2129 =item * filters: array ref of filters
2133 $params is a reference to a hash that can contain the following parameters:
2137 =item * rows: Number of items to return. 0 returns everything (default: 0)
2139 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2142 =item * sortby: A SQL column name in items table to sort on
2144 =item * sortorder: 'ASC' or 'DESC'
2151 my ($filter, $params) = @_;
2155 return unless ref $filter eq 'HASH';
2156 return unless ref $params eq 'HASH';
2158 # Default parameters
2159 $params->{rows} ||= 0;
2160 $params->{page} ||= 1;
2161 $params->{sortby} ||= 'itemnumber';
2162 $params->{sortorder} ||= 'ASC';
2164 my ($where_str, @where_args);
2165 my $where_fragment = _SearchItems_build_where_fragment($filter);
2166 if ($where_fragment) {
2167 $where_str = $where_fragment->{str};
2168 @where_args = @{ $where_fragment->{args} };
2171 my $dbh = C4::Context->dbh;
2173 SELECT SQL_CALC_FOUND_ROWS items.*
2175 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2176 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2177 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2180 if (defined $where_str and $where_str ne '') {
2181 $query .= qq{ AND $where_str };
2184 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2185 push @where_args, C4::Context->preference('marcflavour');
2187 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2188 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2189 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2190 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2191 ? $params->{sortby} : 'itemnumber';
2192 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2193 $query .= qq{ ORDER BY $sortby $sortorder };
2195 my $rows = $params->{rows};
2198 my $offset = $rows * ($params->{page}-1);
2199 $query .= qq { LIMIT ?, ? };
2200 push @limit_args, $offset, $rows;
2203 my $sth = $dbh->prepare($query);
2204 my $rv = $sth->execute(@where_args, @limit_args);
2206 return unless ($rv);
2207 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2209 return ($sth->fetchall_arrayref({}), $total_rows);
2213 =head1 OTHER FUNCTIONS
2217 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2219 Find the given $subfield in the given $tag in the given
2220 MARC::Record $record. If the subfield is found, returns
2221 the (indicators, value) pair; otherwise, (undef, undef) is
2225 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2226 I suggest we export it from this module.
2231 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2234 if ( $tagfield < 10 ) {
2235 if ( $record->field($tagfield) ) {
2236 push @result, $record->field($tagfield)->data();
2241 foreach my $field ( $record->field($tagfield) ) {
2242 my @subfields = $field->subfields();
2243 foreach my $subfield (@subfields) {
2244 if ( @$subfield[0] eq $insubfield ) {
2245 push @result, @$subfield[1];
2246 $indicator = $field->indicator(1) . $field->indicator(2);
2251 return ( $indicator, @result );
2255 =head2 PrepareItemrecordDisplay
2257 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2259 Returns a hash with all the fields for Display a given item data in a template
2261 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2265 sub PrepareItemrecordDisplay {
2267 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2269 my $dbh = C4::Context->dbh;
2270 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2271 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2273 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2274 # a shared data structure. No plugin (including custom ones) should change
2275 # its contents. See also GetMarcStructure.
2276 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2278 # return nothing if we don't have found an existing framework.
2279 return q{} unless $tagslib;
2282 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2286 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2288 SELECT authorised_value,lib FROM authorised_values
2291 LEFT JOIN authorised_values_branches ON ( id = av_id )
2296 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2297 $query .= qq{ ORDER BY lib};
2298 my $authorised_values_sth = $dbh->prepare( $query );
2299 foreach my $tag ( sort keys %{$tagslib} ) {
2302 # loop through each subfield
2304 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2305 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2306 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2307 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2309 $subfield_data{tag} = $tag;
2310 $subfield_data{subfield} = $subfield;
2311 $subfield_data{countsubfield} = $cntsubf++;
2312 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2313 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2315 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2316 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2317 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2318 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2319 $subfield_data{hidden} = "display:none"
2320 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2321 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2322 my ( $x, $defaultvalue );
2324 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2326 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2327 if ( !defined $defaultvalue ) {
2328 $defaultvalue = q||;
2330 $defaultvalue =~ s/"/"/g;
2333 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2335 # search for itemcallnumber if applicable
2336 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2337 && C4::Context->preference('itemcallnumber') && $itemrecord) {
2338 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2339 my $CNtag = substr( $itemcn_pref, 0, 3 );
2340 next unless my $field = $itemrecord->field($CNtag);
2341 my $CNsubfields = substr( $itemcn_pref, 3 );
2342 $defaultvalue = $field->as_string( $CNsubfields, ' ');
2343 last if $defaultvalue;
2346 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2348 && $defaultvalues->{'callnumber'} ) {
2349 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2350 # if the item record exists, only use default value if the item has no callnumber
2351 $defaultvalue = $defaultvalues->{callnumber};
2352 } elsif ( !$itemrecord and $defaultvalues ) {
2353 # if the item record *doesn't* exists, always use the default value
2354 $defaultvalue = $defaultvalues->{callnumber};
2357 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2359 && $defaultvalues->{'branchcode'} ) {
2360 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2361 $defaultvalue = $defaultvalues->{branchcode};
2364 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2366 && $defaultvalues->{'location'} ) {
2368 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2369 # if the item record exists, only use default value if the item has no locationr
2370 $defaultvalue = $defaultvalues->{location};
2371 } elsif ( !$itemrecord and $defaultvalues ) {
2372 # if the item record *doesn't* exists, always use the default value
2373 $defaultvalue = $defaultvalues->{location};
2376 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2377 my @authorised_values;
2380 # builds list, depending on authorised value...
2382 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2383 if ( ( C4::Context->preference("IndependentBranches") )
2384 && !C4::Context->IsSuperLibrarian() ) {
2385 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2386 $sth->execute( C4::Context->userenv->{branch} );
2387 push @authorised_values, ""
2388 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2389 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2390 push @authorised_values, $branchcode;
2391 $authorised_lib{$branchcode} = $branchname;
2394 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2396 push @authorised_values, ""
2397 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2398 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2399 push @authorised_values, $branchcode;
2400 $authorised_lib{$branchcode} = $branchname;
2404 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2405 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2406 $defaultvalue = $defaultvalues->{branchcode};
2410 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2411 my $itemtypes = Koha::ItemTypes->search_with_localization;
2412 push @authorised_values, ""
2413 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2414 while ( my $itemtype = $itemtypes->next ) {
2415 push @authorised_values, $itemtype->itemtype;
2416 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2418 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2419 $defaultvalue = $defaultvalues->{'itemtype'};
2423 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2424 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2426 my $class_sources = GetClassSources();
2427 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2429 foreach my $class_source (sort keys %$class_sources) {
2430 next unless $class_sources->{$class_source}->{'used'} or
2431 ($class_source eq $default_source);
2432 push @authorised_values, $class_source;
2433 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2436 $defaultvalue = $default_source;
2438 #---- "true" authorised value
2440 $authorised_values_sth->execute(
2441 $tagslib->{$tag}->{$subfield}->{authorised_value},
2442 $branch_limit ? $branch_limit : ()
2444 push @authorised_values, ""
2445 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2446 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2447 push @authorised_values, $value;
2448 $authorised_lib{$value} = $lib;
2451 $subfield_data{marc_value} = {
2453 values => \@authorised_values,
2454 default => $defaultvalue // q{},
2455 labels => \%authorised_lib,
2457 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2459 require Koha::FrameworkPlugin;
2460 my $plugin = Koha::FrameworkPlugin->new({
2461 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2464 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2465 $plugin->build( $pars );
2466 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2467 $defaultvalue = $field->subfield($subfield) || q{};
2469 if( !$plugin->errstr ) {
2470 #TODO Move html to template; see report 12176/13397
2471 my $tab= $plugin->noclick? '-1': '';
2472 my $class= $plugin->noclick? ' disabled': '';
2473 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2474 $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;
2476 warn $plugin->errstr;
2477 $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
2480 elsif ( $tag eq '' ) { # it's an hidden field
2481 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2483 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2484 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2486 elsif ( length($defaultvalue) > 100
2487 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2488 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2489 or (C4::Context->preference("marcflavour") eq "MARC21" and
2490 500 <= $tag && $tag < 600 )
2492 # oversize field (textarea)
2493 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2495 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2497 push( @loop_data, \%subfield_data );
2502 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2503 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2506 'itemtagfield' => $itemtagfield,
2507 'itemtagsubfield' => $itemtagsubfield,
2508 'itemnumber' => $itemnumber,
2509 'iteminformation' => \@loop_data
2513 sub ToggleNewStatus {
2514 my ( $params ) = @_;
2515 my @rules = @{ $params->{rules} };
2516 my $report_only = $params->{report_only};
2518 my $dbh = C4::Context->dbh;
2520 my @item_columns = map { "items.$_" } Koha::Items->columns;
2521 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2523 for my $rule ( @rules ) {
2524 my $age = $rule->{age};
2525 my $conditions = $rule->{conditions};
2526 my $substitutions = $rule->{substitutions};
2527 foreach ( @$substitutions ) {
2528 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2535 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2538 for my $condition ( @$conditions ) {
2540 grep { $_ eq $condition->{field} } @item_columns
2541 or grep { $_ eq $condition->{field} } @biblioitem_columns
2543 if ( $condition->{value} =~ /\|/ ) {
2544 my @values = split /\|/, $condition->{value};
2545 $query .= qq| AND $condition->{field} IN (|
2546 . join( ',', ('?') x scalar @values )
2548 push @params, @values;
2550 $query .= qq| AND $condition->{field} = ?|;
2551 push @params, $condition->{value};
2555 if ( defined $age ) {
2556 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2559 my $sth = $dbh->prepare($query);
2560 $sth->execute( @params );
2561 while ( my $values = $sth->fetchrow_hashref ) {
2562 my $biblionumber = $values->{biblionumber};
2563 my $itemnumber = $values->{itemnumber};
2564 for my $substitution ( @$substitutions ) {
2565 next unless $substitution->{field};
2566 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2567 C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
2568 unless $report_only;
2569 push @{ $report->{$itemnumber} }, $substitution;
2577 =head2 _after_item_action_hooks
2579 Helper method that takes care of calling all plugin hooks
2583 sub _after_item_action_hooks {
2586 my $item_id = $args->{item_id};
2587 my $action = $args->{action};
2589 if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
2591 my @plugins = Koha::Plugins->new->GetPlugins({
2592 method => 'after_item_action',
2597 my $item = Koha::Items->find( $item_id );
2599 foreach my $plugin ( @plugins ) {
2601 $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });