Bug 19116: Unit tests
[koha.git] / C4 / Items.pm
blob5a7a56756f4ddd80685fb1a41c70f7a7df8cc748
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 my $linkeditemnumber = $hostfield->subfield($item_s);
1465 my @itemnumbers;
1466 if ( my $itemnumbers =
1467 get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber} )
1469 @itemnumbers = @$itemnumbers;
1471 foreach my $itemnumber (@itemnumbers) {
1472 if ( $itemnumber eq $linkeditemnumber ) {
1473 push( @returnhostitemnumbers, $itemnumber );
1474 last;
1479 return @returnhostitemnumbers;
1483 =head2 GetItemnumberFromBarcode
1485 $result = GetItemnumberFromBarcode($barcode);
1487 =cut
1489 sub GetItemnumberFromBarcode {
1490 my ($barcode) = @_;
1491 my $dbh = C4::Context->dbh;
1493 my $rq =
1494 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1495 $rq->execute($barcode);
1496 my ($result) = $rq->fetchrow;
1497 return ($result);
1500 =head2 GetBarcodeFromItemnumber
1502 $result = GetBarcodeFromItemnumber($itemnumber);
1504 =cut
1506 sub GetBarcodeFromItemnumber {
1507 my ($itemnumber) = @_;
1508 my $dbh = C4::Context->dbh;
1510 my $rq =
1511 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1512 $rq->execute($itemnumber);
1513 my ($result) = $rq->fetchrow;
1514 return ($result);
1517 =head2 GetHiddenItemnumbers
1519 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1521 Given a list of items it checks which should be hidden from the OPAC given
1522 the current configuration. Returns a list of itemnumbers corresponding to
1523 those that should be hidden.
1525 =cut
1527 sub GetHiddenItemnumbers {
1528 my (@items) = @_;
1529 my @resultitems;
1531 my $yaml = C4::Context->preference('OpacHiddenItems');
1532 return () if (! $yaml =~ /\S/ );
1533 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1534 my $hidingrules;
1535 eval {
1536 $hidingrules = YAML::Load($yaml);
1538 if ($@) {
1539 warn "Unable to parse OpacHiddenItems syspref : $@";
1540 return ();
1542 my $dbh = C4::Context->dbh;
1544 # For each item
1545 foreach my $item (@items) {
1547 # We check each rule
1548 foreach my $field (keys %$hidingrules) {
1549 my $val;
1550 if (exists $item->{$field}) {
1551 $val = $item->{$field};
1553 else {
1554 my $query = "SELECT $field from items where itemnumber = ?";
1555 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1557 $val = '' unless defined $val;
1559 # If the results matches the values in the yaml file
1560 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1562 # We add the itemnumber to the list
1563 push @resultitems, $item->{'itemnumber'};
1565 # If at least one rule matched for an item, no need to test the others
1566 last;
1570 return @resultitems;
1573 =head1 LIMITED USE FUNCTIONS
1575 The following functions, while part of the public API,
1576 are not exported. This is generally because they are
1577 meant to be used by only one script for a specific
1578 purpose, and should not be used in any other context
1579 without careful thought.
1581 =cut
1583 =head2 GetMarcItem
1585 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1587 Returns MARC::Record of the item passed in parameter.
1588 This function is meant for use only in C<cataloguing/additem.pl>,
1589 where it is needed to support that script's MARC-like
1590 editor.
1592 =cut
1594 sub GetMarcItem {
1595 my ( $biblionumber, $itemnumber ) = @_;
1597 # GetMarcItem has been revised so that it does the following:
1598 # 1. Gets the item information from the items table.
1599 # 2. Converts it to a MARC field for storage in the bib record.
1601 # The previous behavior was:
1602 # 1. Get the bib record.
1603 # 2. Return the MARC tag corresponding to the item record.
1605 # The difference is that one treats the items row as authoritative,
1606 # while the other treats the MARC representation as authoritative
1607 # under certain circumstances.
1609 my $itemrecord = GetItem($itemnumber);
1611 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1612 # Also, don't emit a subfield if the underlying field is blank.
1615 return Item2Marc($itemrecord,$biblionumber);
1618 sub Item2Marc {
1619 my ($itemrecord,$biblionumber)=@_;
1620 my $mungeditem = {
1621 map {
1622 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1623 } keys %{ $itemrecord }
1625 my $itemmarc = C4::Biblio::TransformKohaToMarc($mungeditem);
1626 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",C4::Biblio::GetFrameworkCode($biblionumber)||'');
1628 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1629 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1630 foreach my $field ($itemmarc->field($itemtag)){
1631 $field->add_subfields(@$unlinked_item_subfields);
1634 return $itemmarc;
1637 =head1 PRIVATE FUNCTIONS AND VARIABLES
1639 The following functions are not meant to be called
1640 directly, but are documented in order to explain
1641 the inner workings of C<C4::Items>.
1643 =cut
1645 =head2 %derived_columns
1647 This hash keeps track of item columns that
1648 are strictly derived from other columns in
1649 the item record and are not meant to be set
1650 independently.
1652 Each key in the hash should be the name of a
1653 column (as named by TransformMarcToKoha). Each
1654 value should be hashref whose keys are the
1655 columns on which the derived column depends. The
1656 hashref should also contain a 'BUILDER' key
1657 that is a reference to a sub that calculates
1658 the derived value.
1660 =cut
1662 my %derived_columns = (
1663 'items.cn_sort' => {
1664 'itemcallnumber' => 1,
1665 'items.cn_source' => 1,
1666 'BUILDER' => \&_calc_items_cn_sort,
1670 =head2 _set_derived_columns_for_add
1672 _set_derived_column_for_add($item);
1674 Given an item hash representing a new item to be added,
1675 calculate any derived columns. Currently the only
1676 such column is C<items.cn_sort>.
1678 =cut
1680 sub _set_derived_columns_for_add {
1681 my $item = shift;
1683 foreach my $column (keys %derived_columns) {
1684 my $builder = $derived_columns{$column}->{'BUILDER'};
1685 my $source_values = {};
1686 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1687 next if $source_column eq 'BUILDER';
1688 $source_values->{$source_column} = $item->{$source_column};
1690 $builder->($item, $source_values);
1694 =head2 _set_derived_columns_for_mod
1696 _set_derived_column_for_mod($item);
1698 Given an item hash representing a new item to be modified.
1699 calculate any derived columns. Currently the only
1700 such column is C<items.cn_sort>.
1702 This routine differs from C<_set_derived_columns_for_add>
1703 in that it needs to handle partial item records. In other
1704 words, the caller of C<ModItem> may have supplied only one
1705 or two columns to be changed, so this function needs to
1706 determine whether any of the columns to be changed affect
1707 any of the derived columns. Also, if a derived column
1708 depends on more than one column, but the caller is not
1709 changing all of then, this routine retrieves the unchanged
1710 values from the database in order to ensure a correct
1711 calculation.
1713 =cut
1715 sub _set_derived_columns_for_mod {
1716 my $item = shift;
1718 foreach my $column (keys %derived_columns) {
1719 my $builder = $derived_columns{$column}->{'BUILDER'};
1720 my $source_values = {};
1721 my %missing_sources = ();
1722 my $must_recalc = 0;
1723 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1724 next if $source_column eq 'BUILDER';
1725 if (exists $item->{$source_column}) {
1726 $must_recalc = 1;
1727 $source_values->{$source_column} = $item->{$source_column};
1728 } else {
1729 $missing_sources{$source_column} = 1;
1732 if ($must_recalc) {
1733 foreach my $source_column (keys %missing_sources) {
1734 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1736 $builder->($item, $source_values);
1741 =head2 _do_column_fixes_for_mod
1743 _do_column_fixes_for_mod($item);
1745 Given an item hashref containing one or more
1746 columns to modify, fix up certain values.
1747 Specifically, set to 0 any passed value
1748 of C<notforloan>, C<damaged>, C<itemlost>, or
1749 C<withdrawn> that is either undefined or
1750 contains the empty string.
1752 =cut
1754 sub _do_column_fixes_for_mod {
1755 my $item = shift;
1757 if (exists $item->{'notforloan'} and
1758 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1759 $item->{'notforloan'} = 0;
1761 if (exists $item->{'damaged'} and
1762 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1763 $item->{'damaged'} = 0;
1765 if (exists $item->{'itemlost'} and
1766 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1767 $item->{'itemlost'} = 0;
1769 if (exists $item->{'withdrawn'} and
1770 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1771 $item->{'withdrawn'} = 0;
1773 if (exists $item->{location}
1774 and $item->{location} ne 'CART'
1775 and $item->{location} ne 'PROC'
1776 and not $item->{permanent_location}
1778 $item->{'permanent_location'} = $item->{'location'};
1780 if (exists $item->{'timestamp'}) {
1781 delete $item->{'timestamp'};
1785 =head2 _get_single_item_column
1787 _get_single_item_column($column, $itemnumber);
1789 Retrieves the value of a single column from an C<items>
1790 row specified by C<$itemnumber>.
1792 =cut
1794 sub _get_single_item_column {
1795 my $column = shift;
1796 my $itemnumber = shift;
1798 my $dbh = C4::Context->dbh;
1799 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1800 $sth->execute($itemnumber);
1801 my ($value) = $sth->fetchrow();
1802 return $value;
1805 =head2 _calc_items_cn_sort
1807 _calc_items_cn_sort($item, $source_values);
1809 Helper routine to calculate C<items.cn_sort>.
1811 =cut
1813 sub _calc_items_cn_sort {
1814 my $item = shift;
1815 my $source_values = shift;
1817 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1820 =head2 _set_defaults_for_add
1822 _set_defaults_for_add($item_hash);
1824 Given an item hash representing an item to be added, set
1825 correct default values for columns whose default value
1826 is not handled by the DBMS. This includes the following
1827 columns:
1829 =over 2
1831 =item *
1833 C<items.dateaccessioned>
1835 =item *
1837 C<items.notforloan>
1839 =item *
1841 C<items.damaged>
1843 =item *
1845 C<items.itemlost>
1847 =item *
1849 C<items.withdrawn>
1851 =back
1853 =cut
1855 sub _set_defaults_for_add {
1856 my $item = shift;
1857 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1858 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1861 =head2 _koha_new_item
1863 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1865 Perform the actual insert into the C<items> table.
1867 =cut
1869 sub _koha_new_item {
1870 my ( $item, $barcode ) = @_;
1871 my $dbh=C4::Context->dbh;
1872 my $error;
1873 $item->{permanent_location} //= $item->{location};
1874 _mod_item_dates( $item );
1875 my $query =
1876 "INSERT INTO items SET
1877 biblionumber = ?,
1878 biblioitemnumber = ?,
1879 barcode = ?,
1880 dateaccessioned = ?,
1881 booksellerid = ?,
1882 homebranch = ?,
1883 price = ?,
1884 replacementprice = ?,
1885 replacementpricedate = ?,
1886 datelastborrowed = ?,
1887 datelastseen = ?,
1888 stack = ?,
1889 notforloan = ?,
1890 damaged = ?,
1891 itemlost = ?,
1892 withdrawn = ?,
1893 itemcallnumber = ?,
1894 coded_location_qualifier = ?,
1895 restricted = ?,
1896 itemnotes = ?,
1897 itemnotes_nonpublic = ?,
1898 holdingbranch = ?,
1899 paidfor = ?,
1900 location = ?,
1901 permanent_location = ?,
1902 onloan = ?,
1903 issues = ?,
1904 renewals = ?,
1905 reserves = ?,
1906 cn_source = ?,
1907 cn_sort = ?,
1908 ccode = ?,
1909 itype = ?,
1910 materials = ?,
1911 uri = ?,
1912 enumchron = ?,
1913 more_subfields_xml = ?,
1914 copynumber = ?,
1915 stocknumber = ?,
1916 new_status = ?
1918 my $sth = $dbh->prepare($query);
1919 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1920 $sth->execute(
1921 $item->{'biblionumber'},
1922 $item->{'biblioitemnumber'},
1923 $barcode,
1924 $item->{'dateaccessioned'},
1925 $item->{'booksellerid'},
1926 $item->{'homebranch'},
1927 $item->{'price'},
1928 $item->{'replacementprice'},
1929 $item->{'replacementpricedate'} || $today,
1930 $item->{datelastborrowed},
1931 $item->{datelastseen} || $today,
1932 $item->{stack},
1933 $item->{'notforloan'},
1934 $item->{'damaged'},
1935 $item->{'itemlost'},
1936 $item->{'withdrawn'},
1937 $item->{'itemcallnumber'},
1938 $item->{'coded_location_qualifier'},
1939 $item->{'restricted'},
1940 $item->{'itemnotes'},
1941 $item->{'itemnotes_nonpublic'},
1942 $item->{'holdingbranch'},
1943 $item->{'paidfor'},
1944 $item->{'location'},
1945 $item->{'permanent_location'},
1946 $item->{'onloan'},
1947 $item->{'issues'},
1948 $item->{'renewals'},
1949 $item->{'reserves'},
1950 $item->{'items.cn_source'},
1951 $item->{'items.cn_sort'},
1952 $item->{'ccode'},
1953 $item->{'itype'},
1954 $item->{'materials'},
1955 $item->{'uri'},
1956 $item->{'enumchron'},
1957 $item->{'more_subfields_xml'},
1958 $item->{'copynumber'},
1959 $item->{'stocknumber'},
1960 $item->{'new_status'},
1963 my $itemnumber;
1964 if ( defined $sth->errstr ) {
1965 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1967 else {
1968 $itemnumber = $dbh->{'mysql_insertid'};
1971 return ( $itemnumber, $error );
1974 =head2 MoveItemFromBiblio
1976 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1978 Moves an item from a biblio to another
1980 Returns undef if the move failed or the biblionumber of the destination record otherwise
1982 =cut
1984 sub MoveItemFromBiblio {
1985 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1986 my $dbh = C4::Context->dbh;
1987 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1988 SELECT biblioitemnumber
1989 FROM biblioitems
1990 WHERE biblionumber = ?
1991 |, undef, $tobiblio );
1992 my $return = $dbh->do(q|
1993 UPDATE items
1994 SET biblioitemnumber = ?,
1995 biblionumber = ?
1996 WHERE itemnumber = ?
1997 AND biblionumber = ?
1998 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1999 if ($return == 1) {
2000 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2001 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2002 # Checking if the item we want to move is in an order
2003 require C4::Acquisition;
2004 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2005 if ($order) {
2006 # Replacing the biblionumber within the order if necessary
2007 $order->{'biblionumber'} = $tobiblio;
2008 C4::Acquisition::ModOrder($order);
2011 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2012 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2013 $dbh->do( qq|
2014 UPDATE $table_name
2015 SET biblionumber = ?
2016 WHERE itemnumber = ?
2017 |, undef, $tobiblio, $itemnumber );
2019 return $tobiblio;
2021 return;
2024 =head2 ItemSafeToDelete
2026 ItemSafeToDelete( $biblionumber, $itemnumber);
2028 Exported function (core API) for checking whether an item record is safe to delete.
2030 returns 1 if the item is safe to delete,
2032 "book_on_loan" if the item is checked out,
2034 "not_same_branch" if the item is blocked by independent branches,
2036 "book_reserved" if the there are holds aganst the item, or
2038 "linked_analytics" if the item has linked analytic records.
2040 =cut
2042 sub ItemSafeToDelete {
2043 my ( $biblionumber, $itemnumber ) = @_;
2044 my $status;
2045 my $dbh = C4::Context->dbh;
2047 my $error;
2049 my $countanalytics = GetAnalyticsCount($itemnumber);
2051 # check that there is no issue on this item before deletion.
2052 my $sth = $dbh->prepare(
2054 SELECT COUNT(*) FROM issues
2055 WHERE itemnumber = ?
2058 $sth->execute($itemnumber);
2059 my ($onloan) = $sth->fetchrow;
2061 my $item = GetItem($itemnumber);
2063 if ($onloan) {
2064 $status = "book_on_loan";
2066 elsif ( defined C4::Context->userenv
2067 and !C4::Context->IsSuperLibrarian()
2068 and C4::Context->preference("IndependentBranches")
2069 and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2071 $status = "not_same_branch";
2073 else {
2074 # check it doesn't have a waiting reserve
2075 $sth = $dbh->prepare(
2077 SELECT COUNT(*) FROM reserves
2078 WHERE (found = 'W' OR found = 'T')
2079 AND itemnumber = ?
2082 $sth->execute($itemnumber);
2083 my ($reserve) = $sth->fetchrow;
2084 if ($reserve) {
2085 $status = "book_reserved";
2087 elsif ( $countanalytics > 0 ) {
2088 $status = "linked_analytics";
2090 else {
2091 $status = 1;
2094 return $status;
2097 =head2 DelItemCheck
2099 DelItemCheck( $biblionumber, $itemnumber);
2101 Exported function (core API) for deleting an item record in Koha if there no current issue.
2103 DelItemCheck wraps ItemSafeToDelete around DelItem.
2105 =cut
2107 sub DelItemCheck {
2108 my ( $biblionumber, $itemnumber ) = @_;
2109 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
2111 if ( $status == 1 ) {
2112 DelItem(
2114 biblionumber => $biblionumber,
2115 itemnumber => $itemnumber
2119 return $status;
2122 =head2 _koha_modify_item
2124 my ($itemnumber,$error) =_koha_modify_item( $item );
2126 Perform the actual update of the C<items> row. Note that this
2127 routine accepts a hashref specifying the columns to update.
2129 =cut
2131 sub _koha_modify_item {
2132 my ( $item ) = @_;
2133 my $dbh=C4::Context->dbh;
2134 my $error;
2136 my $query = "UPDATE items SET ";
2137 my @bind;
2138 _mod_item_dates( $item );
2139 for my $key ( keys %$item ) {
2140 next if ( $key eq 'itemnumber' );
2141 $query.="$key=?,";
2142 push @bind, $item->{$key};
2144 $query =~ s/,$//;
2145 $query .= " WHERE itemnumber=?";
2146 push @bind, $item->{'itemnumber'};
2147 my $sth = $dbh->prepare($query);
2148 $sth->execute(@bind);
2149 if ( $sth->err ) {
2150 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2151 warn $error;
2153 return ($item->{'itemnumber'},$error);
2156 sub _mod_item_dates { # date formatting for date fields in item hash
2157 my ( $item ) = @_;
2158 return if !$item || ref($item) ne 'HASH';
2160 my @keys = grep
2161 { $_ =~ /^onloan$|^date|date$|datetime$/ }
2162 keys %$item;
2163 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
2164 # NOTE: We do not (yet) have items fields ending with datetime
2165 # Fields with _on$ have been handled already
2167 foreach my $key ( @keys ) {
2168 next if !defined $item->{$key}; # skip undefs
2169 my $dt = eval { dt_from_string( $item->{$key} ) };
2170 # eval: dt_from_string will die on us if we pass illegal dates
2172 my $newstr;
2173 if( defined $dt && ref($dt) eq 'DateTime' ) {
2174 if( $key =~ /datetime/ ) {
2175 $newstr = DateTime::Format::MySQL->format_datetime($dt);
2176 } else {
2177 $newstr = DateTime::Format::MySQL->format_date($dt);
2180 $item->{$key} = $newstr; # might be undef to clear garbage
2184 =head2 _koha_delete_item
2186 _koha_delete_item( $itemnum );
2188 Internal function to delete an item record from the koha tables
2190 =cut
2192 sub _koha_delete_item {
2193 my ( $itemnum ) = @_;
2195 my $dbh = C4::Context->dbh;
2196 # save the deleted item to deleteditems table
2197 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2198 $sth->execute($itemnum);
2199 my $data = $sth->fetchrow_hashref();
2201 # There is no item to delete
2202 return 0 unless $data;
2204 my $query = "INSERT INTO deleteditems SET ";
2205 my @bind = ();
2206 foreach my $key ( keys %$data ) {
2207 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2208 $query .= "$key = ?,";
2209 push( @bind, $data->{$key} );
2211 $query =~ s/\,$//;
2212 $sth = $dbh->prepare($query);
2213 $sth->execute(@bind);
2215 # delete from items table
2216 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2217 my $deleted = $sth->execute($itemnum);
2218 return ( $deleted == 1 ) ? 1 : 0;
2221 =head2 _marc_from_item_hash
2223 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2225 Given an item hash representing a complete item record,
2226 create a C<MARC::Record> object containing an embedded
2227 tag representing that item.
2229 The third, optional parameter C<$unlinked_item_subfields> is
2230 an arrayref of subfields (not mapped to C<items> fields per the
2231 framework) to be added to the MARC representation
2232 of the item.
2234 =cut
2236 sub _marc_from_item_hash {
2237 my $item = shift;
2238 my $frameworkcode = shift;
2239 my $unlinked_item_subfields;
2240 if (@_) {
2241 $unlinked_item_subfields = shift;
2244 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2245 # Also, don't emit a subfield if the underlying field is blank.
2246 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2247 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2248 : () } keys %{ $item } };
2250 my $item_marc = MARC::Record->new();
2251 foreach my $item_field ( keys %{$mungeditem} ) {
2252 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
2253 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2254 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2255 foreach my $value (@values){
2256 if ( my $field = $item_marc->field($tag) ) {
2257 $field->add_subfields( $subfield => $value );
2258 } else {
2259 my $add_subfields = [];
2260 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2261 $add_subfields = $unlinked_item_subfields;
2263 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2268 return $item_marc;
2271 =head2 _repack_item_errors
2273 Add an error message hash generated by C<CheckItemPreSave>
2274 to a list of errors.
2276 =cut
2278 sub _repack_item_errors {
2279 my $item_sequence_num = shift;
2280 my $item_ref = shift;
2281 my $error_ref = shift;
2283 my @repacked_errors = ();
2285 foreach my $error_code (sort keys %{ $error_ref }) {
2286 my $repacked_error = {};
2287 $repacked_error->{'item_sequence'} = $item_sequence_num;
2288 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2289 $repacked_error->{'error_code'} = $error_code;
2290 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2291 push @repacked_errors, $repacked_error;
2294 return @repacked_errors;
2297 =head2 _get_unlinked_item_subfields
2299 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2301 =cut
2303 sub _get_unlinked_item_subfields {
2304 my $original_item_marc = shift;
2305 my $frameworkcode = shift;
2307 my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2309 # assume that this record has only one field, and that that
2310 # field contains only the item information
2311 my $subfields = [];
2312 my @fields = $original_item_marc->fields();
2313 if ($#fields > -1) {
2314 my $field = $fields[0];
2315 my $tag = $field->tag();
2316 foreach my $subfield ($field->subfields()) {
2317 if (defined $subfield->[1] and
2318 $subfield->[1] ne '' and
2319 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2320 push @$subfields, $subfield->[0] => $subfield->[1];
2324 return $subfields;
2327 =head2 _get_unlinked_subfields_xml
2329 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2331 =cut
2333 sub _get_unlinked_subfields_xml {
2334 my $unlinked_item_subfields = shift;
2336 my $xml;
2337 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2338 my $marc = MARC::Record->new();
2339 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2340 # used in the framework
2341 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2342 $marc->encoding("UTF-8");
2343 $xml = $marc->as_xml("USMARC");
2346 return $xml;
2349 =head2 _parse_unlinked_item_subfields_from_xml
2351 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2353 =cut
2355 sub _parse_unlinked_item_subfields_from_xml {
2356 my $xml = shift;
2357 require C4::Charset;
2358 return unless defined $xml and $xml ne "";
2359 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2360 my $unlinked_subfields = [];
2361 my @fields = $marc->fields();
2362 if ($#fields > -1) {
2363 foreach my $subfield ($fields[0]->subfields()) {
2364 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2367 return $unlinked_subfields;
2370 =head2 GetAnalyticsCount
2372 $count= &GetAnalyticsCount($itemnumber)
2374 counts Usage of itemnumber in Analytical bibliorecords.
2376 =cut
2378 sub GetAnalyticsCount {
2379 my ($itemnumber) = @_;
2381 ### ZOOM search here
2382 my $query;
2383 $query= "hi=".$itemnumber;
2384 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2385 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2386 return ($result);
2389 =head2 SearchItemsByField
2391 my $items = SearchItemsByField($field, $value);
2393 SearchItemsByField will search for items on a specific given field.
2394 For instance you can search all items with a specific stocknumber like this:
2396 my $items = SearchItemsByField('stocknumber', $stocknumber);
2398 =cut
2400 sub SearchItemsByField {
2401 my ($field, $value) = @_;
2403 my $filters = {
2404 field => $field,
2405 query => $value,
2408 my ($results) = SearchItems($filters);
2409 return $results;
2412 sub _SearchItems_build_where_fragment {
2413 my ($filter) = @_;
2415 my $dbh = C4::Context->dbh;
2417 my $where_fragment;
2418 if (exists($filter->{conjunction})) {
2419 my (@where_strs, @where_args);
2420 foreach my $f (@{ $filter->{filters} }) {
2421 my $fragment = _SearchItems_build_where_fragment($f);
2422 if ($fragment) {
2423 push @where_strs, $fragment->{str};
2424 push @where_args, @{ $fragment->{args} };
2427 my $where_str = '';
2428 if (@where_strs) {
2429 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2430 $where_fragment = {
2431 str => $where_str,
2432 args => \@where_args,
2435 } else {
2436 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2437 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2438 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2439 my @operators = qw(= != > < >= <= like);
2440 my $field = $filter->{field};
2441 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2442 my $op = $filter->{operator};
2443 my $query = $filter->{query};
2445 if (!$op or (0 == grep /^$op$/, @operators)) {
2446 $op = '='; # default operator
2449 my $column;
2450 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2451 my $marcfield = $1;
2452 my $marcsubfield = $2;
2453 my ($kohafield) = $dbh->selectrow_array(q|
2454 SELECT kohafield FROM marc_subfield_structure
2455 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2456 |, undef, $marcfield, $marcsubfield);
2458 if ($kohafield) {
2459 $column = $kohafield;
2460 } else {
2461 # MARC field is not linked to a DB field so we need to use
2462 # ExtractValue on marcxml from biblio_metadata or
2463 # items.more_subfields_xml, depending on the MARC field.
2464 my $xpath;
2465 my $sqlfield;
2466 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
2467 if ($marcfield eq $itemfield) {
2468 $sqlfield = 'more_subfields_xml';
2469 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2470 } else {
2471 $sqlfield = 'metadata'; # From biblio_metadata
2472 if ($marcfield < 10) {
2473 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2474 } else {
2475 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2478 $column = "ExtractValue($sqlfield, '$xpath')";
2480 } else {
2481 $column = $field;
2484 if (ref $query eq 'ARRAY') {
2485 if ($op eq '=') {
2486 $op = 'IN';
2487 } elsif ($op eq '!=') {
2488 $op = 'NOT IN';
2490 $where_fragment = {
2491 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2492 args => $query,
2494 } else {
2495 $where_fragment = {
2496 str => "$column $op ?",
2497 args => [ $query ],
2503 return $where_fragment;
2506 =head2 SearchItems
2508 my ($items, $total) = SearchItems($filter, $params);
2510 Perform a search among items
2512 $filter is a reference to a hash which can be a filter, or a combination of filters.
2514 A filter has the following keys:
2516 =over 2
2518 =item * field: the name of a SQL column in table items
2520 =item * query: the value to search in this column
2522 =item * operator: comparison operator. Can be one of = != > < >= <= like
2524 =back
2526 A combination of filters hash the following keys:
2528 =over 2
2530 =item * conjunction: 'AND' or 'OR'
2532 =item * filters: array ref of filters
2534 =back
2536 $params is a reference to a hash that can contain the following parameters:
2538 =over 2
2540 =item * rows: Number of items to return. 0 returns everything (default: 0)
2542 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2543 (default: 1)
2545 =item * sortby: A SQL column name in items table to sort on
2547 =item * sortorder: 'ASC' or 'DESC'
2549 =back
2551 =cut
2553 sub SearchItems {
2554 my ($filter, $params) = @_;
2556 $filter //= {};
2557 $params //= {};
2558 return unless ref $filter eq 'HASH';
2559 return unless ref $params eq 'HASH';
2561 # Default parameters
2562 $params->{rows} ||= 0;
2563 $params->{page} ||= 1;
2564 $params->{sortby} ||= 'itemnumber';
2565 $params->{sortorder} ||= 'ASC';
2567 my ($where_str, @where_args);
2568 my $where_fragment = _SearchItems_build_where_fragment($filter);
2569 if ($where_fragment) {
2570 $where_str = $where_fragment->{str};
2571 @where_args = @{ $where_fragment->{args} };
2574 my $dbh = C4::Context->dbh;
2575 my $query = q{
2576 SELECT SQL_CALC_FOUND_ROWS items.*
2577 FROM items
2578 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2579 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2580 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2581 WHERE 1
2583 if (defined $where_str and $where_str ne '') {
2584 $query .= qq{ AND $where_str };
2587 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2588 push @where_args, C4::Context->preference('marcflavour');
2590 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2591 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2592 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2593 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2594 ? $params->{sortby} : 'itemnumber';
2595 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2596 $query .= qq{ ORDER BY $sortby $sortorder };
2598 my $rows = $params->{rows};
2599 my @limit_args;
2600 if ($rows > 0) {
2601 my $offset = $rows * ($params->{page}-1);
2602 $query .= qq { LIMIT ?, ? };
2603 push @limit_args, $offset, $rows;
2606 my $sth = $dbh->prepare($query);
2607 my $rv = $sth->execute(@where_args, @limit_args);
2609 return unless ($rv);
2610 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2612 return ($sth->fetchall_arrayref({}), $total_rows);
2616 =head1 OTHER FUNCTIONS
2618 =head2 _find_value
2620 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2622 Find the given $subfield in the given $tag in the given
2623 MARC::Record $record. If the subfield is found, returns
2624 the (indicators, value) pair; otherwise, (undef, undef) is
2625 returned.
2627 PROPOSITION :
2628 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2629 I suggest we export it from this module.
2631 =cut
2633 sub _find_value {
2634 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2635 my @result;
2636 my $indicator;
2637 if ( $tagfield < 10 ) {
2638 if ( $record->field($tagfield) ) {
2639 push @result, $record->field($tagfield)->data();
2640 } else {
2641 push @result, "";
2643 } else {
2644 foreach my $field ( $record->field($tagfield) ) {
2645 my @subfields = $field->subfields();
2646 foreach my $subfield (@subfields) {
2647 if ( @$subfield[0] eq $insubfield ) {
2648 push @result, @$subfield[1];
2649 $indicator = $field->indicator(1) . $field->indicator(2);
2654 return ( $indicator, @result );
2658 =head2 PrepareItemrecordDisplay
2660 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2662 Returns a hash with all the fields for Display a given item data in a template
2664 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2666 =cut
2668 sub PrepareItemrecordDisplay {
2670 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2672 my $dbh = C4::Context->dbh;
2673 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2674 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2676 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2677 # a shared data structure. No plugin (including custom ones) should change
2678 # its contents. See also GetMarcStructure.
2679 my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2681 # return nothing if we don't have found an existing framework.
2682 return q{} unless $tagslib;
2683 my $itemrecord;
2684 if ($itemnum) {
2685 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2687 my @loop_data;
2689 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2690 my $query = qq{
2691 SELECT authorised_value,lib FROM authorised_values
2693 $query .= qq{
2694 LEFT JOIN authorised_values_branches ON ( id = av_id )
2695 } if $branch_limit;
2696 $query .= qq{
2697 WHERE category = ?
2699 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2700 $query .= qq{ ORDER BY lib};
2701 my $authorised_values_sth = $dbh->prepare( $query );
2702 foreach my $tag ( sort keys %{$tagslib} ) {
2703 if ( $tag ne '' ) {
2705 # loop through each subfield
2706 my $cntsubf;
2707 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2708 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2709 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2710 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2711 my %subfield_data;
2712 $subfield_data{tag} = $tag;
2713 $subfield_data{subfield} = $subfield;
2714 $subfield_data{countsubfield} = $cntsubf++;
2715 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2716 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2718 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2719 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2720 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2721 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2722 $subfield_data{hidden} = "display:none"
2723 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2724 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2725 my ( $x, $defaultvalue );
2726 if ($itemrecord) {
2727 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2729 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2730 if ( !defined $defaultvalue ) {
2731 $defaultvalue = q||;
2732 } else {
2733 $defaultvalue =~ s/"/&quot;/g;
2736 # search for itemcallnumber if applicable
2737 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2738 && C4::Context->preference('itemcallnumber') ) {
2739 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2740 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2741 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2742 $defaultvalue = $field->subfield($CNsubfield);
2745 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2746 && $defaultvalues
2747 && $defaultvalues->{'callnumber'} ) {
2748 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2749 # if the item record exists, only use default value if the item has no callnumber
2750 $defaultvalue = $defaultvalues->{callnumber};
2751 } elsif ( !$itemrecord and $defaultvalues ) {
2752 # if the item record *doesn't* exists, always use the default value
2753 $defaultvalue = $defaultvalues->{callnumber};
2756 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2757 && $defaultvalues
2758 && $defaultvalues->{'branchcode'} ) {
2759 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2760 $defaultvalue = $defaultvalues->{branchcode};
2763 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2764 && $defaultvalues
2765 && $defaultvalues->{'location'} ) {
2767 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2768 # if the item record exists, only use default value if the item has no locationr
2769 $defaultvalue = $defaultvalues->{location};
2770 } elsif ( !$itemrecord and $defaultvalues ) {
2771 # if the item record *doesn't* exists, always use the default value
2772 $defaultvalue = $defaultvalues->{location};
2775 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2776 my @authorised_values;
2777 my %authorised_lib;
2779 # builds list, depending on authorised value...
2780 #---- branch
2781 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2782 if ( ( C4::Context->preference("IndependentBranches") )
2783 && !C4::Context->IsSuperLibrarian() ) {
2784 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2785 $sth->execute( C4::Context->userenv->{branch} );
2786 push @authorised_values, ""
2787 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2788 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2789 push @authorised_values, $branchcode;
2790 $authorised_lib{$branchcode} = $branchname;
2792 } else {
2793 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2794 $sth->execute;
2795 push @authorised_values, ""
2796 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2797 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2798 push @authorised_values, $branchcode;
2799 $authorised_lib{$branchcode} = $branchname;
2803 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2804 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2805 $defaultvalue = $defaultvalues->{branchcode};
2808 #----- itemtypes
2809 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2810 my $itemtypes = Koha::ItemTypes->search_with_localization;
2811 push @authorised_values, ""
2812 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2813 while ( my $itemtype = $itemtypes->next ) {
2814 push @authorised_values, $itemtype->itemtype;
2815 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2817 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2818 $defaultvalue = $defaultvalues->{'itemtype'};
2821 #---- class_sources
2822 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2823 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2825 my $class_sources = GetClassSources();
2826 my $default_source = C4::Context->preference("DefaultClassificationSource");
2828 foreach my $class_source (sort keys %$class_sources) {
2829 next unless $class_sources->{$class_source}->{'used'} or
2830 ($class_source eq $default_source);
2831 push @authorised_values, $class_source;
2832 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2835 $defaultvalue = $default_source;
2837 #---- "true" authorised value
2838 } else {
2839 $authorised_values_sth->execute(
2840 $tagslib->{$tag}->{$subfield}->{authorised_value},
2841 $branch_limit ? $branch_limit : ()
2843 push @authorised_values, ""
2844 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2845 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2846 push @authorised_values, $value;
2847 $authorised_lib{$value} = $lib;
2850 $subfield_data{marc_value} = {
2851 type => 'select',
2852 values => \@authorised_values,
2853 default => "$defaultvalue",
2854 labels => \%authorised_lib,
2856 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2857 # it is a plugin
2858 require Koha::FrameworkPlugin;
2859 my $plugin = Koha::FrameworkPlugin->new({
2860 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2861 item_style => 1,
2863 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2864 $plugin->build( $pars );
2865 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2866 $defaultvalue = $field->subfield($subfield);
2868 if( !$plugin->errstr ) {
2869 #TODO Move html to template; see report 12176/13397
2870 my $tab= $plugin->noclick? '-1': '';
2871 my $class= $plugin->noclick? ' disabled': '';
2872 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2873 $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;
2874 } else {
2875 warn $plugin->errstr;
2876 $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
2879 elsif ( $tag eq '' ) { # it's an hidden field
2880 $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" />);
2882 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2883 $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" />);
2885 elsif ( length($defaultvalue) > 100
2886 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2887 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2888 or (C4::Context->preference("marcflavour") eq "MARC21" and
2889 500 <= $tag && $tag < 600 )
2891 # oversize field (textarea)
2892 $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");
2893 } else {
2894 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2896 push( @loop_data, \%subfield_data );
2900 my $itemnumber;
2901 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2902 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2904 return {
2905 'itemtagfield' => $itemtagfield,
2906 'itemtagsubfield' => $itemtagsubfield,
2907 'itemnumber' => $itemnumber,
2908 'iteminformation' => \@loop_data
2912 sub ToggleNewStatus {
2913 my ( $params ) = @_;
2914 my @rules = @{ $params->{rules} };
2915 my $report_only = $params->{report_only};
2917 my $dbh = C4::Context->dbh;
2918 my @errors;
2919 my @item_columns = map { "items.$_" } Koha::Items->columns;
2920 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2921 my $report;
2922 for my $rule ( @rules ) {
2923 my $age = $rule->{age};
2924 my $conditions = $rule->{conditions};
2925 my $substitutions = $rule->{substitutions};
2926 my @params;
2928 my $query = q|
2929 SELECT items.biblionumber, items.itemnumber
2930 FROM items
2931 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2932 WHERE 1
2934 for my $condition ( @$conditions ) {
2935 if (
2936 grep {/^$condition->{field}$/} @item_columns
2937 or grep {/^$condition->{field}$/} @biblioitem_columns
2939 if ( $condition->{value} =~ /\|/ ) {
2940 my @values = split /\|/, $condition->{value};
2941 $query .= qq| AND $condition->{field} IN (|
2942 . join( ',', ('?') x scalar @values )
2943 . q|)|;
2944 push @params, @values;
2945 } else {
2946 $query .= qq| AND $condition->{field} = ?|;
2947 push @params, $condition->{value};
2951 if ( defined $age ) {
2952 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2953 push @params, $age;
2955 my $sth = $dbh->prepare($query);
2956 $sth->execute( @params );
2957 while ( my $values = $sth->fetchrow_hashref ) {
2958 my $biblionumber = $values->{biblionumber};
2959 my $itemnumber = $values->{itemnumber};
2960 my $item = C4::Items::GetItem( $itemnumber );
2961 for my $substitution ( @$substitutions ) {
2962 next unless $substitution->{field};
2963 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
2964 unless $report_only;
2965 push @{ $report->{$itemnumber} }, $substitution;
2970 return $report;