Bug 19350 - Holds without link in 773 trigger SQL::Abstract::puke
[koha.git] / C4 / Items.pm
blobef38b67c249392a377b6f92b17e08caa8480c483
1 package C4::Items;
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>.
21 use strict;
22 #use warnings; FIXME - Bug 2505
24 use Carp;
25 use C4::Context;
26 use C4::Koha;
27 use C4::Biblio;
28 use Koha::DateUtils;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use YAML qw/Load/;
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/;
40 use Koha::Database;
42 use Koha::Biblioitems;
43 use Koha::Items;
44 use Koha::ItemTypes;
45 use Koha::SearchEngine;
46 use Koha::SearchEngine::Search;
47 use Koha::Libraries;
49 use vars qw(@ISA @EXPORT);
51 BEGIN {
53 require Exporter;
54 @ISA = qw( Exporter );
56 # function exports
57 @EXPORT = qw(
58 GetItem
59 AddItemFromMarc
60 AddItem
61 AddItemBatchFromMarc
62 ModItemFromMarc
63 Item2Marc
64 ModItem
65 ModDateLastSeen
66 ModItemTransfer
67 DelItem
69 CheckItemPreSave
71 GetLostItems
72 GetItemsForInventory
73 GetItemInfosOf
74 GetItemsByBiblioitemnumber
75 GetItemsInfo
76 GetItemsLocationInfo
77 GetHostItemsInfo
78 GetItemnumbersForBiblio
79 get_itemnumbers_of
80 get_hostitemnumbers_of
81 GetItemnumberFromBarcode
82 GetBarcodeFromItemnumber
83 GetHiddenItemnumbers
84 ItemSafeToDelete
85 DelItemCheck
86 MoveItemFromBiblio
87 GetLatestAcquisitions
89 CartToShelf
90 ShelfToCart
92 GetAnalyticsCount
94 SearchItemsByField
95 SearchItems
97 PrepareItemrecordDisplay
102 =head1 NAME
104 C4::Items - item management functions
106 =head1 DESCRIPTION
108 This module contains an API for manipulating item
109 records in Koha, and is used by cataloguing, circulation,
110 acquisitions, and serials management.
112 # FIXME This POD is not up-to-date
113 A Koha item record is stored in two places: the
114 items table and embedded in a MARC tag in the XML
115 version of the associated bib record in C<biblioitems.marcxml>.
116 This is done to allow the item information to be readily
117 indexed (e.g., by Zebra), but means that each item
118 modification transaction must keep the items table
119 and the MARC XML in sync at all times.
121 Consequently, all code that creates, modifies, or deletes
122 item records B<must> use an appropriate function from
123 C<C4::Items>. If no existing function is suitable, it is
124 better to add one to C<C4::Items> than to use add
125 one-off SQL statements to add or modify items.
127 The items table will be considered authoritative. In other
128 words, if there is ever a discrepancy between the items
129 table and the MARC XML, the items table should be considered
130 accurate.
132 =head1 HISTORICAL NOTE
134 Most of the functions in C<C4::Items> were originally in
135 the C<C4::Biblio> module.
137 =head1 CORE EXPORTED FUNCTIONS
139 The following functions are meant for use by users
140 of C<C4::Items>
142 =cut
144 =head2 GetItem
146 $item = GetItem($itemnumber,$barcode,$serial);
148 Return item information, for a given itemnumber or barcode.
149 The return value is a hashref mapping item column
150 names to values. If C<$serial> is true, include serial publication data.
152 =cut
154 sub GetItem {
155 my ($itemnumber,$barcode, $serial) = @_;
156 my $dbh = C4::Context->dbh;
158 my $item;
159 if ($itemnumber) {
160 $item = Koha::Items->find( $itemnumber );
161 } else {
162 $item = Koha::Items->find( { barcode => $barcode } );
165 return unless ( $item );
167 my $data = $item->unblessed();
168 $data->{itype} = $item->effective_itemtype(); # set the correct itype
170 if ($serial) {
171 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
172 $ssth->execute( $data->{'itemnumber'} );
173 ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
176 return $data;
177 } # sub GetItem
179 =head2 CartToShelf
181 CartToShelf($itemnumber);
183 Set the current shelving location of the item record
184 to its stored permanent shelving location. This is
185 primarily used to indicate when an item whose current
186 location is a special processing ('PROC') or shelving cart
187 ('CART') location is back in the stacks.
189 =cut
191 sub CartToShelf {
192 my ( $itemnumber ) = @_;
194 unless ( $itemnumber ) {
195 croak "FAILED CartToShelf() - no itemnumber supplied";
198 my $item = GetItem($itemnumber);
199 if ( $item->{location} eq 'CART' ) {
200 $item->{location} = $item->{permanent_location};
201 ModItem($item, undef, $itemnumber);
205 =head2 ShelfToCart
207 ShelfToCart($itemnumber);
209 Set the current shelving location of the item
210 to shelving cart ('CART').
212 =cut
214 sub ShelfToCart {
215 my ( $itemnumber ) = @_;
217 unless ( $itemnumber ) {
218 croak "FAILED ShelfToCart() - no itemnumber supplied";
221 my $item = GetItem($itemnumber);
222 $item->{'location'} = 'CART';
223 ModItem($item, undef, $itemnumber);
226 =head2 AddItemFromMarc
228 my ($biblionumber, $biblioitemnumber, $itemnumber)
229 = AddItemFromMarc($source_item_marc, $biblionumber);
231 Given a MARC::Record object containing an embedded item
232 record and a biblionumber, create a new item record.
234 =cut
236 sub AddItemFromMarc {
237 my ( $source_item_marc, $biblionumber ) = @_;
238 my $dbh = C4::Context->dbh;
240 # parse item hash from MARC
241 my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber );
242 my ($itemtag,$itemsubfield)=C4::Biblio::GetMarcFromKohaField("items.itemnumber",$frameworkcode);
244 my $localitemmarc=MARC::Record->new;
245 $localitemmarc->append_fields($source_item_marc->field($itemtag));
246 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
247 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
248 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
251 =head2 AddItem
253 my ($biblionumber, $biblioitemnumber, $itemnumber)
254 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
256 Given a hash containing item column names as keys,
257 create a new Koha item record.
259 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
260 do not need to be supplied for general use; they exist
261 simply to allow them to be picked up from AddItemFromMarc.
263 The final optional parameter, C<$unlinked_item_subfields>, contains
264 an arrayref containing subfields present in the original MARC
265 representation of the item (e.g., from the item editor) that are
266 not mapped to C<items> columns directly but should instead
267 be stored in C<items.more_subfields_xml> and included in
268 the biblio items tag for display and indexing.
270 =cut
272 sub AddItem {
273 my $item = shift;
274 my $biblionumber = shift;
276 my $dbh = @_ ? shift : C4::Context->dbh;
277 my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
278 my $unlinked_item_subfields;
279 if (@_) {
280 $unlinked_item_subfields = shift;
283 # needs old biblionumber and biblioitemnumber
284 $item->{'biblionumber'} = $biblionumber;
285 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
286 $sth->execute( $item->{'biblionumber'} );
287 ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
289 _set_defaults_for_add($item);
290 _set_derived_columns_for_add($item);
291 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
293 # FIXME - checks here
294 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
295 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
296 $itype_sth->execute( $item->{'biblionumber'} );
297 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
300 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
301 return if $error;
303 $item->{'itemnumber'} = $itemnumber;
305 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
307 logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
308 if C4::Context->preference("CataloguingLog");
310 return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
313 =head2 AddItemBatchFromMarc
315 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
316 $biblionumber, $biblioitemnumber, $frameworkcode);
318 Efficiently create item records from a MARC biblio record with
319 embedded item fields. This routine is suitable for batch jobs.
321 This API assumes that the bib record has already been
322 saved to the C<biblio> and C<biblioitems> tables. It does
323 not expect that C<biblio_metadata.metadata> is populated, but it
324 will do so via a call to ModBibiloMarc.
326 The goal of this API is to have a similar effect to using AddBiblio
327 and AddItems in succession, but without inefficient repeated
328 parsing of the MARC XML bib record.
330 This function returns an arrayref of new itemsnumbers and an arrayref of item
331 errors encountered during the processing. Each entry in the errors
332 list is a hashref containing the following keys:
334 =over
336 =item item_sequence
338 Sequence number of original item tag in the MARC record.
340 =item item_barcode
342 Item barcode, provide to assist in the construction of
343 useful error messages.
345 =item error_code
347 Code representing the error condition. Can be 'duplicate_barcode',
348 'invalid_homebranch', or 'invalid_holdingbranch'.
350 =item error_information
352 Additional information appropriate to the error condition.
354 =back
356 =cut
358 sub AddItemBatchFromMarc {
359 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
360 my $error;
361 my @itemnumbers = ();
362 my @errors = ();
363 my $dbh = C4::Context->dbh;
365 # We modify the record, so lets work on a clone so we don't change the
366 # original.
367 $record = $record->clone();
368 # loop through the item tags and start creating items
369 my @bad_item_fields = ();
370 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",'');
371 my $item_sequence_num = 0;
372 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
373 $item_sequence_num++;
374 # we take the item field and stick it into a new
375 # MARC record -- this is required so far because (FIXME)
376 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
377 # and there is no TransformMarcFieldToKoha
378 my $temp_item_marc = MARC::Record->new();
379 $temp_item_marc->append_fields($item_field);
381 # add biblionumber and biblioitemnumber
382 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
383 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
384 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
385 $item->{'biblionumber'} = $biblionumber;
386 $item->{'biblioitemnumber'} = $biblioitemnumber;
388 # check for duplicate barcode
389 my %item_errors = CheckItemPreSave($item);
390 if (%item_errors) {
391 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
392 push @bad_item_fields, $item_field;
393 next ITEMFIELD;
396 _set_defaults_for_add($item);
397 _set_derived_columns_for_add($item);
398 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
399 warn $error if $error;
400 push @itemnumbers, $itemnumber; # FIXME not checking error
401 $item->{'itemnumber'} = $itemnumber;
403 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
405 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
406 $item_field->replace_with($new_item_marc->field($itemtag));
409 # remove any MARC item fields for rejected items
410 foreach my $item_field (@bad_item_fields) {
411 $record->delete_field($item_field);
414 # update the MARC biblio
415 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
417 return (\@itemnumbers, \@errors);
420 =head2 ModItemFromMarc
422 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
424 This function updates an item record based on a supplied
425 C<MARC::Record> object containing an embedded item field.
426 This API is meant for the use of C<additem.pl>; for
427 other purposes, C<ModItem> should be used.
429 This function uses the hash %default_values_for_mod_from_marc,
430 which contains default values for item fields to
431 apply when modifying an item. This is needed because
432 if an item field's value is cleared, TransformMarcToKoha
433 does not include the column in the
434 hash that's passed to ModItem, which without
435 use of this hash makes it impossible to clear
436 an item field's value. See bug 2466.
438 Note that only columns that can be directly
439 changed from the cataloging and serials
440 item editors are included in this hash.
442 Returns item record
444 =cut
446 sub _build_default_values_for_mod_marc {
447 my ($frameworkcode) = @_;
449 my $cache = Koha::Caches->get_instance();
450 my $cache_key = "default_value_for_mod_marc-$frameworkcode";
451 my $cached = $cache->get_from_cache($cache_key);
452 return $cached if $cached;
454 my $default_values = {
455 barcode => undef,
456 booksellerid => undef,
457 ccode => undef,
458 'items.cn_source' => undef,
459 coded_location_qualifier => undef,
460 copynumber => undef,
461 damaged => 0,
462 enumchron => undef,
463 holdingbranch => undef,
464 homebranch => undef,
465 itemcallnumber => undef,
466 itemlost => 0,
467 itemnotes => undef,
468 itemnotes_nonpublic => undef,
469 itype => undef,
470 location => undef,
471 permanent_location => undef,
472 materials => undef,
473 new_status => undef,
474 notforloan => 0,
475 # paidfor => undef, # commented, see bug 12817
476 price => undef,
477 replacementprice => undef,
478 replacementpricedate => undef,
479 restricted => undef,
480 stack => undef,
481 stocknumber => undef,
482 uri => undef,
483 withdrawn => 0,
485 my %default_values_for_mod_from_marc;
486 while ( my ( $field, $default_value ) = each %$default_values ) {
487 my $kohafield = $field;
488 $kohafield =~ s|^([^\.]+)$|items.$1|;
489 $default_values_for_mod_from_marc{$field} =
490 $default_value
491 if C4::Koha::IsKohaFieldLinked(
492 { kohafield => $kohafield, frameworkcode => $frameworkcode } );
495 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
496 return \%default_values_for_mod_from_marc;
499 sub ModItemFromMarc {
500 my $item_marc = shift;
501 my $biblionumber = shift;
502 my $itemnumber = shift;
504 my $dbh = C4::Context->dbh;
505 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
506 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
508 my $localitemmarc = MARC::Record->new;
509 $localitemmarc->append_fields( $item_marc->field($itemtag) );
510 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
511 my $default_values = _build_default_values_for_mod_marc($frameworkcode);
512 foreach my $item_field ( keys %$default_values ) {
513 $item->{$item_field} = $default_values->{$item_field}
514 unless exists $item->{$item_field};
516 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
518 ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
519 return $item;
522 =head2 ModItem
524 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
526 Change one or more columns in an item record and update
527 the MARC representation of the item.
529 The first argument is a hashref mapping from item column
530 names to the new values. The second and third arguments
531 are the biblionumber and itemnumber, respectively.
533 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
534 an arrayref containing subfields present in the original MARC
535 representation of the item (e.g., from the item editor) that are
536 not mapped to C<items> columns directly but should instead
537 be stored in C<items.more_subfields_xml> and included in
538 the biblio items tag for display and indexing.
540 If one of the changed columns is used to calculate
541 the derived value of a column such as C<items.cn_sort>,
542 this routine will perform the necessary calculation
543 and set the value.
545 =cut
547 sub ModItem {
548 my $item = shift;
549 my $biblionumber = shift;
550 my $itemnumber = shift;
552 # if $biblionumber is undefined, get it from the current item
553 unless (defined $biblionumber) {
554 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
557 my $dbh = @_ ? shift : C4::Context->dbh;
558 my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode( $biblionumber );
560 my $unlinked_item_subfields;
561 if (@_) {
562 $unlinked_item_subfields = shift;
563 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
566 $item->{'itemnumber'} = $itemnumber or return;
568 my @fields = qw( itemlost withdrawn );
570 # Only call GetItem if we need to set an "on" date field
571 if ( $item->{itemlost} || $item->{withdrawn} ) {
572 my $pre_mod_item = GetItem( $item->{'itemnumber'} );
573 for my $field (@fields) {
574 if ( defined( $item->{$field} )
575 and not $pre_mod_item->{$field}
576 and $item->{$field} )
578 $item->{ $field . '_on' } =
579 DateTime::Format::MySQL->format_datetime( dt_from_string() );
584 # If the field is defined but empty, we are removing and,
585 # and thus need to clear out the 'on' field as well
586 for my $field (@fields) {
587 if ( defined( $item->{$field} ) && !$item->{$field} ) {
588 $item->{ $field . '_on' } = undef;
593 _set_derived_columns_for_mod($item);
594 _do_column_fixes_for_mod($item);
595 # FIXME add checks
596 # duplicate barcode
597 # attempt to change itemnumber
598 # attempt to change biblionumber (if we want
599 # an API to relink an item to a different bib,
600 # it should be a separate function)
602 # update items table
603 _koha_modify_item($item);
605 # request that bib be reindexed so that searching on current
606 # item status is possible
607 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
609 logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
612 =head2 ModItemTransfer
614 ModItemTransfer($itenumber, $frombranch, $tobranch);
616 Marks an item as being transferred from one branch
617 to another.
619 =cut
621 sub ModItemTransfer {
622 my ( $itemnumber, $frombranch, $tobranch ) = @_;
624 my $dbh = C4::Context->dbh;
626 # Remove the 'shelving cart' location status if it is being used.
627 CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
629 #new entry in branchtransfers....
630 my $sth = $dbh->prepare(
631 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
632 VALUES (?, ?, NOW(), ?)");
633 $sth->execute($itemnumber, $frombranch, $tobranch);
635 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
636 ModDateLastSeen($itemnumber);
637 return;
640 =head2 ModDateLastSeen
642 ModDateLastSeen($itemnum);
644 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
645 C<$itemnum> is the item number
647 =cut
649 sub ModDateLastSeen {
650 my ($itemnumber) = @_;
652 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
653 ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
656 =head2 DelItem
658 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
660 Exported function (core API) for deleting an item record in Koha.
662 =cut
664 sub DelItem {
665 my ( $params ) = @_;
667 my $itemnumber = $params->{itemnumber};
668 my $biblionumber = $params->{biblionumber};
670 unless ($biblionumber) {
671 $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
674 # If there is no biblionumber for the given itemnumber, there is nothing to delete
675 return 0 unless $biblionumber;
677 # FIXME check the item has no current issues
678 my $deleted = _koha_delete_item( $itemnumber );
680 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
682 #search item field code
683 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
684 return $deleted;
687 =head2 CheckItemPreSave
689 my $item_ref = TransformMarcToKoha($marc, 'items');
690 # do stuff
691 my %errors = CheckItemPreSave($item_ref);
692 if (exists $errors{'duplicate_barcode'}) {
693 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
694 } elsif (exists $errors{'invalid_homebranch'}) {
695 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
696 } elsif (exists $errors{'invalid_holdingbranch'}) {
697 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
698 } else {
699 print "item is OK";
702 Given a hashref containing item fields, determine if it can be
703 inserted or updated in the database. Specifically, checks for
704 database integrity issues, and returns a hash containing any
705 of the following keys, if applicable.
707 =over 2
709 =item duplicate_barcode
711 Barcode, if it duplicates one already found in the database.
713 =item invalid_homebranch
715 Home branch, if not defined in branches table.
717 =item invalid_holdingbranch
719 Holding branch, if not defined in branches table.
721 =back
723 This function does NOT implement any policy-related checks,
724 e.g., whether current operator is allowed to save an
725 item that has a given branch code.
727 =cut
729 sub CheckItemPreSave {
730 my $item_ref = shift;
732 my %errors = ();
734 # check for duplicate barcode
735 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
736 my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
737 if ($existing_itemnumber) {
738 if (!exists $item_ref->{'itemnumber'} # new item
739 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
740 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
745 # check for valid home branch
746 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
747 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
748 unless (defined $home_library) {
749 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
753 # check for valid holding branch
754 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
755 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
756 unless (defined $holding_library) {
757 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
761 return %errors;
765 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
767 The following functions provide various ways of
768 getting an item record, a set of item records, or
769 lists of authorized values for certain item fields.
771 Some of the functions in this group are candidates
772 for refactoring -- for example, some of the code
773 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
774 has copy-and-paste work.
776 =cut
778 =head2 GetLostItems
780 $items = GetLostItems( $where );
782 This function gets a list of lost items.
784 =over 2
786 =item input:
788 C<$where> is a hashref. it containts a field of the items table as key
789 and the value to match as value. For example:
791 { barcode => 'abc123',
792 homebranch => 'CPL', }
794 =item return:
796 C<$items> is a reference to an array full of hashrefs with columns
797 from the "items" table as keys.
799 =item usage in the perl script:
801 my $where = { barcode => '0001548' };
802 my $items = GetLostItems( $where );
803 $template->param( itemsloop => $items );
805 =back
807 =cut
809 sub GetLostItems {
810 # Getting input args.
811 my $where = shift;
812 my $dbh = C4::Context->dbh;
814 my $query = "
815 SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
816 itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
817 FROM items
818 LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
819 LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
820 LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
821 WHERE
822 authorised_values.category = 'LOST'
823 AND itemlost IS NOT NULL
824 AND itemlost <> 0
826 my @query_parameters;
827 foreach my $key (keys %$where) {
828 $query .= " AND $key LIKE ?";
829 push @query_parameters, "%$where->{$key}%";
832 my $sth = $dbh->prepare($query);
833 $sth->execute( @query_parameters );
834 my $items = [];
835 while ( my $row = $sth->fetchrow_hashref ){
836 push @$items, $row;
838 return $items;
841 =head2 GetItemsForInventory
843 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
844 minlocation => $minlocation,
845 maxlocation => $maxlocation,
846 location => $location,
847 itemtype => $itemtype,
848 ignoreissued => $ignoreissued,
849 datelastseen => $datelastseen,
850 branchcode => $branchcode,
851 branch => $branch,
852 offset => $offset,
853 size => $size,
854 statushash => $statushash,
855 } );
857 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
859 The sub returns a reference to a list of hashes, each containing
860 itemnumber, author, title, barcode, item callnumber, and date last
861 seen. It is ordered by callnumber then title.
863 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
864 the datelastseen can be used to specify that you want to see items not seen since a past date only.
865 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
866 $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.
868 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
870 =cut
872 sub GetItemsForInventory {
873 my ( $parameters ) = @_;
874 my $minlocation = $parameters->{'minlocation'} // '';
875 my $maxlocation = $parameters->{'maxlocation'} // '';
876 my $location = $parameters->{'location'} // '';
877 my $itemtype = $parameters->{'itemtype'} // '';
878 my $ignoreissued = $parameters->{'ignoreissued'} // '';
879 my $datelastseen = $parameters->{'datelastseen'} // '';
880 my $branchcode = $parameters->{'branchcode'} // '';
881 my $branch = $parameters->{'branch'} // '';
882 my $offset = $parameters->{'offset'} // '';
883 my $size = $parameters->{'size'} // '';
884 my $statushash = $parameters->{'statushash'} // '';
886 my $dbh = C4::Context->dbh;
887 my ( @bind_params, @where_strings );
889 my $select_columns = q{
890 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
892 my $select_count = q{SELECT COUNT(*)};
893 my $query = q{
894 FROM items
895 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
896 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
898 if ($statushash){
899 for my $authvfield (keys %$statushash){
900 if ( scalar @{$statushash->{$authvfield}} > 0 ){
901 my $joinedvals = join ',', @{$statushash->{$authvfield}};
902 push @where_strings, "$authvfield in (" . $joinedvals . ")";
907 if ($minlocation) {
908 push @where_strings, 'itemcallnumber >= ?';
909 push @bind_params, $minlocation;
912 if ($maxlocation) {
913 push @where_strings, 'itemcallnumber <= ?';
914 push @bind_params, $maxlocation;
917 if ($datelastseen) {
918 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
919 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
920 push @bind_params, $datelastseen;
923 if ( $location ) {
924 push @where_strings, 'items.location = ?';
925 push @bind_params, $location;
928 if ( $branchcode ) {
929 if($branch eq "homebranch"){
930 push @where_strings, 'items.homebranch = ?';
931 }else{
932 push @where_strings, 'items.holdingbranch = ?';
934 push @bind_params, $branchcode;
937 if ( $itemtype ) {
938 push @where_strings, 'biblioitems.itemtype = ?';
939 push @bind_params, $itemtype;
942 if ( $ignoreissued) {
943 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
944 push @where_strings, 'issues.date_due IS NULL';
947 if ( @where_strings ) {
948 $query .= 'WHERE ';
949 $query .= join ' AND ', @where_strings;
951 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
952 my $count_query = $select_count . $query;
953 $query .= " LIMIT $offset, $size" if ($offset and $size);
954 $query = $select_columns . $query;
955 my $sth = $dbh->prepare($query);
956 $sth->execute( @bind_params );
958 my @results = ();
959 my $tmpresults = $sth->fetchall_arrayref({});
960 $sth = $dbh->prepare( $count_query );
961 $sth->execute( @bind_params );
962 my ($iTotalRecords) = $sth->fetchrow_array();
964 my @avs = Koha::AuthorisedValues->search(
965 { 'marc_subfield_structures.kohafield' => { '>' => '' },
966 'me.authorised_value' => { '>' => '' },
968 { join => { category => 'marc_subfield_structures' },
969 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
970 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
971 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
975 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
977 foreach my $row (@$tmpresults) {
979 # Auth values
980 foreach (keys %$row) {
981 if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
982 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
985 push @results, $row;
988 return (\@results, $iTotalRecords);
991 =head2 GetItemInfosOf
993 GetItemInfosOf(@itemnumbers);
995 =cut
997 sub GetItemInfosOf {
998 my @itemnumbers = @_;
1000 my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1002 my $dbh = C4::Context->dbh;
1003 my $query = "
1004 SELECT *
1005 FROM items
1006 WHERE itemnumber IN ($itemnumber_values)
1008 return $dbh->selectall_hashref($query, 'itemnumber');
1011 =head2 GetItemsByBiblioitemnumber
1013 GetItemsByBiblioitemnumber($biblioitemnumber);
1015 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1016 Called by C<C4::XISBN>
1018 =cut
1020 sub GetItemsByBiblioitemnumber {
1021 my ( $bibitem ) = @_;
1022 my $dbh = C4::Context->dbh;
1023 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1024 # Get all items attached to a biblioitem
1025 my $i = 0;
1026 my @results;
1027 $sth->execute($bibitem) || die $sth->errstr;
1028 while ( my $data = $sth->fetchrow_hashref ) {
1029 # Foreach item, get circulation information
1030 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1031 WHERE itemnumber = ?
1032 AND issues.borrowernumber = borrowers.borrowernumber"
1034 $sth2->execute( $data->{'itemnumber'} );
1035 if ( my $data2 = $sth2->fetchrow_hashref ) {
1036 # if item is out, set the due date and who it is out too
1037 $data->{'date_due'} = $data2->{'date_due'};
1038 $data->{'cardnumber'} = $data2->{'cardnumber'};
1039 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
1041 else {
1042 # set date_due to blank, so in the template we check itemlost, and withdrawn
1043 $data->{'date_due'} = '';
1044 } # else
1045 # Find the last 3 people who borrowed this item.
1046 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1047 AND old_issues.borrowernumber = borrowers.borrowernumber
1048 ORDER BY returndate desc,timestamp desc LIMIT 3";
1049 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1050 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1051 my $i2 = 0;
1052 while ( my $data2 = $sth2->fetchrow_hashref ) {
1053 $data->{"timestamp$i2"} = $data2->{'timestamp'};
1054 $data->{"card$i2"} = $data2->{'cardnumber'};
1055 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
1056 $i2++;
1058 push(@results,$data);
1060 return (\@results);
1063 =head2 GetItemsInfo
1065 @results = GetItemsInfo($biblionumber);
1067 Returns information about items with the given biblionumber.
1069 C<GetItemsInfo> returns a list of references-to-hash. Each element
1070 contains a number of keys. Most of them are attributes from the
1071 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1072 Koha database. Other keys include:
1074 =over 2
1076 =item C<$data-E<gt>{branchname}>
1078 The name (not the code) of the branch to which the book belongs.
1080 =item C<$data-E<gt>{datelastseen}>
1082 This is simply C<items.datelastseen>, except that while the date is
1083 stored in YYYY-MM-DD format in the database, here it is converted to
1084 DD/MM/YYYY format. A NULL date is returned as C<//>.
1086 =item C<$data-E<gt>{datedue}>
1088 =item C<$data-E<gt>{class}>
1090 This is the concatenation of C<biblioitems.classification>, the book's
1091 Dewey code, and C<biblioitems.subclass>.
1093 =item C<$data-E<gt>{ocount}>
1095 I think this is the number of copies of the book available.
1097 =item C<$data-E<gt>{order}>
1099 If this is set, it is set to C<One Order>.
1101 =back
1103 =cut
1105 sub GetItemsInfo {
1106 my ( $biblionumber ) = @_;
1107 my $dbh = C4::Context->dbh;
1108 require C4::Languages;
1109 my $language = C4::Languages::getlanguage();
1110 my $query = "
1111 SELECT items.*,
1112 biblio.*,
1113 biblioitems.volume,
1114 biblioitems.number,
1115 biblioitems.itemtype,
1116 biblioitems.isbn,
1117 biblioitems.issn,
1118 biblioitems.publicationyear,
1119 biblioitems.publishercode,
1120 biblioitems.volumedate,
1121 biblioitems.volumedesc,
1122 biblioitems.lccn,
1123 biblioitems.url,
1124 items.notforloan as itemnotforloan,
1125 issues.borrowernumber,
1126 issues.date_due as datedue,
1127 issues.onsite_checkout,
1128 borrowers.cardnumber,
1129 borrowers.surname,
1130 borrowers.firstname,
1131 borrowers.branchcode as bcode,
1132 serial.serialseq,
1133 serial.publisheddate,
1134 itemtypes.description,
1135 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1136 itemtypes.notforloan as notforloan_per_itemtype,
1137 holding.branchurl,
1138 holding.branchcode,
1139 holding.branchname,
1140 holding.opac_info as holding_branch_opac_info,
1141 home.opac_info as home_branch_opac_info
1143 $query .= "
1144 FROM items
1145 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1146 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1147 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1148 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1149 LEFT JOIN issues USING (itemnumber)
1150 LEFT JOIN borrowers USING (borrowernumber)
1151 LEFT JOIN serialitems USING (itemnumber)
1152 LEFT JOIN serial USING (serialid)
1153 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1154 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1155 $query .= q|
1156 LEFT JOIN localization ON itemtypes.itemtype = localization.code
1157 AND localization.entity = 'itemtypes'
1158 AND localization.lang = ?
1161 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1162 my $sth = $dbh->prepare($query);
1163 $sth->execute($language, $biblionumber);
1164 my $i = 0;
1165 my @results;
1166 my $serial;
1168 my $userenv = C4::Context->userenv;
1169 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1170 while ( my $data = $sth->fetchrow_hashref ) {
1171 if ( $data->{borrowernumber} && $want_not_same_branch) {
1172 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1175 $serial ||= $data->{'serial'};
1177 my $descriptions;
1178 # get notforloan complete status if applicable
1179 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1180 $data->{notforloanvalue} = $descriptions->{lib} // '';
1181 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
1183 # get restricted status and description if applicable
1184 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1185 $data->{restricted} = $descriptions->{lib} // '';
1186 $data->{restrictedopac} = $descriptions->{opac_description} // '';
1188 # my stack procedures
1189 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1190 $data->{stack} = $descriptions->{lib} // '';
1192 # Find the last 3 people who borrowed this item.
1193 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1194 WHERE itemnumber = ?
1195 AND old_issues.borrowernumber = borrowers.borrowernumber
1196 ORDER BY returndate DESC
1197 LIMIT 3");
1198 $sth2->execute($data->{'itemnumber'});
1199 my $ii = 0;
1200 while (my $data2 = $sth2->fetchrow_hashref()) {
1201 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1202 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1203 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1204 $ii++;
1207 $results[$i] = $data;
1208 $i++;
1211 return $serial
1212 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1213 : @results;
1216 =head2 GetItemsLocationInfo
1218 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1220 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1222 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1224 =over 2
1226 =item C<$data-E<gt>{homebranch}>
1228 Branch Name of the item's homebranch
1230 =item C<$data-E<gt>{holdingbranch}>
1232 Branch Name of the item's holdingbranch
1234 =item C<$data-E<gt>{location}>
1236 Item's shelving location code
1238 =item C<$data-E<gt>{location_intranet}>
1240 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1242 =item C<$data-E<gt>{location_opac}>
1244 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1245 description is set.
1247 =item C<$data-E<gt>{itemcallnumber}>
1249 Item's itemcallnumber
1251 =item C<$data-E<gt>{cn_sort}>
1253 Item's call number normalized for sorting
1255 =back
1257 =cut
1259 sub GetItemsLocationInfo {
1260 my $biblionumber = shift;
1261 my @results;
1263 my $dbh = C4::Context->dbh;
1264 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1265 location, itemcallnumber, cn_sort
1266 FROM items, branches as a, branches as b
1267 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1268 AND biblionumber = ?
1269 ORDER BY cn_sort ASC";
1270 my $sth = $dbh->prepare($query);
1271 $sth->execute($biblionumber);
1273 while ( my $data = $sth->fetchrow_hashref ) {
1274 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1275 $av = $av->count ? $av->next : undef;
1276 $data->{location_intranet} = $av ? $av->lib : '';
1277 $data->{location_opac} = $av ? $av->opac_description : '';
1278 push @results, $data;
1280 return @results;
1283 =head2 GetHostItemsInfo
1285 $hostiteminfo = GetHostItemsInfo($hostfield);
1286 Returns the iteminfo for items linked to records via a host field
1288 =cut
1290 sub GetHostItemsInfo {
1291 my ($record) = @_;
1292 my @returnitemsInfo;
1294 if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1295 C4::Context->preference('marcflavour') eq 'NORMARC'){
1296 foreach my $hostfield ( $record->field('773') ) {
1297 my $hostbiblionumber = $hostfield->subfield("0");
1298 my $linkeditemnumber = $hostfield->subfield("9");
1299 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1300 foreach my $hostitemInfo (@hostitemInfos){
1301 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1302 push (@returnitemsInfo,$hostitemInfo);
1303 last;
1307 } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1308 foreach my $hostfield ( $record->field('461') ) {
1309 my $hostbiblionumber = $hostfield->subfield("0");
1310 my $linkeditemnumber = $hostfield->subfield("9");
1311 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1312 foreach my $hostitemInfo (@hostitemInfos){
1313 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1314 push (@returnitemsInfo,$hostitemInfo);
1315 last;
1320 return @returnitemsInfo;
1324 =head2 GetLastAcquisitions
1326 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1327 'itemtypes' => ('BK','BD')}, 10);
1329 =cut
1331 sub GetLastAcquisitions {
1332 my ($data,$max) = @_;
1334 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1336 my $number_of_branches = @{$data->{branches}};
1337 my $number_of_itemtypes = @{$data->{itemtypes}};
1340 my @where = ('WHERE 1 ');
1341 $number_of_branches and push @where
1342 , 'AND holdingbranch IN ('
1343 , join(',', ('?') x $number_of_branches )
1344 , ')'
1347 $number_of_itemtypes and push @where
1348 , "AND $itemtype IN ("
1349 , join(',', ('?') x $number_of_itemtypes )
1350 , ')'
1353 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1354 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1355 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1356 @where
1357 GROUP BY biblio.biblionumber
1358 ORDER BY dateaccessioned DESC LIMIT $max";
1360 my $dbh = C4::Context->dbh;
1361 my $sth = $dbh->prepare($query);
1363 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1365 my @results;
1366 while( my $row = $sth->fetchrow_hashref){
1367 push @results, {date => $row->{dateaccessioned}
1368 , biblionumber => $row->{biblionumber}
1369 , title => $row->{title}};
1372 return @results;
1375 =head2 GetItemnumbersForBiblio
1377 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1379 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1381 =cut
1383 sub GetItemnumbersForBiblio {
1384 my $biblionumber = shift;
1385 my @items;
1386 my $dbh = C4::Context->dbh;
1387 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1388 $sth->execute($biblionumber);
1389 while (my $result = $sth->fetchrow_hashref) {
1390 push @items, $result->{'itemnumber'};
1392 return \@items;
1395 =head2 get_itemnumbers_of
1397 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1399 Given a list of biblionumbers, return the list of corresponding itemnumbers
1400 for each biblionumber.
1402 Return a reference on a hash where keys are biblionumbers and values are
1403 references on array of itemnumbers.
1405 =cut
1407 sub get_itemnumbers_of {
1408 my @biblionumbers = @_;
1410 my $dbh = C4::Context->dbh;
1412 my $query = '
1413 SELECT itemnumber,
1414 biblionumber
1415 FROM items
1416 WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1418 my $sth = $dbh->prepare($query);
1419 $sth->execute(@biblionumbers);
1421 my %itemnumbers_of;
1423 while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1424 push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1427 return \%itemnumbers_of;
1430 =head2 get_hostitemnumbers_of
1432 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1434 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1436 Return a reference on a hash where key is a biblionumber and values are
1437 references on array of itemnumbers.
1439 =cut
1442 sub get_hostitemnumbers_of {
1443 my ($biblionumber) = @_;
1444 my $marcrecord = C4::Biblio::GetMarcBiblio($biblionumber);
1446 return unless $marcrecord;
1448 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1450 my $marcflavor = C4::Context->preference('marcflavour');
1451 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1452 $tag = '773';
1453 $biblio_s = '0';
1454 $item_s = '9';
1456 elsif ( $marcflavor eq 'UNIMARC' ) {
1457 $tag = '461';
1458 $biblio_s = '0';
1459 $item_s = '9';
1462 foreach my $hostfield ( $marcrecord->field($tag) ) {
1463 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1464 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1465 my $linkeditemnumber = $hostfield->subfield($item_s);
1466 if ( ! $linkeditemnumber ) {
1467 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1468 next;
1470 my @itemnumbers;
1471 if ( my $itemnumbers =
1472 get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber} )
1474 @itemnumbers = @$itemnumbers;
1476 foreach my $itemnumber (@itemnumbers) {
1477 if ( $itemnumber eq $linkeditemnumber ) {
1478 push( @returnhostitemnumbers, $itemnumber );
1479 last;
1484 return @returnhostitemnumbers;
1488 =head2 GetItemnumberFromBarcode
1490 $result = GetItemnumberFromBarcode($barcode);
1492 =cut
1494 sub GetItemnumberFromBarcode {
1495 my ($barcode) = @_;
1496 my $dbh = C4::Context->dbh;
1498 my $rq =
1499 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1500 $rq->execute($barcode);
1501 my ($result) = $rq->fetchrow;
1502 return ($result);
1505 =head2 GetBarcodeFromItemnumber
1507 $result = GetBarcodeFromItemnumber($itemnumber);
1509 =cut
1511 sub GetBarcodeFromItemnumber {
1512 my ($itemnumber) = @_;
1513 my $dbh = C4::Context->dbh;
1515 my $rq =
1516 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1517 $rq->execute($itemnumber);
1518 my ($result) = $rq->fetchrow;
1519 return ($result);
1522 =head2 GetHiddenItemnumbers
1524 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1526 Given a list of items it checks which should be hidden from the OPAC given
1527 the current configuration. Returns a list of itemnumbers corresponding to
1528 those that should be hidden.
1530 =cut
1532 sub GetHiddenItemnumbers {
1533 my (@items) = @_;
1534 my @resultitems;
1536 my $yaml = C4::Context->preference('OpacHiddenItems');
1537 return () if (! $yaml =~ /\S/ );
1538 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1539 my $hidingrules;
1540 eval {
1541 $hidingrules = YAML::Load($yaml);
1543 if ($@) {
1544 warn "Unable to parse OpacHiddenItems syspref : $@";
1545 return ();
1547 my $dbh = C4::Context->dbh;
1549 # For each item
1550 foreach my $item (@items) {
1552 # We check each rule
1553 foreach my $field (keys %$hidingrules) {
1554 my $val;
1555 if (exists $item->{$field}) {
1556 $val = $item->{$field};
1558 else {
1559 my $query = "SELECT $field from items where itemnumber = ?";
1560 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1562 $val = '' unless defined $val;
1564 # If the results matches the values in the yaml file
1565 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1567 # We add the itemnumber to the list
1568 push @resultitems, $item->{'itemnumber'};
1570 # If at least one rule matched for an item, no need to test the others
1571 last;
1575 return @resultitems;
1578 =head1 LIMITED USE FUNCTIONS
1580 The following functions, while part of the public API,
1581 are not exported. This is generally because they are
1582 meant to be used by only one script for a specific
1583 purpose, and should not be used in any other context
1584 without careful thought.
1586 =cut
1588 =head2 GetMarcItem
1590 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1592 Returns MARC::Record of the item passed in parameter.
1593 This function is meant for use only in C<cataloguing/additem.pl>,
1594 where it is needed to support that script's MARC-like
1595 editor.
1597 =cut
1599 sub GetMarcItem {
1600 my ( $biblionumber, $itemnumber ) = @_;
1602 # GetMarcItem has been revised so that it does the following:
1603 # 1. Gets the item information from the items table.
1604 # 2. Converts it to a MARC field for storage in the bib record.
1606 # The previous behavior was:
1607 # 1. Get the bib record.
1608 # 2. Return the MARC tag corresponding to the item record.
1610 # The difference is that one treats the items row as authoritative,
1611 # while the other treats the MARC representation as authoritative
1612 # under certain circumstances.
1614 my $itemrecord = GetItem($itemnumber);
1616 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1617 # Also, don't emit a subfield if the underlying field is blank.
1620 return Item2Marc($itemrecord,$biblionumber);
1623 sub Item2Marc {
1624 my ($itemrecord,$biblionumber)=@_;
1625 my $mungeditem = {
1626 map {
1627 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1628 } keys %{ $itemrecord }
1630 my $itemmarc = C4::Biblio::TransformKohaToMarc($mungeditem);
1631 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",C4::Biblio::GetFrameworkCode($biblionumber)||'');
1633 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1634 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1635 foreach my $field ($itemmarc->field($itemtag)){
1636 $field->add_subfields(@$unlinked_item_subfields);
1639 return $itemmarc;
1642 =head1 PRIVATE FUNCTIONS AND VARIABLES
1644 The following functions are not meant to be called
1645 directly, but are documented in order to explain
1646 the inner workings of C<C4::Items>.
1648 =cut
1650 =head2 %derived_columns
1652 This hash keeps track of item columns that
1653 are strictly derived from other columns in
1654 the item record and are not meant to be set
1655 independently.
1657 Each key in the hash should be the name of a
1658 column (as named by TransformMarcToKoha). Each
1659 value should be hashref whose keys are the
1660 columns on which the derived column depends. The
1661 hashref should also contain a 'BUILDER' key
1662 that is a reference to a sub that calculates
1663 the derived value.
1665 =cut
1667 my %derived_columns = (
1668 'items.cn_sort' => {
1669 'itemcallnumber' => 1,
1670 'items.cn_source' => 1,
1671 'BUILDER' => \&_calc_items_cn_sort,
1675 =head2 _set_derived_columns_for_add
1677 _set_derived_column_for_add($item);
1679 Given an item hash representing a new item to be added,
1680 calculate any derived columns. Currently the only
1681 such column is C<items.cn_sort>.
1683 =cut
1685 sub _set_derived_columns_for_add {
1686 my $item = shift;
1688 foreach my $column (keys %derived_columns) {
1689 my $builder = $derived_columns{$column}->{'BUILDER'};
1690 my $source_values = {};
1691 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1692 next if $source_column eq 'BUILDER';
1693 $source_values->{$source_column} = $item->{$source_column};
1695 $builder->($item, $source_values);
1699 =head2 _set_derived_columns_for_mod
1701 _set_derived_column_for_mod($item);
1703 Given an item hash representing a new item to be modified.
1704 calculate any derived columns. Currently the only
1705 such column is C<items.cn_sort>.
1707 This routine differs from C<_set_derived_columns_for_add>
1708 in that it needs to handle partial item records. In other
1709 words, the caller of C<ModItem> may have supplied only one
1710 or two columns to be changed, so this function needs to
1711 determine whether any of the columns to be changed affect
1712 any of the derived columns. Also, if a derived column
1713 depends on more than one column, but the caller is not
1714 changing all of then, this routine retrieves the unchanged
1715 values from the database in order to ensure a correct
1716 calculation.
1718 =cut
1720 sub _set_derived_columns_for_mod {
1721 my $item = shift;
1723 foreach my $column (keys %derived_columns) {
1724 my $builder = $derived_columns{$column}->{'BUILDER'};
1725 my $source_values = {};
1726 my %missing_sources = ();
1727 my $must_recalc = 0;
1728 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1729 next if $source_column eq 'BUILDER';
1730 if (exists $item->{$source_column}) {
1731 $must_recalc = 1;
1732 $source_values->{$source_column} = $item->{$source_column};
1733 } else {
1734 $missing_sources{$source_column} = 1;
1737 if ($must_recalc) {
1738 foreach my $source_column (keys %missing_sources) {
1739 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1741 $builder->($item, $source_values);
1746 =head2 _do_column_fixes_for_mod
1748 _do_column_fixes_for_mod($item);
1750 Given an item hashref containing one or more
1751 columns to modify, fix up certain values.
1752 Specifically, set to 0 any passed value
1753 of C<notforloan>, C<damaged>, C<itemlost>, or
1754 C<withdrawn> that is either undefined or
1755 contains the empty string.
1757 =cut
1759 sub _do_column_fixes_for_mod {
1760 my $item = shift;
1762 if (exists $item->{'notforloan'} and
1763 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1764 $item->{'notforloan'} = 0;
1766 if (exists $item->{'damaged'} and
1767 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1768 $item->{'damaged'} = 0;
1770 if (exists $item->{'itemlost'} and
1771 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1772 $item->{'itemlost'} = 0;
1774 if (exists $item->{'withdrawn'} and
1775 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1776 $item->{'withdrawn'} = 0;
1778 if (exists $item->{location}
1779 and $item->{location} ne 'CART'
1780 and $item->{location} ne 'PROC'
1781 and not $item->{permanent_location}
1783 $item->{'permanent_location'} = $item->{'location'};
1785 if (exists $item->{'timestamp'}) {
1786 delete $item->{'timestamp'};
1790 =head2 _get_single_item_column
1792 _get_single_item_column($column, $itemnumber);
1794 Retrieves the value of a single column from an C<items>
1795 row specified by C<$itemnumber>.
1797 =cut
1799 sub _get_single_item_column {
1800 my $column = shift;
1801 my $itemnumber = shift;
1803 my $dbh = C4::Context->dbh;
1804 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1805 $sth->execute($itemnumber);
1806 my ($value) = $sth->fetchrow();
1807 return $value;
1810 =head2 _calc_items_cn_sort
1812 _calc_items_cn_sort($item, $source_values);
1814 Helper routine to calculate C<items.cn_sort>.
1816 =cut
1818 sub _calc_items_cn_sort {
1819 my $item = shift;
1820 my $source_values = shift;
1822 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1825 =head2 _set_defaults_for_add
1827 _set_defaults_for_add($item_hash);
1829 Given an item hash representing an item to be added, set
1830 correct default values for columns whose default value
1831 is not handled by the DBMS. This includes the following
1832 columns:
1834 =over 2
1836 =item *
1838 C<items.dateaccessioned>
1840 =item *
1842 C<items.notforloan>
1844 =item *
1846 C<items.damaged>
1848 =item *
1850 C<items.itemlost>
1852 =item *
1854 C<items.withdrawn>
1856 =back
1858 =cut
1860 sub _set_defaults_for_add {
1861 my $item = shift;
1862 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1863 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1866 =head2 _koha_new_item
1868 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1870 Perform the actual insert into the C<items> table.
1872 =cut
1874 sub _koha_new_item {
1875 my ( $item, $barcode ) = @_;
1876 my $dbh=C4::Context->dbh;
1877 my $error;
1878 $item->{permanent_location} //= $item->{location};
1879 _mod_item_dates( $item );
1880 my $query =
1881 "INSERT INTO items SET
1882 biblionumber = ?,
1883 biblioitemnumber = ?,
1884 barcode = ?,
1885 dateaccessioned = ?,
1886 booksellerid = ?,
1887 homebranch = ?,
1888 price = ?,
1889 replacementprice = ?,
1890 replacementpricedate = ?,
1891 datelastborrowed = ?,
1892 datelastseen = ?,
1893 stack = ?,
1894 notforloan = ?,
1895 damaged = ?,
1896 itemlost = ?,
1897 withdrawn = ?,
1898 itemcallnumber = ?,
1899 coded_location_qualifier = ?,
1900 restricted = ?,
1901 itemnotes = ?,
1902 itemnotes_nonpublic = ?,
1903 holdingbranch = ?,
1904 paidfor = ?,
1905 location = ?,
1906 permanent_location = ?,
1907 onloan = ?,
1908 issues = ?,
1909 renewals = ?,
1910 reserves = ?,
1911 cn_source = ?,
1912 cn_sort = ?,
1913 ccode = ?,
1914 itype = ?,
1915 materials = ?,
1916 uri = ?,
1917 enumchron = ?,
1918 more_subfields_xml = ?,
1919 copynumber = ?,
1920 stocknumber = ?,
1921 new_status = ?
1923 my $sth = $dbh->prepare($query);
1924 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1925 $sth->execute(
1926 $item->{'biblionumber'},
1927 $item->{'biblioitemnumber'},
1928 $barcode,
1929 $item->{'dateaccessioned'},
1930 $item->{'booksellerid'},
1931 $item->{'homebranch'},
1932 $item->{'price'},
1933 $item->{'replacementprice'},
1934 $item->{'replacementpricedate'} || $today,
1935 $item->{datelastborrowed},
1936 $item->{datelastseen} || $today,
1937 $item->{stack},
1938 $item->{'notforloan'},
1939 $item->{'damaged'},
1940 $item->{'itemlost'},
1941 $item->{'withdrawn'},
1942 $item->{'itemcallnumber'},
1943 $item->{'coded_location_qualifier'},
1944 $item->{'restricted'},
1945 $item->{'itemnotes'},
1946 $item->{'itemnotes_nonpublic'},
1947 $item->{'holdingbranch'},
1948 $item->{'paidfor'},
1949 $item->{'location'},
1950 $item->{'permanent_location'},
1951 $item->{'onloan'},
1952 $item->{'issues'},
1953 $item->{'renewals'},
1954 $item->{'reserves'},
1955 $item->{'items.cn_source'},
1956 $item->{'items.cn_sort'},
1957 $item->{'ccode'},
1958 $item->{'itype'},
1959 $item->{'materials'},
1960 $item->{'uri'},
1961 $item->{'enumchron'},
1962 $item->{'more_subfields_xml'},
1963 $item->{'copynumber'},
1964 $item->{'stocknumber'},
1965 $item->{'new_status'},
1968 my $itemnumber;
1969 if ( defined $sth->errstr ) {
1970 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1972 else {
1973 $itemnumber = $dbh->{'mysql_insertid'};
1976 return ( $itemnumber, $error );
1979 =head2 MoveItemFromBiblio
1981 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1983 Moves an item from a biblio to another
1985 Returns undef if the move failed or the biblionumber of the destination record otherwise
1987 =cut
1989 sub MoveItemFromBiblio {
1990 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1991 my $dbh = C4::Context->dbh;
1992 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1993 SELECT biblioitemnumber
1994 FROM biblioitems
1995 WHERE biblionumber = ?
1996 |, undef, $tobiblio );
1997 my $return = $dbh->do(q|
1998 UPDATE items
1999 SET biblioitemnumber = ?,
2000 biblionumber = ?
2001 WHERE itemnumber = ?
2002 AND biblionumber = ?
2003 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2004 if ($return == 1) {
2005 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2006 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2007 # Checking if the item we want to move is in an order
2008 require C4::Acquisition;
2009 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2010 if ($order) {
2011 # Replacing the biblionumber within the order if necessary
2012 $order->{'biblionumber'} = $tobiblio;
2013 C4::Acquisition::ModOrder($order);
2016 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2017 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2018 $dbh->do( qq|
2019 UPDATE $table_name
2020 SET biblionumber = ?
2021 WHERE itemnumber = ?
2022 |, undef, $tobiblio, $itemnumber );
2024 return $tobiblio;
2026 return;
2029 =head2 ItemSafeToDelete
2031 ItemSafeToDelete( $biblionumber, $itemnumber);
2033 Exported function (core API) for checking whether an item record is safe to delete.
2035 returns 1 if the item is safe to delete,
2037 "book_on_loan" if the item is checked out,
2039 "not_same_branch" if the item is blocked by independent branches,
2041 "book_reserved" if the there are holds aganst the item, or
2043 "linked_analytics" if the item has linked analytic records.
2045 =cut
2047 sub ItemSafeToDelete {
2048 my ( $biblionumber, $itemnumber ) = @_;
2049 my $status;
2050 my $dbh = C4::Context->dbh;
2052 my $error;
2054 my $countanalytics = GetAnalyticsCount($itemnumber);
2056 # check that there is no issue on this item before deletion.
2057 my $sth = $dbh->prepare(
2059 SELECT COUNT(*) FROM issues
2060 WHERE itemnumber = ?
2063 $sth->execute($itemnumber);
2064 my ($onloan) = $sth->fetchrow;
2066 my $item = GetItem($itemnumber);
2068 if ($onloan) {
2069 $status = "book_on_loan";
2071 elsif ( defined C4::Context->userenv
2072 and !C4::Context->IsSuperLibrarian()
2073 and C4::Context->preference("IndependentBranches")
2074 and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2076 $status = "not_same_branch";
2078 else {
2079 # check it doesn't have a waiting reserve
2080 $sth = $dbh->prepare(
2082 SELECT COUNT(*) FROM reserves
2083 WHERE (found = 'W' OR found = 'T')
2084 AND itemnumber = ?
2087 $sth->execute($itemnumber);
2088 my ($reserve) = $sth->fetchrow;
2089 if ($reserve) {
2090 $status = "book_reserved";
2092 elsif ( $countanalytics > 0 ) {
2093 $status = "linked_analytics";
2095 else {
2096 $status = 1;
2099 return $status;
2102 =head2 DelItemCheck
2104 DelItemCheck( $biblionumber, $itemnumber);
2106 Exported function (core API) for deleting an item record in Koha if there no current issue.
2108 DelItemCheck wraps ItemSafeToDelete around DelItem.
2110 =cut
2112 sub DelItemCheck {
2113 my ( $biblionumber, $itemnumber ) = @_;
2114 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
2116 if ( $status == 1 ) {
2117 DelItem(
2119 biblionumber => $biblionumber,
2120 itemnumber => $itemnumber
2124 return $status;
2127 =head2 _koha_modify_item
2129 my ($itemnumber,$error) =_koha_modify_item( $item );
2131 Perform the actual update of the C<items> row. Note that this
2132 routine accepts a hashref specifying the columns to update.
2134 =cut
2136 sub _koha_modify_item {
2137 my ( $item ) = @_;
2138 my $dbh=C4::Context->dbh;
2139 my $error;
2141 my $query = "UPDATE items SET ";
2142 my @bind;
2143 _mod_item_dates( $item );
2144 for my $key ( keys %$item ) {
2145 next if ( $key eq 'itemnumber' );
2146 $query.="$key=?,";
2147 push @bind, $item->{$key};
2149 $query =~ s/,$//;
2150 $query .= " WHERE itemnumber=?";
2151 push @bind, $item->{'itemnumber'};
2152 my $sth = $dbh->prepare($query);
2153 $sth->execute(@bind);
2154 if ( $sth->err ) {
2155 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2156 warn $error;
2158 return ($item->{'itemnumber'},$error);
2161 sub _mod_item_dates { # date formatting for date fields in item hash
2162 my ( $item ) = @_;
2163 return if !$item || ref($item) ne 'HASH';
2165 my @keys = grep
2166 { $_ =~ /^onloan$|^date|date$|datetime$/ }
2167 keys %$item;
2168 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
2169 # NOTE: We do not (yet) have items fields ending with datetime
2170 # Fields with _on$ have been handled already
2172 foreach my $key ( @keys ) {
2173 next if !defined $item->{$key}; # skip undefs
2174 my $dt = eval { dt_from_string( $item->{$key} ) };
2175 # eval: dt_from_string will die on us if we pass illegal dates
2177 my $newstr;
2178 if( defined $dt && ref($dt) eq 'DateTime' ) {
2179 if( $key =~ /datetime/ ) {
2180 $newstr = DateTime::Format::MySQL->format_datetime($dt);
2181 } else {
2182 $newstr = DateTime::Format::MySQL->format_date($dt);
2185 $item->{$key} = $newstr; # might be undef to clear garbage
2189 =head2 _koha_delete_item
2191 _koha_delete_item( $itemnum );
2193 Internal function to delete an item record from the koha tables
2195 =cut
2197 sub _koha_delete_item {
2198 my ( $itemnum ) = @_;
2200 my $dbh = C4::Context->dbh;
2201 # save the deleted item to deleteditems table
2202 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2203 $sth->execute($itemnum);
2204 my $data = $sth->fetchrow_hashref();
2206 # There is no item to delete
2207 return 0 unless $data;
2209 my $query = "INSERT INTO deleteditems SET ";
2210 my @bind = ();
2211 foreach my $key ( keys %$data ) {
2212 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2213 $query .= "$key = ?,";
2214 push( @bind, $data->{$key} );
2216 $query =~ s/\,$//;
2217 $sth = $dbh->prepare($query);
2218 $sth->execute(@bind);
2220 # delete from items table
2221 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2222 my $deleted = $sth->execute($itemnum);
2223 return ( $deleted == 1 ) ? 1 : 0;
2226 =head2 _marc_from_item_hash
2228 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2230 Given an item hash representing a complete item record,
2231 create a C<MARC::Record> object containing an embedded
2232 tag representing that item.
2234 The third, optional parameter C<$unlinked_item_subfields> is
2235 an arrayref of subfields (not mapped to C<items> fields per the
2236 framework) to be added to the MARC representation
2237 of the item.
2239 =cut
2241 sub _marc_from_item_hash {
2242 my $item = shift;
2243 my $frameworkcode = shift;
2244 my $unlinked_item_subfields;
2245 if (@_) {
2246 $unlinked_item_subfields = shift;
2249 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2250 # Also, don't emit a subfield if the underlying field is blank.
2251 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2252 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2253 : () } keys %{ $item } };
2255 my $item_marc = MARC::Record->new();
2256 foreach my $item_field ( keys %{$mungeditem} ) {
2257 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
2258 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2259 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2260 foreach my $value (@values){
2261 if ( my $field = $item_marc->field($tag) ) {
2262 $field->add_subfields( $subfield => $value );
2263 } else {
2264 my $add_subfields = [];
2265 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2266 $add_subfields = $unlinked_item_subfields;
2268 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2273 return $item_marc;
2276 =head2 _repack_item_errors
2278 Add an error message hash generated by C<CheckItemPreSave>
2279 to a list of errors.
2281 =cut
2283 sub _repack_item_errors {
2284 my $item_sequence_num = shift;
2285 my $item_ref = shift;
2286 my $error_ref = shift;
2288 my @repacked_errors = ();
2290 foreach my $error_code (sort keys %{ $error_ref }) {
2291 my $repacked_error = {};
2292 $repacked_error->{'item_sequence'} = $item_sequence_num;
2293 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2294 $repacked_error->{'error_code'} = $error_code;
2295 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2296 push @repacked_errors, $repacked_error;
2299 return @repacked_errors;
2302 =head2 _get_unlinked_item_subfields
2304 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2306 =cut
2308 sub _get_unlinked_item_subfields {
2309 my $original_item_marc = shift;
2310 my $frameworkcode = shift;
2312 my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2314 # assume that this record has only one field, and that that
2315 # field contains only the item information
2316 my $subfields = [];
2317 my @fields = $original_item_marc->fields();
2318 if ($#fields > -1) {
2319 my $field = $fields[0];
2320 my $tag = $field->tag();
2321 foreach my $subfield ($field->subfields()) {
2322 if (defined $subfield->[1] and
2323 $subfield->[1] ne '' and
2324 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2325 push @$subfields, $subfield->[0] => $subfield->[1];
2329 return $subfields;
2332 =head2 _get_unlinked_subfields_xml
2334 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2336 =cut
2338 sub _get_unlinked_subfields_xml {
2339 my $unlinked_item_subfields = shift;
2341 my $xml;
2342 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2343 my $marc = MARC::Record->new();
2344 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2345 # used in the framework
2346 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2347 $marc->encoding("UTF-8");
2348 $xml = $marc->as_xml("USMARC");
2351 return $xml;
2354 =head2 _parse_unlinked_item_subfields_from_xml
2356 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2358 =cut
2360 sub _parse_unlinked_item_subfields_from_xml {
2361 my $xml = shift;
2362 require C4::Charset;
2363 return unless defined $xml and $xml ne "";
2364 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2365 my $unlinked_subfields = [];
2366 my @fields = $marc->fields();
2367 if ($#fields > -1) {
2368 foreach my $subfield ($fields[0]->subfields()) {
2369 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2372 return $unlinked_subfields;
2375 =head2 GetAnalyticsCount
2377 $count= &GetAnalyticsCount($itemnumber)
2379 counts Usage of itemnumber in Analytical bibliorecords.
2381 =cut
2383 sub GetAnalyticsCount {
2384 my ($itemnumber) = @_;
2386 ### ZOOM search here
2387 my $query;
2388 $query= "hi=".$itemnumber;
2389 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2390 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2391 return ($result);
2394 =head2 SearchItemsByField
2396 my $items = SearchItemsByField($field, $value);
2398 SearchItemsByField will search for items on a specific given field.
2399 For instance you can search all items with a specific stocknumber like this:
2401 my $items = SearchItemsByField('stocknumber', $stocknumber);
2403 =cut
2405 sub SearchItemsByField {
2406 my ($field, $value) = @_;
2408 my $filters = {
2409 field => $field,
2410 query => $value,
2413 my ($results) = SearchItems($filters);
2414 return $results;
2417 sub _SearchItems_build_where_fragment {
2418 my ($filter) = @_;
2420 my $dbh = C4::Context->dbh;
2422 my $where_fragment;
2423 if (exists($filter->{conjunction})) {
2424 my (@where_strs, @where_args);
2425 foreach my $f (@{ $filter->{filters} }) {
2426 my $fragment = _SearchItems_build_where_fragment($f);
2427 if ($fragment) {
2428 push @where_strs, $fragment->{str};
2429 push @where_args, @{ $fragment->{args} };
2432 my $where_str = '';
2433 if (@where_strs) {
2434 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2435 $where_fragment = {
2436 str => $where_str,
2437 args => \@where_args,
2440 } else {
2441 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2442 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2443 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2444 my @operators = qw(= != > < >= <= like);
2445 my $field = $filter->{field};
2446 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2447 my $op = $filter->{operator};
2448 my $query = $filter->{query};
2450 if (!$op or (0 == grep /^$op$/, @operators)) {
2451 $op = '='; # default operator
2454 my $column;
2455 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2456 my $marcfield = $1;
2457 my $marcsubfield = $2;
2458 my ($kohafield) = $dbh->selectrow_array(q|
2459 SELECT kohafield FROM marc_subfield_structure
2460 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2461 |, undef, $marcfield, $marcsubfield);
2463 if ($kohafield) {
2464 $column = $kohafield;
2465 } else {
2466 # MARC field is not linked to a DB field so we need to use
2467 # ExtractValue on marcxml from biblio_metadata or
2468 # items.more_subfields_xml, depending on the MARC field.
2469 my $xpath;
2470 my $sqlfield;
2471 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2472 if ($marcfield eq $itemfield) {
2473 $sqlfield = 'more_subfields_xml';
2474 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2475 } else {
2476 $sqlfield = 'metadata'; # From biblio_metadata
2477 if ($marcfield < 10) {
2478 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2479 } else {
2480 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2483 $column = "ExtractValue($sqlfield, '$xpath')";
2485 } else {
2486 $column = $field;
2489 if (ref $query eq 'ARRAY') {
2490 if ($op eq '=') {
2491 $op = 'IN';
2492 } elsif ($op eq '!=') {
2493 $op = 'NOT IN';
2495 $where_fragment = {
2496 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2497 args => $query,
2499 } else {
2500 $where_fragment = {
2501 str => "$column $op ?",
2502 args => [ $query ],
2508 return $where_fragment;
2511 =head2 SearchItems
2513 my ($items, $total) = SearchItems($filter, $params);
2515 Perform a search among items
2517 $filter is a reference to a hash which can be a filter, or a combination of filters.
2519 A filter has the following keys:
2521 =over 2
2523 =item * field: the name of a SQL column in table items
2525 =item * query: the value to search in this column
2527 =item * operator: comparison operator. Can be one of = != > < >= <= like
2529 =back
2531 A combination of filters hash the following keys:
2533 =over 2
2535 =item * conjunction: 'AND' or 'OR'
2537 =item * filters: array ref of filters
2539 =back
2541 $params is a reference to a hash that can contain the following parameters:
2543 =over 2
2545 =item * rows: Number of items to return. 0 returns everything (default: 0)
2547 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2548 (default: 1)
2550 =item * sortby: A SQL column name in items table to sort on
2552 =item * sortorder: 'ASC' or 'DESC'
2554 =back
2556 =cut
2558 sub SearchItems {
2559 my ($filter, $params) = @_;
2561 $filter //= {};
2562 $params //= {};
2563 return unless ref $filter eq 'HASH';
2564 return unless ref $params eq 'HASH';
2566 # Default parameters
2567 $params->{rows} ||= 0;
2568 $params->{page} ||= 1;
2569 $params->{sortby} ||= 'itemnumber';
2570 $params->{sortorder} ||= 'ASC';
2572 my ($where_str, @where_args);
2573 my $where_fragment = _SearchItems_build_where_fragment($filter);
2574 if ($where_fragment) {
2575 $where_str = $where_fragment->{str};
2576 @where_args = @{ $where_fragment->{args} };
2579 my $dbh = C4::Context->dbh;
2580 my $query = q{
2581 SELECT SQL_CALC_FOUND_ROWS items.*
2582 FROM items
2583 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2584 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2585 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2586 WHERE 1
2588 if (defined $where_str and $where_str ne '') {
2589 $query .= qq{ AND $where_str };
2592 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2593 push @where_args, C4::Context->preference('marcflavour');
2595 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2596 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2597 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2598 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2599 ? $params->{sortby} : 'itemnumber';
2600 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2601 $query .= qq{ ORDER BY $sortby $sortorder };
2603 my $rows = $params->{rows};
2604 my @limit_args;
2605 if ($rows > 0) {
2606 my $offset = $rows * ($params->{page}-1);
2607 $query .= qq { LIMIT ?, ? };
2608 push @limit_args, $offset, $rows;
2611 my $sth = $dbh->prepare($query);
2612 my $rv = $sth->execute(@where_args, @limit_args);
2614 return unless ($rv);
2615 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2617 return ($sth->fetchall_arrayref({}), $total_rows);
2621 =head1 OTHER FUNCTIONS
2623 =head2 _find_value
2625 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2627 Find the given $subfield in the given $tag in the given
2628 MARC::Record $record. If the subfield is found, returns
2629 the (indicators, value) pair; otherwise, (undef, undef) is
2630 returned.
2632 PROPOSITION :
2633 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2634 I suggest we export it from this module.
2636 =cut
2638 sub _find_value {
2639 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2640 my @result;
2641 my $indicator;
2642 if ( $tagfield < 10 ) {
2643 if ( $record->field($tagfield) ) {
2644 push @result, $record->field($tagfield)->data();
2645 } else {
2646 push @result, "";
2648 } else {
2649 foreach my $field ( $record->field($tagfield) ) {
2650 my @subfields = $field->subfields();
2651 foreach my $subfield (@subfields) {
2652 if ( @$subfield[0] eq $insubfield ) {
2653 push @result, @$subfield[1];
2654 $indicator = $field->indicator(1) . $field->indicator(2);
2659 return ( $indicator, @result );
2663 =head2 PrepareItemrecordDisplay
2665 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2667 Returns a hash with all the fields for Display a given item data in a template
2669 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2671 =cut
2673 sub PrepareItemrecordDisplay {
2675 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2677 my $dbh = C4::Context->dbh;
2678 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2679 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2681 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2682 # a shared data structure. No plugin (including custom ones) should change
2683 # its contents. See also GetMarcStructure.
2684 my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2686 # return nothing if we don't have found an existing framework.
2687 return q{} unless $tagslib;
2688 my $itemrecord;
2689 if ($itemnum) {
2690 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2692 my @loop_data;
2694 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2695 my $query = qq{
2696 SELECT authorised_value,lib FROM authorised_values
2698 $query .= qq{
2699 LEFT JOIN authorised_values_branches ON ( id = av_id )
2700 } if $branch_limit;
2701 $query .= qq{
2702 WHERE category = ?
2704 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2705 $query .= qq{ ORDER BY lib};
2706 my $authorised_values_sth = $dbh->prepare( $query );
2707 foreach my $tag ( sort keys %{$tagslib} ) {
2708 if ( $tag ne '' ) {
2710 # loop through each subfield
2711 my $cntsubf;
2712 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2713 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2714 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2715 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2716 my %subfield_data;
2717 $subfield_data{tag} = $tag;
2718 $subfield_data{subfield} = $subfield;
2719 $subfield_data{countsubfield} = $cntsubf++;
2720 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2721 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2723 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2724 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2725 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2726 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2727 $subfield_data{hidden} = "display:none"
2728 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2729 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2730 my ( $x, $defaultvalue );
2731 if ($itemrecord) {
2732 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2734 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2735 if ( !defined $defaultvalue ) {
2736 $defaultvalue = q||;
2737 } else {
2738 $defaultvalue =~ s/"/&quot;/g;
2741 # search for itemcallnumber if applicable
2742 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2743 && C4::Context->preference('itemcallnumber') ) {
2744 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2745 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2746 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2747 $defaultvalue = $field->subfield($CNsubfield);
2750 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2751 && $defaultvalues
2752 && $defaultvalues->{'callnumber'} ) {
2753 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2754 # if the item record exists, only use default value if the item has no callnumber
2755 $defaultvalue = $defaultvalues->{callnumber};
2756 } elsif ( !$itemrecord and $defaultvalues ) {
2757 # if the item record *doesn't* exists, always use the default value
2758 $defaultvalue = $defaultvalues->{callnumber};
2761 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2762 && $defaultvalues
2763 && $defaultvalues->{'branchcode'} ) {
2764 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2765 $defaultvalue = $defaultvalues->{branchcode};
2768 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2769 && $defaultvalues
2770 && $defaultvalues->{'location'} ) {
2772 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2773 # if the item record exists, only use default value if the item has no locationr
2774 $defaultvalue = $defaultvalues->{location};
2775 } elsif ( !$itemrecord and $defaultvalues ) {
2776 # if the item record *doesn't* exists, always use the default value
2777 $defaultvalue = $defaultvalues->{location};
2780 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2781 my @authorised_values;
2782 my %authorised_lib;
2784 # builds list, depending on authorised value...
2785 #---- branch
2786 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2787 if ( ( C4::Context->preference("IndependentBranches") )
2788 && !C4::Context->IsSuperLibrarian() ) {
2789 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2790 $sth->execute( C4::Context->userenv->{branch} );
2791 push @authorised_values, ""
2792 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2793 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2794 push @authorised_values, $branchcode;
2795 $authorised_lib{$branchcode} = $branchname;
2797 } else {
2798 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2799 $sth->execute;
2800 push @authorised_values, ""
2801 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2802 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2803 push @authorised_values, $branchcode;
2804 $authorised_lib{$branchcode} = $branchname;
2808 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2809 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2810 $defaultvalue = $defaultvalues->{branchcode};
2813 #----- itemtypes
2814 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2815 my $itemtypes = Koha::ItemTypes->search_with_localization;
2816 push @authorised_values, ""
2817 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2818 while ( my $itemtype = $itemtypes->next ) {
2819 push @authorised_values, $itemtype->itemtype;
2820 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2822 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2823 $defaultvalue = $defaultvalues->{'itemtype'};
2826 #---- class_sources
2827 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2828 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2830 my $class_sources = GetClassSources();
2831 my $default_source = C4::Context->preference("DefaultClassificationSource");
2833 foreach my $class_source (sort keys %$class_sources) {
2834 next unless $class_sources->{$class_source}->{'used'} or
2835 ($class_source eq $default_source);
2836 push @authorised_values, $class_source;
2837 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2840 $defaultvalue = $default_source;
2842 #---- "true" authorised value
2843 } else {
2844 $authorised_values_sth->execute(
2845 $tagslib->{$tag}->{$subfield}->{authorised_value},
2846 $branch_limit ? $branch_limit : ()
2848 push @authorised_values, ""
2849 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2850 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2851 push @authorised_values, $value;
2852 $authorised_lib{$value} = $lib;
2855 $subfield_data{marc_value} = {
2856 type => 'select',
2857 values => \@authorised_values,
2858 default => "$defaultvalue",
2859 labels => \%authorised_lib,
2861 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2862 # it is a plugin
2863 require Koha::FrameworkPlugin;
2864 my $plugin = Koha::FrameworkPlugin->new({
2865 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2866 item_style => 1,
2868 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2869 $plugin->build( $pars );
2870 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2871 $defaultvalue = $field->subfield($subfield);
2873 if( !$plugin->errstr ) {
2874 #TODO Move html to template; see report 12176/13397
2875 my $tab= $plugin->noclick? '-1': '';
2876 my $class= $plugin->noclick? ' disabled': '';
2877 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2878 $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;
2879 } else {
2880 warn $plugin->errstr;
2881 $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
2884 elsif ( $tag eq '' ) { # it's an hidden field
2885 $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" />);
2887 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2888 $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" />);
2890 elsif ( length($defaultvalue) > 100
2891 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2892 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2893 or (C4::Context->preference("marcflavour") eq "MARC21" and
2894 500 <= $tag && $tag < 600 )
2896 # oversize field (textarea)
2897 $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");
2898 } else {
2899 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2901 push( @loop_data, \%subfield_data );
2905 my $itemnumber;
2906 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2907 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2909 return {
2910 'itemtagfield' => $itemtagfield,
2911 'itemtagsubfield' => $itemtagsubfield,
2912 'itemnumber' => $itemnumber,
2913 'iteminformation' => \@loop_data
2917 sub ToggleNewStatus {
2918 my ( $params ) = @_;
2919 my @rules = @{ $params->{rules} };
2920 my $report_only = $params->{report_only};
2922 my $dbh = C4::Context->dbh;
2923 my @errors;
2924 my @item_columns = map { "items.$_" } Koha::Items->columns;
2925 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2926 my $report;
2927 for my $rule ( @rules ) {
2928 my $age = $rule->{age};
2929 my $conditions = $rule->{conditions};
2930 my $substitutions = $rule->{substitutions};
2931 my @params;
2933 my $query = q|
2934 SELECT items.biblionumber, items.itemnumber
2935 FROM items
2936 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2937 WHERE 1
2939 for my $condition ( @$conditions ) {
2940 if (
2941 grep {/^$condition->{field}$/} @item_columns
2942 or grep {/^$condition->{field}$/} @biblioitem_columns
2944 if ( $condition->{value} =~ /\|/ ) {
2945 my @values = split /\|/, $condition->{value};
2946 $query .= qq| AND $condition->{field} IN (|
2947 . join( ',', ('?') x scalar @values )
2948 . q|)|;
2949 push @params, @values;
2950 } else {
2951 $query .= qq| AND $condition->{field} = ?|;
2952 push @params, $condition->{value};
2956 if ( defined $age ) {
2957 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2958 push @params, $age;
2960 my $sth = $dbh->prepare($query);
2961 $sth->execute( @params );
2962 while ( my $values = $sth->fetchrow_hashref ) {
2963 my $biblionumber = $values->{biblionumber};
2964 my $itemnumber = $values->{itemnumber};
2965 my $item = C4::Items::GetItem( $itemnumber );
2966 for my $substitution ( @$substitutions ) {
2967 next unless $substitution->{field};
2968 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
2969 unless $report_only;
2970 push @{ $report->{$itemnumber} }, $substitution;
2975 return $report;