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>.
22 #use warnings; FIXME - Bug 2505
32 use List::MoreUtils qw/any/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36 # debugging; so please don't remove this
38 use Koha::AuthorisedValues;
39 use Koha::DateUtils qw/dt_from_string/;
42 use Koha::Biblioitems;
45 use Koha::SearchEngine;
46 use Koha::SearchEngine::Search;
49 use vars qw(@ISA @EXPORT);
54 @ISA = qw( Exporter );
75 GetItemnumbersForBiblio
76 get_hostitemnumbers_of
77 GetBarcodeFromItemnumber
92 PrepareItemrecordDisplay
99 C4::Items - item management functions
103 This module contains an API for manipulating item
104 records in Koha, and is used by cataloguing, circulation,
105 acquisitions, and serials management.
107 # FIXME This POD is not up-to-date
108 A Koha item record is stored in two places: the
109 items table and embedded in a MARC tag in the XML
110 version of the associated bib record in C<biblioitems.marcxml>.
111 This is done to allow the item information to be readily
112 indexed (e.g., by Zebra), but means that each item
113 modification transaction must keep the items table
114 and the MARC XML in sync at all times.
116 Consequently, all code that creates, modifies, or deletes
117 item records B<must> use an appropriate function from
118 C<C4::Items>. If no existing function is suitable, it is
119 better to add one to C<C4::Items> than to use add
120 one-off SQL statements to add or modify items.
122 The items table will be considered authoritative. In other
123 words, if there is ever a discrepancy between the items
124 table and the MARC XML, the items table should be considered
127 =head1 HISTORICAL NOTE
129 Most of the functions in C<C4::Items> were originally in
130 the C<C4::Biblio> module.
132 =head1 CORE EXPORTED FUNCTIONS
134 The following functions are meant for use by users
141 $item = GetItem($itemnumber,$barcode,$serial);
143 Return item information, for a given itemnumber or barcode.
144 The return value is a hashref mapping item column
145 names to values. If C<$serial> is true, include serial publication data.
150 my ($itemnumber,$barcode, $serial) = @_;
151 my $dbh = C4::Context->dbh;
155 $item = Koha::Items->find( $itemnumber );
157 $item = Koha::Items->find( { barcode => $barcode } );
160 return unless ( $item );
162 my $data = $item->unblessed();
163 $data->{itype} = $item->effective_itemtype(); # set the correct itype
166 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
167 $ssth->execute( $data->{'itemnumber'} );
168 ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
176 CartToShelf($itemnumber);
178 Set the current shelving location of the item record
179 to its stored permanent shelving location. This is
180 primarily used to indicate when an item whose current
181 location is a special processing ('PROC') or shelving cart
182 ('CART') location is back in the stacks.
187 my ( $itemnumber ) = @_;
189 unless ( $itemnumber ) {
190 croak "FAILED CartToShelf() - no itemnumber supplied";
193 my $item = GetItem($itemnumber);
194 if ( $item->{location} eq 'CART' ) {
195 $item->{location} = $item->{permanent_location};
196 ModItem($item, undef, $itemnumber);
202 ShelfToCart($itemnumber);
204 Set the current shelving location of the item
205 to shelving cart ('CART').
210 my ( $itemnumber ) = @_;
212 unless ( $itemnumber ) {
213 croak "FAILED ShelfToCart() - no itemnumber supplied";
216 my $item = GetItem($itemnumber);
217 $item->{'location'} = 'CART';
218 ModItem($item, undef, $itemnumber);
221 =head2 AddItemFromMarc
223 my ($biblionumber, $biblioitemnumber, $itemnumber)
224 = AddItemFromMarc($source_item_marc, $biblionumber);
226 Given a MARC::Record object containing an embedded item
227 record and a biblionumber, create a new item record.
231 sub AddItemFromMarc {
232 my ( $source_item_marc, $biblionumber ) = @_;
233 my $dbh = C4::Context->dbh;
235 # parse item hash from MARC
236 my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
237 my ($itemtag,$itemsubfield)=C4::Biblio::GetMarcFromKohaField("items.itemnumber",$frameworkcode);
239 my $localitemmarc=MARC::Record->new;
240 $localitemmarc->append_fields($source_item_marc->field($itemtag));
241 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
242 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
243 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
248 my ($biblionumber, $biblioitemnumber, $itemnumber)
249 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
251 Given a hash containing item column names as keys,
252 create a new Koha item record.
254 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
255 do not need to be supplied for general use; they exist
256 simply to allow them to be picked up from AddItemFromMarc.
258 The final optional parameter, C<$unlinked_item_subfields>, contains
259 an arrayref containing subfields present in the original MARC
260 representation of the item (e.g., from the item editor) that are
261 not mapped to C<items> columns directly but should instead
262 be stored in C<items.more_subfields_xml> and included in
263 the biblio items tag for display and indexing.
269 my $biblionumber = shift;
271 my $dbh = @_ ? shift : C4::Context->dbh;
272 my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
273 my $unlinked_item_subfields;
275 $unlinked_item_subfields = shift;
278 # needs old biblionumber and biblioitemnumber
279 $item->{'biblionumber'} = $biblionumber;
280 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
281 $sth->execute( $item->{'biblionumber'} );
282 ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
284 _set_defaults_for_add($item);
285 _set_derived_columns_for_add($item);
286 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
288 # FIXME - checks here
289 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
290 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
291 $itype_sth->execute( $item->{'biblionumber'} );
292 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
295 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
298 $item->{'itemnumber'} = $itemnumber;
300 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
302 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
303 if C4::Context->preference("CataloguingLog");
305 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
308 =head2 AddItemBatchFromMarc
310 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
311 $biblionumber, $biblioitemnumber, $frameworkcode);
313 Efficiently create item records from a MARC biblio record with
314 embedded item fields. This routine is suitable for batch jobs.
316 This API assumes that the bib record has already been
317 saved to the C<biblio> and C<biblioitems> tables. It does
318 not expect that C<biblio_metadata.metadata> is populated, but it
319 will do so via a call to ModBibiloMarc.
321 The goal of this API is to have a similar effect to using AddBiblio
322 and AddItems in succession, but without inefficient repeated
323 parsing of the MARC XML bib record.
325 This function returns an arrayref of new itemsnumbers and an arrayref of item
326 errors encountered during the processing. Each entry in the errors
327 list is a hashref containing the following keys:
333 Sequence number of original item tag in the MARC record.
337 Item barcode, provide to assist in the construction of
338 useful error messages.
342 Code representing the error condition. Can be 'duplicate_barcode',
343 'invalid_homebranch', or 'invalid_holdingbranch'.
345 =item error_information
347 Additional information appropriate to the error condition.
353 sub AddItemBatchFromMarc {
354 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
356 my @itemnumbers = ();
358 my $dbh = C4::Context->dbh;
360 # We modify the record, so lets work on a clone so we don't change the
362 $record = $record->clone();
363 # loop through the item tags and start creating items
364 my @bad_item_fields = ();
365 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",'');
366 my $item_sequence_num = 0;
367 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
368 $item_sequence_num++;
369 # we take the item field and stick it into a new
370 # MARC record -- this is required so far because (FIXME)
371 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
372 # and there is no TransformMarcFieldToKoha
373 my $temp_item_marc = MARC::Record->new();
374 $temp_item_marc->append_fields($item_field);
376 # add biblionumber and biblioitemnumber
377 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
378 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
379 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
380 $item->{'biblionumber'} = $biblionumber;
381 $item->{'biblioitemnumber'} = $biblioitemnumber;
383 # check for duplicate barcode
384 my %item_errors = CheckItemPreSave($item);
386 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
387 push @bad_item_fields, $item_field;
391 _set_defaults_for_add($item);
392 _set_derived_columns_for_add($item);
393 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
394 warn $error if $error;
395 push @itemnumbers, $itemnumber; # FIXME not checking error
396 $item->{'itemnumber'} = $itemnumber;
398 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
400 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
401 $item_field->replace_with($new_item_marc->field($itemtag));
404 # remove any MARC item fields for rejected items
405 foreach my $item_field (@bad_item_fields) {
406 $record->delete_field($item_field);
409 # update the MARC biblio
410 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
412 return (\@itemnumbers, \@errors);
415 =head2 ModItemFromMarc
417 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
419 This function updates an item record based on a supplied
420 C<MARC::Record> object containing an embedded item field.
421 This API is meant for the use of C<additem.pl>; for
422 other purposes, C<ModItem> should be used.
424 This function uses the hash %default_values_for_mod_from_marc,
425 which contains default values for item fields to
426 apply when modifying an item. This is needed because
427 if an item field's value is cleared, TransformMarcToKoha
428 does not include the column in the
429 hash that's passed to ModItem, which without
430 use of this hash makes it impossible to clear
431 an item field's value. See bug 2466.
433 Note that only columns that can be directly
434 changed from the cataloging and serials
435 item editors are included in this hash.
441 sub _build_default_values_for_mod_marc {
442 # Has no framework parameter anymore, since Default is authoritative
443 # for Koha to MARC mappings.
445 my $cache = Koha::Caches->get_instance();
446 my $cache_key = "default_value_for_mod_marc-";
447 my $cached = $cache->get_from_cache($cache_key);
448 return $cached if $cached;
450 my $default_values = {
452 booksellerid => undef,
454 'items.cn_source' => undef,
455 coded_location_qualifier => undef,
459 holdingbranch => undef,
461 itemcallnumber => undef,
464 itemnotes_nonpublic => undef,
467 permanent_location => undef,
471 # paidfor => undef, # commented, see bug 12817
473 replacementprice => undef,
474 replacementpricedate => undef,
477 stocknumber => undef,
481 my %default_values_for_mod_from_marc;
482 while ( my ( $field, $default_value ) = each %$default_values ) {
483 my $kohafield = $field;
484 $kohafield =~ s|^([^\.]+)$|items.$1|;
485 $default_values_for_mod_from_marc{$field} = $default_value
486 if C4::Biblio::GetMarcFromKohaField( $kohafield );
489 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
490 return \%default_values_for_mod_from_marc;
493 sub ModItemFromMarc {
494 my $item_marc = shift;
495 my $biblionumber = shift;
496 my $itemnumber = shift;
498 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
499 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
501 my $localitemmarc = MARC::Record->new;
502 $localitemmarc->append_fields( $item_marc->field($itemtag) );
503 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
504 my $default_values = _build_default_values_for_mod_marc();
505 foreach my $item_field ( keys %$default_values ) {
506 $item->{$item_field} = $default_values->{$item_field}
507 unless exists $item->{$item_field};
509 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
511 ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
518 { column => $newvalue },
522 [ unlinked_item_subfields => $unlinked_item_subfields, ]
527 Change one or more columns in an item record and update
528 the MARC representation of the item.
530 The first argument is a hashref mapping from item column
531 names to the new values. The second and third arguments
532 are the biblionumber and itemnumber, respectively.
533 The fourth, optional parameter (additional_params) may contain the keys
534 unlinked_item_subfields and log_action.
536 C<$unlinked_item_subfields> contains an arrayref containing
537 subfields present in the original MARC
538 representation of the item (e.g., from the item editor) that are
539 not mapped to C<items> columns directly but should instead
540 be stored in C<items.more_subfields_xml> and included in
541 the biblio items tag for display and indexing.
543 If one of the changed columns is used to calculate
544 the derived value of a column such as C<items.cn_sort>,
545 this routine will perform the necessary calculation
548 If log_action is set to false, the action will not be logged.
549 If log_action is true or undefined, the action will be logged.
554 my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
555 my $log_action = $additional_params->{log_action} // 1;
556 my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
558 # if $biblionumber is undefined, get it from the current item
559 unless (defined $biblionumber) {
560 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
563 if ($unlinked_item_subfields) {
564 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
567 $item->{'itemnumber'} = $itemnumber or return;
569 my @fields = qw( itemlost withdrawn damaged );
571 # Only call GetItem if we need to set an "on" date field
572 if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
573 my $pre_mod_item = GetItem( $item->{'itemnumber'} );
574 for my $field (@fields) {
575 if ( defined( $item->{$field} )
576 and not $pre_mod_item->{$field}
577 and $item->{$field} )
579 $item->{ $field . '_on' } =
580 DateTime::Format::MySQL->format_datetime( dt_from_string() );
585 # If the field is defined but empty, we are removing and,
586 # and thus need to clear out the 'on' field as well
587 for my $field (@fields) {
588 if ( defined( $item->{$field} ) && !$item->{$field} ) {
589 $item->{ $field . '_on' } = undef;
594 _set_derived_columns_for_mod($item);
595 _do_column_fixes_for_mod($item);
598 # attempt to change itemnumber
599 # attempt to change biblionumber (if we want
600 # an API to relink an item to a different bib,
601 # it should be a separate function)
604 _koha_modify_item($item);
606 # request that bib be reindexed so that searching on current
607 # item status is possible
608 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
610 logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
611 if $log_action && C4::Context->preference("CataloguingLog");
614 =head2 ModItemTransfer
616 ModItemTransfer($itenumber, $frombranch, $tobranch);
618 Marks an item as being transferred from one branch
623 sub ModItemTransfer {
624 my ( $itemnumber, $frombranch, $tobranch ) = @_;
626 my $dbh = C4::Context->dbh;
628 # Remove the 'shelving cart' location status if it is being used.
629 CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
631 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
633 #new entry in branchtransfers....
634 my $sth = $dbh->prepare(
635 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
636 VALUES (?, ?, NOW(), ?)");
637 $sth->execute($itemnumber, $frombranch, $tobranch);
639 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
640 ModDateLastSeen($itemnumber);
644 =head2 ModDateLastSeen
646 ModDateLastSeen( $itemnumber, $leave_item_lost );
648 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
649 C<$itemnumber> is the item number
650 C<$leave_item_lost> determines if a lost item will be found or remain lost
654 sub ModDateLastSeen {
655 my ( $itemnumber, $leave_item_lost ) = @_;
657 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
660 $params->{datelastseen} = $today;
661 $params->{itemlost} = 0 unless $leave_item_lost;
663 ModItem( $params, undef, $itemnumber, { log_action => 0 } );
668 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
670 Exported function (core API) for deleting an item record in Koha.
677 my $itemnumber = $params->{itemnumber};
678 my $biblionumber = $params->{biblionumber};
680 unless ($biblionumber) {
681 my $item = Koha::Items->find( $itemnumber );
682 $biblionumber = $item ? $item->biblio->biblionumber : undef;
685 # If there is no biblionumber for the given itemnumber, there is nothing to delete
686 return 0 unless $biblionumber;
688 # FIXME check the item has no current issues
689 my $deleted = _koha_delete_item( $itemnumber );
691 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
693 #search item field code
694 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
698 =head2 CheckItemPreSave
700 my $item_ref = TransformMarcToKoha($marc, 'items');
702 my %errors = CheckItemPreSave($item_ref);
703 if (exists $errors{'duplicate_barcode'}) {
704 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
705 } elsif (exists $errors{'invalid_homebranch'}) {
706 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
707 } elsif (exists $errors{'invalid_holdingbranch'}) {
708 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
713 Given a hashref containing item fields, determine if it can be
714 inserted or updated in the database. Specifically, checks for
715 database integrity issues, and returns a hash containing any
716 of the following keys, if applicable.
720 =item duplicate_barcode
722 Barcode, if it duplicates one already found in the database.
724 =item invalid_homebranch
726 Home branch, if not defined in branches table.
728 =item invalid_holdingbranch
730 Holding branch, if not defined in branches table.
734 This function does NOT implement any policy-related checks,
735 e.g., whether current operator is allowed to save an
736 item that has a given branch code.
740 sub CheckItemPreSave {
741 my $item_ref = shift;
745 # check for duplicate barcode
746 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
747 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
748 if ($existing_item) {
749 if (!exists $item_ref->{'itemnumber'} # new item
750 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
751 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
756 # check for valid home branch
757 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
758 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
759 unless (defined $home_library) {
760 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
764 # check for valid holding branch
765 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
766 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
767 unless (defined $holding_library) {
768 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
776 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
778 The following functions provide various ways of
779 getting an item record, a set of item records, or
780 lists of authorized values for certain item fields.
784 =head2 GetItemsForInventory
786 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
787 minlocation => $minlocation,
788 maxlocation => $maxlocation,
789 location => $location,
790 itemtype => $itemtype,
791 ignoreissued => $ignoreissued,
792 datelastseen => $datelastseen,
793 branchcode => $branchcode,
797 statushash => $statushash,
800 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
802 The sub returns a reference to a list of hashes, each containing
803 itemnumber, author, title, barcode, item callnumber, and date last
804 seen. It is ordered by callnumber then title.
806 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
807 the datelastseen can be used to specify that you want to see items not seen since a past date only.
808 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
809 $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.
811 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
815 sub GetItemsForInventory {
816 my ( $parameters ) = @_;
817 my $minlocation = $parameters->{'minlocation'} // '';
818 my $maxlocation = $parameters->{'maxlocation'} // '';
819 my $location = $parameters->{'location'} // '';
820 my $itemtype = $parameters->{'itemtype'} // '';
821 my $ignoreissued = $parameters->{'ignoreissued'} // '';
822 my $datelastseen = $parameters->{'datelastseen'} // '';
823 my $branchcode = $parameters->{'branchcode'} // '';
824 my $branch = $parameters->{'branch'} // '';
825 my $offset = $parameters->{'offset'} // '';
826 my $size = $parameters->{'size'} // '';
827 my $statushash = $parameters->{'statushash'} // '';
829 my $dbh = C4::Context->dbh;
830 my ( @bind_params, @where_strings );
832 my $select_columns = q{
833 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
835 my $select_count = q{SELECT COUNT(*)};
838 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
839 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
842 for my $authvfield (keys %$statushash){
843 if ( scalar @{$statushash->{$authvfield}} > 0 ){
844 my $joinedvals = join ',', @{$statushash->{$authvfield}};
845 push @where_strings, "$authvfield in (" . $joinedvals . ")";
851 push @where_strings, 'itemcallnumber >= ?';
852 push @bind_params, $minlocation;
856 push @where_strings, 'itemcallnumber <= ?';
857 push @bind_params, $maxlocation;
861 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
862 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
863 push @bind_params, $datelastseen;
867 push @where_strings, 'items.location = ?';
868 push @bind_params, $location;
872 if($branch eq "homebranch"){
873 push @where_strings, 'items.homebranch = ?';
875 push @where_strings, 'items.holdingbranch = ?';
877 push @bind_params, $branchcode;
881 push @where_strings, 'biblioitems.itemtype = ?';
882 push @bind_params, $itemtype;
885 if ( $ignoreissued) {
886 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
887 push @where_strings, 'issues.date_due IS NULL';
890 if ( @where_strings ) {
892 $query .= join ' AND ', @where_strings;
894 my $count_query = $select_count . $query;
895 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
896 $query .= " LIMIT $offset, $size" if ($offset and $size);
897 $query = $select_columns . $query;
898 my $sth = $dbh->prepare($query);
899 $sth->execute( @bind_params );
902 my $tmpresults = $sth->fetchall_arrayref({});
903 $sth = $dbh->prepare( $count_query );
904 $sth->execute( @bind_params );
905 my ($iTotalRecords) = $sth->fetchrow_array();
907 my @avs = Koha::AuthorisedValues->search(
908 { 'marc_subfield_structures.kohafield' => { '>' => '' },
909 'me.authorised_value' => { '>' => '' },
911 { join => { category => 'marc_subfield_structures' },
912 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
913 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
914 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
918 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
920 foreach my $row (@$tmpresults) {
923 foreach (keys %$row) {
924 if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
925 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
931 return (\@results, $iTotalRecords);
936 @results = GetItemsInfo($biblionumber);
938 Returns information about items with the given biblionumber.
940 C<GetItemsInfo> returns a list of references-to-hash. Each element
941 contains a number of keys. Most of them are attributes from the
942 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
943 Koha database. Other keys include:
947 =item C<$data-E<gt>{branchname}>
949 The name (not the code) of the branch to which the book belongs.
951 =item C<$data-E<gt>{datelastseen}>
953 This is simply C<items.datelastseen>, except that while the date is
954 stored in YYYY-MM-DD format in the database, here it is converted to
955 DD/MM/YYYY format. A NULL date is returned as C<//>.
957 =item C<$data-E<gt>{datedue}>
959 =item C<$data-E<gt>{class}>
961 This is the concatenation of C<biblioitems.classification>, the book's
962 Dewey code, and C<biblioitems.subclass>.
964 =item C<$data-E<gt>{ocount}>
966 I think this is the number of copies of the book available.
968 =item C<$data-E<gt>{order}>
970 If this is set, it is set to C<One Order>.
977 my ( $biblionumber ) = @_;
978 my $dbh = C4::Context->dbh;
979 require C4::Languages;
980 my $language = C4::Languages::getlanguage();
986 biblioitems.itemtype,
989 biblioitems.publicationyear,
990 biblioitems.publishercode,
991 biblioitems.volumedate,
992 biblioitems.volumedesc,
995 items.notforloan as itemnotforloan,
996 issues.borrowernumber,
997 issues.date_due as datedue,
998 issues.onsite_checkout,
999 borrowers.cardnumber,
1001 borrowers.firstname,
1002 borrowers.branchcode as bcode,
1004 serial.publisheddate,
1005 itemtypes.description,
1006 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1007 itemtypes.notforloan as notforloan_per_itemtype,
1011 holding.opac_info as holding_branch_opac_info,
1012 home.opac_info as home_branch_opac_info
1016 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1017 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1018 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1019 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1020 LEFT JOIN issues USING (itemnumber)
1021 LEFT JOIN borrowers USING (borrowernumber)
1022 LEFT JOIN serialitems USING (itemnumber)
1023 LEFT JOIN serial USING (serialid)
1024 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1025 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1027 LEFT JOIN localization ON itemtypes.itemtype = localization.code
1028 AND localization.entity = 'itemtypes'
1029 AND localization.lang = ?
1032 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1033 my $sth = $dbh->prepare($query);
1034 $sth->execute($language, $biblionumber);
1039 my $userenv = C4::Context->userenv;
1040 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1041 while ( my $data = $sth->fetchrow_hashref ) {
1042 if ( $data->{borrowernumber} && $want_not_same_branch) {
1043 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1046 $serial ||= $data->{'serial'};
1049 # get notforloan complete status if applicable
1050 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1051 $data->{notforloanvalue} = $descriptions->{lib} // '';
1052 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
1054 # get restricted status and description if applicable
1055 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1056 $data->{restricted} = $descriptions->{lib} // '';
1057 $data->{restrictedopac} = $descriptions->{opac_description} // '';
1059 # my stack procedures
1060 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1061 $data->{stack} = $descriptions->{lib} // '';
1063 # Find the last 3 people who borrowed this item.
1064 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1065 WHERE itemnumber = ?
1066 AND old_issues.borrowernumber = borrowers.borrowernumber
1067 ORDER BY returndate DESC
1069 $sth2->execute($data->{'itemnumber'});
1071 while (my $data2 = $sth2->fetchrow_hashref()) {
1072 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1073 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1074 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1078 $results[$i] = $data;
1083 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1087 =head2 GetItemsLocationInfo
1089 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1091 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1093 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1097 =item C<$data-E<gt>{homebranch}>
1099 Branch Name of the item's homebranch
1101 =item C<$data-E<gt>{holdingbranch}>
1103 Branch Name of the item's holdingbranch
1105 =item C<$data-E<gt>{location}>
1107 Item's shelving location code
1109 =item C<$data-E<gt>{location_intranet}>
1111 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1113 =item C<$data-E<gt>{location_opac}>
1115 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1118 =item C<$data-E<gt>{itemcallnumber}>
1120 Item's itemcallnumber
1122 =item C<$data-E<gt>{cn_sort}>
1124 Item's call number normalized for sorting
1130 sub GetItemsLocationInfo {
1131 my $biblionumber = shift;
1134 my $dbh = C4::Context->dbh;
1135 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1136 location, itemcallnumber, cn_sort
1137 FROM items, branches as a, branches as b
1138 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1139 AND biblionumber = ?
1140 ORDER BY cn_sort ASC";
1141 my $sth = $dbh->prepare($query);
1142 $sth->execute($biblionumber);
1144 while ( my $data = $sth->fetchrow_hashref ) {
1145 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1146 $av = $av->count ? $av->next : undef;
1147 $data->{location_intranet} = $av ? $av->lib : '';
1148 $data->{location_opac} = $av ? $av->opac_description : '';
1149 push @results, $data;
1154 =head2 GetHostItemsInfo
1156 $hostiteminfo = GetHostItemsInfo($hostfield);
1157 Returns the iteminfo for items linked to records via a host field
1161 sub GetHostItemsInfo {
1163 my @returnitemsInfo;
1165 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1166 return @returnitemsInfo;
1170 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1171 C4::Context->preference('marcflavour') eq 'NORMARC') {
1172 @fields = $record->field('773');
1173 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1174 @fields = $record->field('461');
1177 foreach my $hostfield ( @fields ) {
1178 my $hostbiblionumber = $hostfield->subfield("0");
1179 my $linkeditemnumber = $hostfield->subfield("9");
1180 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1181 foreach my $hostitemInfo (@hostitemInfos) {
1182 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1183 push @returnitemsInfo, $hostitemInfo;
1188 return @returnitemsInfo;
1191 =head2 GetLastAcquisitions
1193 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1194 'itemtypes' => ('BK','BD')}, 10);
1198 sub GetLastAcquisitions {
1199 my ($data,$max) = @_;
1201 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1203 my $number_of_branches = @{$data->{branches}};
1204 my $number_of_itemtypes = @{$data->{itemtypes}};
1207 my @where = ('WHERE 1 ');
1208 $number_of_branches and push @where
1209 , 'AND holdingbranch IN ('
1210 , join(',', ('?') x $number_of_branches )
1214 $number_of_itemtypes and push @where
1215 , "AND $itemtype IN ("
1216 , join(',', ('?') x $number_of_itemtypes )
1220 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1221 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1222 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1224 GROUP BY biblio.biblionumber
1225 ORDER BY dateaccessioned DESC LIMIT $max";
1227 my $dbh = C4::Context->dbh;
1228 my $sth = $dbh->prepare($query);
1230 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1233 while( my $row = $sth->fetchrow_hashref){
1234 push @results, {date => $row->{dateaccessioned}
1235 , biblionumber => $row->{biblionumber}
1236 , title => $row->{title}};
1242 =head2 GetItemnumbersForBiblio
1244 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1246 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1250 sub GetItemnumbersForBiblio {
1251 my $biblionumber = shift;
1253 my $dbh = C4::Context->dbh;
1254 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1255 $sth->execute($biblionumber);
1256 while (my $result = $sth->fetchrow_hashref) {
1257 push @items, $result->{'itemnumber'};
1262 =head2 get_hostitemnumbers_of
1264 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1266 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1268 Return a reference on a hash where key is a biblionumber and values are
1269 references on array of itemnumbers.
1274 sub get_hostitemnumbers_of {
1275 my ($biblionumber) = @_;
1276 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1278 return unless $marcrecord;
1280 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1282 my $marcflavor = C4::Context->preference('marcflavour');
1283 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1288 elsif ( $marcflavor eq 'UNIMARC' ) {
1294 foreach my $hostfield ( $marcrecord->field($tag) ) {
1295 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1296 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1297 my $linkeditemnumber = $hostfield->subfield($item_s);
1298 if ( ! $linkeditemnumber ) {
1299 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1302 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1303 push @returnhostitemnumbers, $linkeditemnumber
1307 return @returnhostitemnumbers;
1311 =head2 GetBarcodeFromItemnumber
1313 $result = GetBarcodeFromItemnumber($itemnumber);
1317 sub GetBarcodeFromItemnumber {
1318 my ($itemnumber) = @_;
1319 my $dbh = C4::Context->dbh;
1322 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1323 $rq->execute($itemnumber);
1324 my ($result) = $rq->fetchrow;
1328 =head2 GetHiddenItemnumbers
1330 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1332 Given a list of items it checks which should be hidden from the OPAC given
1333 the current configuration. Returns a list of itemnumbers corresponding to
1334 those that should be hidden.
1338 sub GetHiddenItemnumbers {
1342 my $yaml = C4::Context->preference('OpacHiddenItems');
1343 return () if (! $yaml =~ /\S/ );
1344 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1347 $hidingrules = YAML::Load($yaml);
1350 warn "Unable to parse OpacHiddenItems syspref : $@";
1353 my $dbh = C4::Context->dbh;
1356 foreach my $item (@items) {
1358 # We check each rule
1359 foreach my $field (keys %$hidingrules) {
1361 if (exists $item->{$field}) {
1362 $val = $item->{$field};
1365 my $query = "SELECT $field from items where itemnumber = ?";
1366 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1368 $val = '' unless defined $val;
1370 # If the results matches the values in the yaml file
1371 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1373 # We add the itemnumber to the list
1374 push @resultitems, $item->{'itemnumber'};
1376 # If at least one rule matched for an item, no need to test the others
1381 return @resultitems;
1384 =head1 LIMITED USE FUNCTIONS
1386 The following functions, while part of the public API,
1387 are not exported. This is generally because they are
1388 meant to be used by only one script for a specific
1389 purpose, and should not be used in any other context
1390 without careful thought.
1396 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1398 Returns MARC::Record of the item passed in parameter.
1399 This function is meant for use only in C<cataloguing/additem.pl>,
1400 where it is needed to support that script's MARC-like
1406 my ( $biblionumber, $itemnumber ) = @_;
1408 # GetMarcItem has been revised so that it does the following:
1409 # 1. Gets the item information from the items table.
1410 # 2. Converts it to a MARC field for storage in the bib record.
1412 # The previous behavior was:
1413 # 1. Get the bib record.
1414 # 2. Return the MARC tag corresponding to the item record.
1416 # The difference is that one treats the items row as authoritative,
1417 # while the other treats the MARC representation as authoritative
1418 # under certain circumstances.
1420 my $itemrecord = GetItem($itemnumber);
1422 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1423 # Also, don't emit a subfield if the underlying field is blank.
1426 return Item2Marc($itemrecord,$biblionumber);
1430 my ($itemrecord,$biblionumber)=@_;
1433 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1434 } keys %{ $itemrecord }
1436 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1437 my $itemmarc = C4::Biblio::TransformKohaToMarc(
1438 $mungeditem, { no_split => 1},
1440 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1441 "items.itemnumber", $framework,
1444 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1445 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1446 foreach my $field ($itemmarc->field($itemtag)){
1447 $field->add_subfields(@$unlinked_item_subfields);
1453 =head1 PRIVATE FUNCTIONS AND VARIABLES
1455 The following functions are not meant to be called
1456 directly, but are documented in order to explain
1457 the inner workings of C<C4::Items>.
1461 =head2 %derived_columns
1463 This hash keeps track of item columns that
1464 are strictly derived from other columns in
1465 the item record and are not meant to be set
1468 Each key in the hash should be the name of a
1469 column (as named by TransformMarcToKoha). Each
1470 value should be hashref whose keys are the
1471 columns on which the derived column depends. The
1472 hashref should also contain a 'BUILDER' key
1473 that is a reference to a sub that calculates
1478 my %derived_columns = (
1479 'items.cn_sort' => {
1480 'itemcallnumber' => 1,
1481 'items.cn_source' => 1,
1482 'BUILDER' => \&_calc_items_cn_sort,
1486 =head2 _set_derived_columns_for_add
1488 _set_derived_column_for_add($item);
1490 Given an item hash representing a new item to be added,
1491 calculate any derived columns. Currently the only
1492 such column is C<items.cn_sort>.
1496 sub _set_derived_columns_for_add {
1499 foreach my $column (keys %derived_columns) {
1500 my $builder = $derived_columns{$column}->{'BUILDER'};
1501 my $source_values = {};
1502 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1503 next if $source_column eq 'BUILDER';
1504 $source_values->{$source_column} = $item->{$source_column};
1506 $builder->($item, $source_values);
1510 =head2 _set_derived_columns_for_mod
1512 _set_derived_column_for_mod($item);
1514 Given an item hash representing a new item to be modified.
1515 calculate any derived columns. Currently the only
1516 such column is C<items.cn_sort>.
1518 This routine differs from C<_set_derived_columns_for_add>
1519 in that it needs to handle partial item records. In other
1520 words, the caller of C<ModItem> may have supplied only one
1521 or two columns to be changed, so this function needs to
1522 determine whether any of the columns to be changed affect
1523 any of the derived columns. Also, if a derived column
1524 depends on more than one column, but the caller is not
1525 changing all of then, this routine retrieves the unchanged
1526 values from the database in order to ensure a correct
1531 sub _set_derived_columns_for_mod {
1534 foreach my $column (keys %derived_columns) {
1535 my $builder = $derived_columns{$column}->{'BUILDER'};
1536 my $source_values = {};
1537 my %missing_sources = ();
1538 my $must_recalc = 0;
1539 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1540 next if $source_column eq 'BUILDER';
1541 if (exists $item->{$source_column}) {
1543 $source_values->{$source_column} = $item->{$source_column};
1545 $missing_sources{$source_column} = 1;
1549 foreach my $source_column (keys %missing_sources) {
1550 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1552 $builder->($item, $source_values);
1557 =head2 _do_column_fixes_for_mod
1559 _do_column_fixes_for_mod($item);
1561 Given an item hashref containing one or more
1562 columns to modify, fix up certain values.
1563 Specifically, set to 0 any passed value
1564 of C<notforloan>, C<damaged>, C<itemlost>, or
1565 C<withdrawn> that is either undefined or
1566 contains the empty string.
1570 sub _do_column_fixes_for_mod {
1573 if (exists $item->{'notforloan'} and
1574 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1575 $item->{'notforloan'} = 0;
1577 if (exists $item->{'damaged'} and
1578 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1579 $item->{'damaged'} = 0;
1581 if (exists $item->{'itemlost'} and
1582 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1583 $item->{'itemlost'} = 0;
1585 if (exists $item->{'withdrawn'} and
1586 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1587 $item->{'withdrawn'} = 0;
1589 if (exists $item->{location}
1590 and $item->{location} ne 'CART'
1591 and $item->{location} ne 'PROC'
1592 and not $item->{permanent_location}
1594 $item->{'permanent_location'} = $item->{'location'};
1596 if (exists $item->{'timestamp'}) {
1597 delete $item->{'timestamp'};
1601 =head2 _get_single_item_column
1603 _get_single_item_column($column, $itemnumber);
1605 Retrieves the value of a single column from an C<items>
1606 row specified by C<$itemnumber>.
1610 sub _get_single_item_column {
1612 my $itemnumber = shift;
1614 my $dbh = C4::Context->dbh;
1615 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1616 $sth->execute($itemnumber);
1617 my ($value) = $sth->fetchrow();
1621 =head2 _calc_items_cn_sort
1623 _calc_items_cn_sort($item, $source_values);
1625 Helper routine to calculate C<items.cn_sort>.
1629 sub _calc_items_cn_sort {
1631 my $source_values = shift;
1633 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1636 =head2 _set_defaults_for_add
1638 _set_defaults_for_add($item_hash);
1640 Given an item hash representing an item to be added, set
1641 correct default values for columns whose default value
1642 is not handled by the DBMS. This includes the following
1649 C<items.dateaccessioned>
1671 sub _set_defaults_for_add {
1673 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1674 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1677 =head2 _koha_new_item
1679 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1681 Perform the actual insert into the C<items> table.
1685 sub _koha_new_item {
1686 my ( $item, $barcode ) = @_;
1687 my $dbh=C4::Context->dbh;
1689 $item->{permanent_location} //= $item->{location};
1690 _mod_item_dates( $item );
1692 "INSERT INTO items SET
1694 biblioitemnumber = ?,
1696 dateaccessioned = ?,
1700 replacementprice = ?,
1701 replacementpricedate = ?,
1702 datelastborrowed = ?,
1710 coded_location_qualifier = ?,
1713 itemnotes_nonpublic = ?,
1717 permanent_location = ?,
1729 more_subfields_xml = ?,
1734 my $sth = $dbh->prepare($query);
1735 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1737 $item->{'biblionumber'},
1738 $item->{'biblioitemnumber'},
1740 $item->{'dateaccessioned'},
1741 $item->{'booksellerid'},
1742 $item->{'homebranch'},
1744 $item->{'replacementprice'},
1745 $item->{'replacementpricedate'} || $today,
1746 $item->{datelastborrowed},
1747 $item->{datelastseen} || $today,
1749 $item->{'notforloan'},
1751 $item->{'itemlost'},
1752 $item->{'withdrawn'},
1753 $item->{'itemcallnumber'},
1754 $item->{'coded_location_qualifier'},
1755 $item->{'restricted'},
1756 $item->{'itemnotes'},
1757 $item->{'itemnotes_nonpublic'},
1758 $item->{'holdingbranch'},
1760 $item->{'location'},
1761 $item->{'permanent_location'},
1764 $item->{'renewals'},
1765 $item->{'reserves'},
1766 $item->{'items.cn_source'},
1767 $item->{'items.cn_sort'},
1770 $item->{'materials'},
1772 $item->{'enumchron'},
1773 $item->{'more_subfields_xml'},
1774 $item->{'copynumber'},
1775 $item->{'stocknumber'},
1776 $item->{'new_status'},
1780 if ( defined $sth->errstr ) {
1781 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1784 $itemnumber = $dbh->{'mysql_insertid'};
1787 return ( $itemnumber, $error );
1790 =head2 MoveItemFromBiblio
1792 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1794 Moves an item from a biblio to another
1796 Returns undef if the move failed or the biblionumber of the destination record otherwise
1800 sub MoveItemFromBiblio {
1801 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1802 my $dbh = C4::Context->dbh;
1803 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1804 SELECT biblioitemnumber
1806 WHERE biblionumber = ?
1807 |, undef, $tobiblio );
1808 my $return = $dbh->do(q|
1810 SET biblioitemnumber = ?,
1812 WHERE itemnumber = ?
1813 AND biblionumber = ?
1814 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1816 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1817 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1818 # Checking if the item we want to move is in an order
1819 require C4::Acquisition;
1820 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1822 # Replacing the biblionumber within the order if necessary
1823 $order->{'biblionumber'} = $tobiblio;
1824 C4::Acquisition::ModOrder($order);
1827 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1828 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1831 SET biblionumber = ?
1832 WHERE itemnumber = ?
1833 |, undef, $tobiblio, $itemnumber );
1840 =head2 ItemSafeToDelete
1842 ItemSafeToDelete( $biblionumber, $itemnumber);
1844 Exported function (core API) for checking whether an item record is safe to delete.
1846 returns 1 if the item is safe to delete,
1848 "book_on_loan" if the item is checked out,
1850 "not_same_branch" if the item is blocked by independent branches,
1852 "book_reserved" if the there are holds aganst the item, or
1854 "linked_analytics" if the item has linked analytic records.
1858 sub ItemSafeToDelete {
1859 my ( $biblionumber, $itemnumber ) = @_;
1861 my $dbh = C4::Context->dbh;
1865 my $countanalytics = GetAnalyticsCount($itemnumber);
1867 # check that there is no issue on this item before deletion.
1868 my $sth = $dbh->prepare(
1870 SELECT COUNT(*) FROM issues
1871 WHERE itemnumber = ?
1874 $sth->execute($itemnumber);
1875 my ($onloan) = $sth->fetchrow;
1877 my $item = GetItem($itemnumber);
1880 $status = "book_on_loan";
1882 elsif ( defined C4::Context->userenv
1883 and !C4::Context->IsSuperLibrarian()
1884 and C4::Context->preference("IndependentBranches")
1885 and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
1887 $status = "not_same_branch";
1890 # check it doesn't have a waiting reserve
1891 $sth = $dbh->prepare(
1893 SELECT COUNT(*) FROM reserves
1894 WHERE (found = 'W' OR found = 'T')
1898 $sth->execute($itemnumber);
1899 my ($reserve) = $sth->fetchrow;
1901 $status = "book_reserved";
1903 elsif ( $countanalytics > 0 ) {
1904 $status = "linked_analytics";
1915 DelItemCheck( $biblionumber, $itemnumber);
1917 Exported function (core API) for deleting an item record in Koha if there no current issue.
1919 DelItemCheck wraps ItemSafeToDelete around DelItem.
1924 my ( $biblionumber, $itemnumber ) = @_;
1925 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1927 if ( $status == 1 ) {
1930 biblionumber => $biblionumber,
1931 itemnumber => $itemnumber
1938 =head2 _koha_modify_item
1940 my ($itemnumber,$error) =_koha_modify_item( $item );
1942 Perform the actual update of the C<items> row. Note that this
1943 routine accepts a hashref specifying the columns to update.
1947 sub _koha_modify_item {
1949 my $dbh=C4::Context->dbh;
1952 my $query = "UPDATE items SET ";
1954 _mod_item_dates( $item );
1955 for my $key ( keys %$item ) {
1956 next if ( $key eq 'itemnumber' );
1958 push @bind, $item->{$key};
1961 $query .= " WHERE itemnumber=?";
1962 push @bind, $item->{'itemnumber'};
1963 my $sth = $dbh->prepare($query);
1964 $sth->execute(@bind);
1966 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1969 return ($item->{'itemnumber'},$error);
1972 sub _mod_item_dates { # date formatting for date fields in item hash
1974 return if !$item || ref($item) ne 'HASH';
1977 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1979 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1980 # NOTE: We do not (yet) have items fields ending with datetime
1981 # Fields with _on$ have been handled already
1983 foreach my $key ( @keys ) {
1984 next if !defined $item->{$key}; # skip undefs
1985 my $dt = eval { dt_from_string( $item->{$key} ) };
1986 # eval: dt_from_string will die on us if we pass illegal dates
1989 if( defined $dt && ref($dt) eq 'DateTime' ) {
1990 if( $key =~ /datetime/ ) {
1991 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1993 $newstr = DateTime::Format::MySQL->format_date($dt);
1996 $item->{$key} = $newstr; # might be undef to clear garbage
2000 =head2 _koha_delete_item
2002 _koha_delete_item( $itemnum );
2004 Internal function to delete an item record from the koha tables
2008 sub _koha_delete_item {
2009 my ( $itemnum ) = @_;
2011 my $dbh = C4::Context->dbh;
2012 # save the deleted item to deleteditems table
2013 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2014 $sth->execute($itemnum);
2015 my $data = $sth->fetchrow_hashref();
2017 # There is no item to delete
2018 return 0 unless $data;
2020 my $query = "INSERT INTO deleteditems SET ";
2022 foreach my $key ( keys %$data ) {
2023 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2024 $query .= "$key = ?,";
2025 push( @bind, $data->{$key} );
2028 $sth = $dbh->prepare($query);
2029 $sth->execute(@bind);
2031 # delete from items table
2032 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2033 my $deleted = $sth->execute($itemnum);
2034 return ( $deleted == 1 ) ? 1 : 0;
2037 =head2 _marc_from_item_hash
2039 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2041 Given an item hash representing a complete item record,
2042 create a C<MARC::Record> object containing an embedded
2043 tag representing that item.
2045 The third, optional parameter C<$unlinked_item_subfields> is
2046 an arrayref of subfields (not mapped to C<items> fields per the
2047 framework) to be added to the MARC representation
2052 sub _marc_from_item_hash {
2054 my $frameworkcode = shift;
2055 my $unlinked_item_subfields;
2057 $unlinked_item_subfields = shift;
2060 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2061 # Also, don't emit a subfield if the underlying field is blank.
2062 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2063 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2064 : () } keys %{ $item } };
2066 my $item_marc = MARC::Record->new();
2067 foreach my $item_field ( keys %{$mungeditem} ) {
2068 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
2069 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2070 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2071 foreach my $value (@values){
2072 if ( my $field = $item_marc->field($tag) ) {
2073 $field->add_subfields( $subfield => $value );
2075 my $add_subfields = [];
2076 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2077 $add_subfields = $unlinked_item_subfields;
2079 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2087 =head2 _repack_item_errors
2089 Add an error message hash generated by C<CheckItemPreSave>
2090 to a list of errors.
2094 sub _repack_item_errors {
2095 my $item_sequence_num = shift;
2096 my $item_ref = shift;
2097 my $error_ref = shift;
2099 my @repacked_errors = ();
2101 foreach my $error_code (sort keys %{ $error_ref }) {
2102 my $repacked_error = {};
2103 $repacked_error->{'item_sequence'} = $item_sequence_num;
2104 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2105 $repacked_error->{'error_code'} = $error_code;
2106 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2107 push @repacked_errors, $repacked_error;
2110 return @repacked_errors;
2113 =head2 _get_unlinked_item_subfields
2115 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2119 sub _get_unlinked_item_subfields {
2120 my $original_item_marc = shift;
2121 my $frameworkcode = shift;
2123 my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2125 # assume that this record has only one field, and that that
2126 # field contains only the item information
2128 my @fields = $original_item_marc->fields();
2129 if ($#fields > -1) {
2130 my $field = $fields[0];
2131 my $tag = $field->tag();
2132 foreach my $subfield ($field->subfields()) {
2133 if (defined $subfield->[1] and
2134 $subfield->[1] ne '' and
2135 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2136 push @$subfields, $subfield->[0] => $subfield->[1];
2143 =head2 _get_unlinked_subfields_xml
2145 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2149 sub _get_unlinked_subfields_xml {
2150 my $unlinked_item_subfields = shift;
2153 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2154 my $marc = MARC::Record->new();
2155 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2156 # used in the framework
2157 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2158 $marc->encoding("UTF-8");
2159 $xml = $marc->as_xml("USMARC");
2165 =head2 _parse_unlinked_item_subfields_from_xml
2167 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2171 sub _parse_unlinked_item_subfields_from_xml {
2173 require C4::Charset;
2174 return unless defined $xml and $xml ne "";
2175 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2176 my $unlinked_subfields = [];
2177 my @fields = $marc->fields();
2178 if ($#fields > -1) {
2179 foreach my $subfield ($fields[0]->subfields()) {
2180 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2183 return $unlinked_subfields;
2186 =head2 GetAnalyticsCount
2188 $count= &GetAnalyticsCount($itemnumber)
2190 counts Usage of itemnumber in Analytical bibliorecords.
2194 sub GetAnalyticsCount {
2195 my ($itemnumber) = @_;
2197 ### ZOOM search here
2199 $query= "hi=".$itemnumber;
2200 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2201 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2205 =head2 SearchItemsByField
2207 my $items = SearchItemsByField($field, $value);
2209 SearchItemsByField will search for items on a specific given field.
2210 For instance you can search all items with a specific stocknumber like this:
2212 my $items = SearchItemsByField('stocknumber', $stocknumber);
2216 sub SearchItemsByField {
2217 my ($field, $value) = @_;
2224 my ($results) = SearchItems($filters);
2228 sub _SearchItems_build_where_fragment {
2231 my $dbh = C4::Context->dbh;
2234 if (exists($filter->{conjunction})) {
2235 my (@where_strs, @where_args);
2236 foreach my $f (@{ $filter->{filters} }) {
2237 my $fragment = _SearchItems_build_where_fragment($f);
2239 push @where_strs, $fragment->{str};
2240 push @where_args, @{ $fragment->{args} };
2245 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2248 args => \@where_args,
2252 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2253 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2254 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2255 my @operators = qw(= != > < >= <= like);
2256 my $field = $filter->{field};
2257 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2258 my $op = $filter->{operator};
2259 my $query = $filter->{query};
2261 if (!$op or (0 == grep /^$op$/, @operators)) {
2262 $op = '='; # default operator
2266 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2268 my $marcsubfield = $2;
2269 my ($kohafield) = $dbh->selectrow_array(q|
2270 SELECT kohafield FROM marc_subfield_structure
2271 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2272 |, undef, $marcfield, $marcsubfield);
2275 $column = $kohafield;
2277 # MARC field is not linked to a DB field so we need to use
2278 # ExtractValue on marcxml from biblio_metadata or
2279 # items.more_subfields_xml, depending on the MARC field.
2282 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2283 if ($marcfield eq $itemfield) {
2284 $sqlfield = 'more_subfields_xml';
2285 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2287 $sqlfield = 'metadata'; # From biblio_metadata
2288 if ($marcfield < 10) {
2289 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2291 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2294 $column = "ExtractValue($sqlfield, '$xpath')";
2300 if (ref $query eq 'ARRAY') {
2303 } elsif ($op eq '!=') {
2307 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2312 str => "$column $op ?",
2319 return $where_fragment;
2324 my ($items, $total) = SearchItems($filter, $params);
2326 Perform a search among items
2328 $filter is a reference to a hash which can be a filter, or a combination of filters.
2330 A filter has the following keys:
2334 =item * field: the name of a SQL column in table items
2336 =item * query: the value to search in this column
2338 =item * operator: comparison operator. Can be one of = != > < >= <= like
2342 A combination of filters hash the following keys:
2346 =item * conjunction: 'AND' or 'OR'
2348 =item * filters: array ref of filters
2352 $params is a reference to a hash that can contain the following parameters:
2356 =item * rows: Number of items to return. 0 returns everything (default: 0)
2358 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2361 =item * sortby: A SQL column name in items table to sort on
2363 =item * sortorder: 'ASC' or 'DESC'
2370 my ($filter, $params) = @_;
2374 return unless ref $filter eq 'HASH';
2375 return unless ref $params eq 'HASH';
2377 # Default parameters
2378 $params->{rows} ||= 0;
2379 $params->{page} ||= 1;
2380 $params->{sortby} ||= 'itemnumber';
2381 $params->{sortorder} ||= 'ASC';
2383 my ($where_str, @where_args);
2384 my $where_fragment = _SearchItems_build_where_fragment($filter);
2385 if ($where_fragment) {
2386 $where_str = $where_fragment->{str};
2387 @where_args = @{ $where_fragment->{args} };
2390 my $dbh = C4::Context->dbh;
2392 SELECT SQL_CALC_FOUND_ROWS items.*
2394 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2395 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2396 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2399 if (defined $where_str and $where_str ne '') {
2400 $query .= qq{ AND $where_str };
2403 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2404 push @where_args, C4::Context->preference('marcflavour');
2406 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2407 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2408 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2409 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2410 ? $params->{sortby} : 'itemnumber';
2411 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2412 $query .= qq{ ORDER BY $sortby $sortorder };
2414 my $rows = $params->{rows};
2417 my $offset = $rows * ($params->{page}-1);
2418 $query .= qq { LIMIT ?, ? };
2419 push @limit_args, $offset, $rows;
2422 my $sth = $dbh->prepare($query);
2423 my $rv = $sth->execute(@where_args, @limit_args);
2425 return unless ($rv);
2426 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2428 return ($sth->fetchall_arrayref({}), $total_rows);
2432 =head1 OTHER FUNCTIONS
2436 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2438 Find the given $subfield in the given $tag in the given
2439 MARC::Record $record. If the subfield is found, returns
2440 the (indicators, value) pair; otherwise, (undef, undef) is
2444 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2445 I suggest we export it from this module.
2450 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2453 if ( $tagfield < 10 ) {
2454 if ( $record->field($tagfield) ) {
2455 push @result, $record->field($tagfield)->data();
2460 foreach my $field ( $record->field($tagfield) ) {
2461 my @subfields = $field->subfields();
2462 foreach my $subfield (@subfields) {
2463 if ( @$subfield[0] eq $insubfield ) {
2464 push @result, @$subfield[1];
2465 $indicator = $field->indicator(1) . $field->indicator(2);
2470 return ( $indicator, @result );
2474 =head2 PrepareItemrecordDisplay
2476 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2478 Returns a hash with all the fields for Display a given item data in a template
2480 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2484 sub PrepareItemrecordDisplay {
2486 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2488 my $dbh = C4::Context->dbh;
2489 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2490 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2492 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2493 # a shared data structure. No plugin (including custom ones) should change
2494 # its contents. See also GetMarcStructure.
2495 my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2497 # return nothing if we don't have found an existing framework.
2498 return q{} unless $tagslib;
2501 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2505 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2507 SELECT authorised_value,lib FROM authorised_values
2510 LEFT JOIN authorised_values_branches ON ( id = av_id )
2515 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2516 $query .= qq{ ORDER BY lib};
2517 my $authorised_values_sth = $dbh->prepare( $query );
2518 foreach my $tag ( sort keys %{$tagslib} ) {
2521 # loop through each subfield
2523 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2524 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2525 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2526 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2528 $subfield_data{tag} = $tag;
2529 $subfield_data{subfield} = $subfield;
2530 $subfield_data{countsubfield} = $cntsubf++;
2531 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2532 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2534 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2535 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2536 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2537 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2538 $subfield_data{hidden} = "display:none"
2539 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2540 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2541 my ( $x, $defaultvalue );
2543 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2545 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2546 if ( !defined $defaultvalue ) {
2547 $defaultvalue = q||;
2549 $defaultvalue =~ s/"/"/g;
2552 # search for itemcallnumber if applicable
2553 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2554 && C4::Context->preference('itemcallnumber') ) {
2555 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2556 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2557 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2558 $defaultvalue = $field->subfield($CNsubfield);
2561 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2563 && $defaultvalues->{'callnumber'} ) {
2564 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2565 # if the item record exists, only use default value if the item has no callnumber
2566 $defaultvalue = $defaultvalues->{callnumber};
2567 } elsif ( !$itemrecord and $defaultvalues ) {
2568 # if the item record *doesn't* exists, always use the default value
2569 $defaultvalue = $defaultvalues->{callnumber};
2572 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2574 && $defaultvalues->{'branchcode'} ) {
2575 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2576 $defaultvalue = $defaultvalues->{branchcode};
2579 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2581 && $defaultvalues->{'location'} ) {
2583 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2584 # if the item record exists, only use default value if the item has no locationr
2585 $defaultvalue = $defaultvalues->{location};
2586 } elsif ( !$itemrecord and $defaultvalues ) {
2587 # if the item record *doesn't* exists, always use the default value
2588 $defaultvalue = $defaultvalues->{location};
2591 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2592 my @authorised_values;
2595 # builds list, depending on authorised value...
2597 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2598 if ( ( C4::Context->preference("IndependentBranches") )
2599 && !C4::Context->IsSuperLibrarian() ) {
2600 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2601 $sth->execute( C4::Context->userenv->{branch} );
2602 push @authorised_values, ""
2603 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2604 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2605 push @authorised_values, $branchcode;
2606 $authorised_lib{$branchcode} = $branchname;
2609 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2611 push @authorised_values, ""
2612 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2613 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2614 push @authorised_values, $branchcode;
2615 $authorised_lib{$branchcode} = $branchname;
2619 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2620 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2621 $defaultvalue = $defaultvalues->{branchcode};
2625 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2626 my $itemtypes = Koha::ItemTypes->search_with_localization;
2627 push @authorised_values, ""
2628 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2629 while ( my $itemtype = $itemtypes->next ) {
2630 push @authorised_values, $itemtype->itemtype;
2631 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2633 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2634 $defaultvalue = $defaultvalues->{'itemtype'};
2638 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2639 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2641 my $class_sources = GetClassSources();
2642 my $default_source = C4::Context->preference("DefaultClassificationSource");
2644 foreach my $class_source (sort keys %$class_sources) {
2645 next unless $class_sources->{$class_source}->{'used'} or
2646 ($class_source eq $default_source);
2647 push @authorised_values, $class_source;
2648 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2651 $defaultvalue = $default_source;
2653 #---- "true" authorised value
2655 $authorised_values_sth->execute(
2656 $tagslib->{$tag}->{$subfield}->{authorised_value},
2657 $branch_limit ? $branch_limit : ()
2659 push @authorised_values, ""
2660 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2661 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2662 push @authorised_values, $value;
2663 $authorised_lib{$value} = $lib;
2666 $subfield_data{marc_value} = {
2668 values => \@authorised_values,
2669 default => "$defaultvalue",
2670 labels => \%authorised_lib,
2672 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2674 require Koha::FrameworkPlugin;
2675 my $plugin = Koha::FrameworkPlugin->new({
2676 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2679 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2680 $plugin->build( $pars );
2681 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2682 $defaultvalue = $field->subfield($subfield);
2684 if( !$plugin->errstr ) {
2685 #TODO Move html to template; see report 12176/13397
2686 my $tab= $plugin->noclick? '-1': '';
2687 my $class= $plugin->noclick? ' disabled': '';
2688 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2689 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2691 warn $plugin->errstr;
2692 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />); # supply default input form
2695 elsif ( $tag eq '' ) { # it's an hidden field
2696 $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2698 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2699 $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
2701 elsif ( length($defaultvalue) > 100
2702 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2703 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2704 or (C4::Context->preference("marcflavour") eq "MARC21" and
2705 500 <= $tag && $tag < 600 )
2707 # oversize field (textarea)
2708 $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
2710 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2712 push( @loop_data, \%subfield_data );
2717 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2718 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2721 'itemtagfield' => $itemtagfield,
2722 'itemtagsubfield' => $itemtagsubfield,
2723 'itemnumber' => $itemnumber,
2724 'iteminformation' => \@loop_data
2728 sub ToggleNewStatus {
2729 my ( $params ) = @_;
2730 my @rules = @{ $params->{rules} };
2731 my $report_only = $params->{report_only};
2733 my $dbh = C4::Context->dbh;
2735 my @item_columns = map { "items.$_" } Koha::Items->columns;
2736 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2738 for my $rule ( @rules ) {
2739 my $age = $rule->{age};
2740 my $conditions = $rule->{conditions};
2741 my $substitutions = $rule->{substitutions};
2745 SELECT items.biblionumber, items.itemnumber
2747 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2750 for my $condition ( @$conditions ) {
2752 grep {/^$condition->{field}$/} @item_columns
2753 or grep {/^$condition->{field}$/} @biblioitem_columns
2755 if ( $condition->{value} =~ /\|/ ) {
2756 my @values = split /\|/, $condition->{value};
2757 $query .= qq| AND $condition->{field} IN (|
2758 . join( ',', ('?') x scalar @values )
2760 push @params, @values;
2762 $query .= qq| AND $condition->{field} = ?|;
2763 push @params, $condition->{value};
2767 if ( defined $age ) {
2768 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2771 my $sth = $dbh->prepare($query);
2772 $sth->execute( @params );
2773 while ( my $values = $sth->fetchrow_hashref ) {
2774 my $biblionumber = $values->{biblionumber};
2775 my $itemnumber = $values->{itemnumber};
2776 my $item = C4::Items::GetItem( $itemnumber );
2777 for my $substitution ( @$substitutions ) {
2778 next unless $substitution->{field};
2779 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
2780 unless $report_only;
2781 push @{ $report->{$itemnumber} }, $substitution;