Bug 16400: Restore the previous stack order behavior
[koha.git] / C4 / Items.pm
blobc870655dd3a0836e1b4ac1e716f527b8ebccb21f
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
37 use Koha::DateUtils qw/dt_from_string/;
38 use Koha::Database;
40 use Koha::Database;
41 use Koha::SearchEngine;
42 use Koha::SearchEngine::Search;
44 use vars qw(@ISA @EXPORT);
46 BEGIN {
48 require Exporter;
49 @ISA = qw( Exporter );
51 # function exports
52 @EXPORT = qw(
53 GetItem
54 AddItemFromMarc
55 AddItem
56 AddItemBatchFromMarc
57 ModItemFromMarc
58 Item2Marc
59 ModItem
60 ModDateLastSeen
61 ModItemTransfer
62 DelItem
64 CheckItemPreSave
66 GetItemStatus
67 GetItemLocation
68 GetLostItems
69 GetItemsForInventory
70 GetItemsCount
71 GetItemInfosOf
72 GetItemsByBiblioitemnumber
73 GetItemsInfo
74 GetItemsLocationInfo
75 GetHostItemsInfo
76 GetItemnumbersForBiblio
77 get_itemnumbers_of
78 get_hostitemnumbers_of
79 GetItemnumberFromBarcode
80 GetBarcodeFromItemnumber
81 GetHiddenItemnumbers
82 DelItemCheck
83 MoveItemFromBiblio
84 GetLatestAcquisitions
86 CartToShelf
87 ShelfToCart
89 GetAnalyticsCount
90 GetItemHolds
92 SearchItemsByField
93 SearchItems
95 PrepareItemrecordDisplay
100 =head1 NAME
102 C4::Items - item management functions
104 =head1 DESCRIPTION
106 This module contains an API for manipulating item
107 records in Koha, and is used by cataloguing, circulation,
108 acquisitions, and serials management.
110 A Koha item record is stored in two places: the
111 items table and embedded in a MARC tag in the XML
112 version of the associated bib record in C<biblioitems.marcxml>.
113 This is done to allow the item information to be readily
114 indexed (e.g., by Zebra), but means that each item
115 modification transaction must keep the items table
116 and the MARC XML in sync at all times.
118 Consequently, all code that creates, modifies, or deletes
119 item records B<must> use an appropriate function from
120 C<C4::Items>. If no existing function is suitable, it is
121 better to add one to C<C4::Items> than to use add
122 one-off SQL statements to add or modify items.
124 The items table will be considered authoritative. In other
125 words, if there is ever a discrepancy between the items
126 table and the MARC XML, the items table should be considered
127 accurate.
129 =head1 HISTORICAL NOTE
131 Most of the functions in C<C4::Items> were originally in
132 the C<C4::Biblio> module.
134 =head1 CORE EXPORTED FUNCTIONS
136 The following functions are meant for use by users
137 of C<C4::Items>
139 =cut
141 =head2 GetItem
143 $item = GetItem($itemnumber,$barcode,$serial);
145 Return item information, for a given itemnumber or barcode.
146 The return value is a hashref mapping item column
147 names to values. If C<$serial> is true, include serial publication data.
149 =cut
151 sub GetItem {
152 my ($itemnumber,$barcode, $serial) = @_;
153 my $dbh = C4::Context->dbh;
154 my $data;
156 if ($itemnumber) {
157 my $sth = $dbh->prepare("
158 SELECT * FROM items
159 WHERE itemnumber = ?");
160 $sth->execute($itemnumber);
161 $data = $sth->fetchrow_hashref;
162 } else {
163 my $sth = $dbh->prepare("
164 SELECT * FROM items
165 WHERE barcode = ?"
167 $sth->execute($barcode);
168 $data = $sth->fetchrow_hashref;
171 return unless ( $data );
173 if ( $serial) {
174 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
175 $ssth->execute($data->{'itemnumber'}) ;
176 ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
178 #if we don't have an items.itype, use biblioitems.itemtype.
179 # FIXME this should respect the itypes systempreference
180 # if (C4::Context->preference('item-level_itypes')) {
181 if( ! $data->{'itype'} ) {
182 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
183 $sth->execute($data->{'biblionumber'});
184 ($data->{'itype'}) = $sth->fetchrow_array;
186 return $data;
187 } # sub GetItem
189 =head2 CartToShelf
191 CartToShelf($itemnumber);
193 Set the current shelving location of the item record
194 to its stored permanent shelving location. This is
195 primarily used to indicate when an item whose current
196 location is a special processing ('PROC') or shelving cart
197 ('CART') location is back in the stacks.
199 =cut
201 sub CartToShelf {
202 my ( $itemnumber ) = @_;
204 unless ( $itemnumber ) {
205 croak "FAILED CartToShelf() - no itemnumber supplied";
208 my $item = GetItem($itemnumber);
209 if ( $item->{location} eq 'CART' ) {
210 $item->{location} = $item->{permanent_location};
211 ModItem($item, undef, $itemnumber);
215 =head2 ShelfToCart
217 ShelfToCart($itemnumber);
219 Set the current shelving location of the item
220 to shelving cart ('CART').
222 =cut
224 sub ShelfToCart {
225 my ( $itemnumber ) = @_;
227 unless ( $itemnumber ) {
228 croak "FAILED ShelfToCart() - no itemnumber supplied";
231 my $item = GetItem($itemnumber);
232 $item->{'location'} = 'CART';
233 ModItem($item, undef, $itemnumber);
236 =head2 AddItemFromMarc
238 my ($biblionumber, $biblioitemnumber, $itemnumber)
239 = AddItemFromMarc($source_item_marc, $biblionumber);
241 Given a MARC::Record object containing an embedded item
242 record and a biblionumber, create a new item record.
244 =cut
246 sub AddItemFromMarc {
247 my ( $source_item_marc, $biblionumber ) = @_;
248 my $dbh = C4::Context->dbh;
250 # parse item hash from MARC
251 my $frameworkcode = GetFrameworkCode( $biblionumber );
252 my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
254 my $localitemmarc=MARC::Record->new;
255 $localitemmarc->append_fields($source_item_marc->field($itemtag));
256 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
257 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
258 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
261 =head2 AddItem
263 my ($biblionumber, $biblioitemnumber, $itemnumber)
264 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
266 Given a hash containing item column names as keys,
267 create a new Koha item record.
269 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
270 do not need to be supplied for general use; they exist
271 simply to allow them to be picked up from AddItemFromMarc.
273 The final optional parameter, C<$unlinked_item_subfields>, contains
274 an arrayref containing subfields present in the original MARC
275 representation of the item (e.g., from the item editor) that are
276 not mapped to C<items> columns directly but should instead
277 be stored in C<items.more_subfields_xml> and included in
278 the biblio items tag for display and indexing.
280 =cut
282 sub AddItem {
283 my $item = shift;
284 my $biblionumber = shift;
286 my $dbh = @_ ? shift : C4::Context->dbh;
287 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
288 my $unlinked_item_subfields;
289 if (@_) {
290 $unlinked_item_subfields = shift
293 # needs old biblionumber and biblioitemnumber
294 $item->{'biblionumber'} = $biblionumber;
295 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
296 $sth->execute( $item->{'biblionumber'} );
297 ($item->{'biblioitemnumber'}) = $sth->fetchrow;
299 _set_defaults_for_add($item);
300 _set_derived_columns_for_add($item);
301 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
302 # FIXME - checks here
303 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
304 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
305 $itype_sth->execute( $item->{'biblionumber'} );
306 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
309 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
310 $item->{'itemnumber'} = $itemnumber;
312 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
314 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
316 return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
319 =head2 AddItemBatchFromMarc
321 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
322 $biblionumber, $biblioitemnumber, $frameworkcode);
324 Efficiently create item records from a MARC biblio record with
325 embedded item fields. This routine is suitable for batch jobs.
327 This API assumes that the bib record has already been
328 saved to the C<biblio> and C<biblioitems> tables. It does
329 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
330 are populated, but it will do so via a call to ModBibiloMarc.
332 The goal of this API is to have a similar effect to using AddBiblio
333 and AddItems in succession, but without inefficient repeated
334 parsing of the MARC XML bib record.
336 This function returns an arrayref of new itemsnumbers and an arrayref of item
337 errors encountered during the processing. Each entry in the errors
338 list is a hashref containing the following keys:
340 =over
342 =item item_sequence
344 Sequence number of original item tag in the MARC record.
346 =item item_barcode
348 Item barcode, provide to assist in the construction of
349 useful error messages.
351 =item error_code
353 Code representing the error condition. Can be 'duplicate_barcode',
354 'invalid_homebranch', or 'invalid_holdingbranch'.
356 =item error_information
358 Additional information appropriate to the error condition.
360 =back
362 =cut
364 sub AddItemBatchFromMarc {
365 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
366 my $error;
367 my @itemnumbers = ();
368 my @errors = ();
369 my $dbh = C4::Context->dbh;
371 # We modify the record, so lets work on a clone so we don't change the
372 # original.
373 $record = $record->clone();
374 # loop through the item tags and start creating items
375 my @bad_item_fields = ();
376 my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
377 my $item_sequence_num = 0;
378 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
379 $item_sequence_num++;
380 # we take the item field and stick it into a new
381 # MARC record -- this is required so far because (FIXME)
382 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
383 # and there is no TransformMarcFieldToKoha
384 my $temp_item_marc = MARC::Record->new();
385 $temp_item_marc->append_fields($item_field);
387 # add biblionumber and biblioitemnumber
388 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
389 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
390 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
391 $item->{'biblionumber'} = $biblionumber;
392 $item->{'biblioitemnumber'} = $biblioitemnumber;
394 # check for duplicate barcode
395 my %item_errors = CheckItemPreSave($item);
396 if (%item_errors) {
397 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
398 push @bad_item_fields, $item_field;
399 next ITEMFIELD;
402 _set_defaults_for_add($item);
403 _set_derived_columns_for_add($item);
404 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
405 warn $error if $error;
406 push @itemnumbers, $itemnumber; # FIXME not checking error
407 $item->{'itemnumber'} = $itemnumber;
409 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
411 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
412 $item_field->replace_with($new_item_marc->field($itemtag));
415 # remove any MARC item fields for rejected items
416 foreach my $item_field (@bad_item_fields) {
417 $record->delete_field($item_field);
420 # update the MARC biblio
421 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
423 return (\@itemnumbers, \@errors);
426 =head2 ModItemFromMarc
428 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
430 This function updates an item record based on a supplied
431 C<MARC::Record> object containing an embedded item field.
432 This API is meant for the use of C<additem.pl>; for
433 other purposes, C<ModItem> should be used.
435 This function uses the hash %default_values_for_mod_from_marc,
436 which contains default values for item fields to
437 apply when modifying an item. This is needed because
438 if an item field's value is cleared, TransformMarcToKoha
439 does not include the column in the
440 hash that's passed to ModItem, which without
441 use of this hash makes it impossible to clear
442 an item field's value. See bug 2466.
444 Note that only columns that can be directly
445 changed from the cataloging and serials
446 item editors are included in this hash.
448 Returns item record
450 =cut
452 sub _build_default_values_for_mod_marc {
453 my ($frameworkcode) = @_;
455 my $cache = Koha::Cache->get_instance();
456 my $cache_key = "default_value_for_mod_marc-$frameworkcode";
457 my $cached = $cache->get_from_cache($cache_key);
458 return $cached if $cached;
460 my $default_values = {
461 barcode => undef,
462 booksellerid => undef,
463 ccode => undef,
464 'items.cn_source' => undef,
465 coded_location_qualifier => undef,
466 copynumber => undef,
467 damaged => 0,
468 enumchron => undef,
469 holdingbranch => undef,
470 homebranch => undef,
471 itemcallnumber => undef,
472 itemlost => 0,
473 itemnotes => undef,
474 itemnotes_nonpublic => undef,
475 itype => undef,
476 location => undef,
477 permanent_location => undef,
478 materials => undef,
479 new_status => undef,
480 notforloan => 0,
481 # paidfor => undef, # commented, see bug 12817
482 price => undef,
483 replacementprice => undef,
484 replacementpricedate => undef,
485 restricted => undef,
486 stack => undef,
487 stocknumber => undef,
488 uri => undef,
489 withdrawn => 0,
491 my %default_values_for_mod_from_marc;
492 while ( my ( $field, $default_value ) = each %$default_values ) {
493 my $kohafield = $field;
494 $kohafield =~ s|^([^\.]+)$|items.$1|;
495 $default_values_for_mod_from_marc{$field} =
496 $default_value
497 if C4::Koha::IsKohaFieldLinked(
498 { kohafield => $kohafield, frameworkcode => $frameworkcode } );
501 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
502 return \%default_values_for_mod_from_marc;
505 sub ModItemFromMarc {
506 my $item_marc = shift;
507 my $biblionumber = shift;
508 my $itemnumber = shift;
510 my $dbh = C4::Context->dbh;
511 my $frameworkcode = GetFrameworkCode($biblionumber);
512 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
514 my $localitemmarc = MARC::Record->new;
515 $localitemmarc->append_fields( $item_marc->field($itemtag) );
516 my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
517 my $default_values = _build_default_values_for_mod_marc($frameworkcode);
518 foreach my $item_field ( keys %$default_values ) {
519 $item->{$item_field} = $default_values->{$item_field}
520 unless exists $item->{$item_field};
522 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
524 ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
525 return $item;
528 =head2 ModItem
530 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
532 Change one or more columns in an item record and update
533 the MARC representation of the item.
535 The first argument is a hashref mapping from item column
536 names to the new values. The second and third arguments
537 are the biblionumber and itemnumber, respectively.
539 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
540 an arrayref containing subfields present in the original MARC
541 representation of the item (e.g., from the item editor) that are
542 not mapped to C<items> columns directly but should instead
543 be stored in C<items.more_subfields_xml> and included in
544 the biblio items tag for display and indexing.
546 If one of the changed columns is used to calculate
547 the derived value of a column such as C<items.cn_sort>,
548 this routine will perform the necessary calculation
549 and set the value.
551 =cut
553 sub ModItem {
554 my $item = shift;
555 my $biblionumber = shift;
556 my $itemnumber = shift;
558 # if $biblionumber is undefined, get it from the current item
559 unless (defined $biblionumber) {
560 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
563 my $dbh = @_ ? shift : C4::Context->dbh;
564 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
566 my $unlinked_item_subfields;
567 if (@_) {
568 $unlinked_item_subfields = shift;
569 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
572 $item->{'itemnumber'} = $itemnumber or return;
574 my @fields = qw( itemlost withdrawn );
576 # Only call GetItem if we need to set an "on" date field
577 if ( $item->{itemlost} || $item->{withdrawn} ) {
578 my $pre_mod_item = GetItem( $item->{'itemnumber'} );
579 for my $field (@fields) {
580 if ( defined( $item->{$field} )
581 and not $pre_mod_item->{$field}
582 and $item->{$field} )
584 $item->{ $field . '_on' } =
585 DateTime::Format::MySQL->format_datetime( dt_from_string() );
590 # If the field is defined but empty, we are removing and,
591 # and thus need to clear out the 'on' field as well
592 for my $field (@fields) {
593 if ( defined( $item->{$field} ) && !$item->{$field} ) {
594 $item->{ $field . '_on' } = undef;
599 _set_derived_columns_for_mod($item);
600 _do_column_fixes_for_mod($item);
601 # FIXME add checks
602 # duplicate barcode
603 # attempt to change itemnumber
604 # attempt to change biblionumber (if we want
605 # an API to relink an item to a different bib,
606 # it should be a separate function)
608 # update items table
609 _koha_modify_item($item);
611 # request that bib be reindexed so that searching on current
612 # item status is possible
613 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
615 logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
618 =head2 ModItemTransfer
620 ModItemTransfer($itenumber, $frombranch, $tobranch);
622 Marks an item as being transferred from one branch
623 to another.
625 =cut
627 sub ModItemTransfer {
628 my ( $itemnumber, $frombranch, $tobranch ) = @_;
630 my $dbh = C4::Context->dbh;
632 # Remove the 'shelving cart' location status if it is being used.
633 CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
635 #new entry in branchtransfers....
636 my $sth = $dbh->prepare(
637 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
638 VALUES (?, ?, NOW(), ?)");
639 $sth->execute($itemnumber, $frombranch, $tobranch);
641 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
642 ModDateLastSeen($itemnumber);
643 return;
646 =head2 ModDateLastSeen
648 ModDateLastSeen($itemnum);
650 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
651 C<$itemnum> is the item number
653 =cut
655 sub ModDateLastSeen {
656 my ($itemnumber) = @_;
658 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
659 ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
662 =head2 DelItem
664 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
666 Exported function (core API) for deleting an item record in Koha.
668 =cut
670 sub DelItem {
671 my ( $params ) = @_;
673 my $itemnumber = $params->{itemnumber};
674 my $biblionumber = $params->{biblionumber};
676 unless ($biblionumber) {
677 $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
680 # If there is no biblionumber for the given itemnumber, there is nothing to delete
681 return 0 unless $biblionumber;
683 # FIXME check the item has no current issues
684 my $deleted = _koha_delete_item( $itemnumber );
686 # get the MARC record
687 my $record = GetMarcBiblio($biblionumber);
688 ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
690 #search item field code
691 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
692 return $deleted;
695 =head2 CheckItemPreSave
697 my $item_ref = TransformMarcToKoha($marc, 'items');
698 # do stuff
699 my %errors = CheckItemPreSave($item_ref);
700 if (exists $errors{'duplicate_barcode'}) {
701 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
702 } elsif (exists $errors{'invalid_homebranch'}) {
703 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
704 } elsif (exists $errors{'invalid_holdingbranch'}) {
705 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
706 } else {
707 print "item is OK";
710 Given a hashref containing item fields, determine if it can be
711 inserted or updated in the database. Specifically, checks for
712 database integrity issues, and returns a hash containing any
713 of the following keys, if applicable.
715 =over 2
717 =item duplicate_barcode
719 Barcode, if it duplicates one already found in the database.
721 =item invalid_homebranch
723 Home branch, if not defined in branches table.
725 =item invalid_holdingbranch
727 Holding branch, if not defined in branches table.
729 =back
731 This function does NOT implement any policy-related checks,
732 e.g., whether current operator is allowed to save an
733 item that has a given branch code.
735 =cut
737 sub CheckItemPreSave {
738 my $item_ref = shift;
739 require C4::Branch;
741 my %errors = ();
743 # check for duplicate barcode
744 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
745 my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
746 if ($existing_itemnumber) {
747 if (!exists $item_ref->{'itemnumber'} # new item
748 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
749 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
754 # check for valid home branch
755 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
756 my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'});
757 unless (defined $branch_name) {
758 # relies on fact that branches.branchname is a non-NULL column,
759 # so GetBranchName returns undef only if branch does not exist
760 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
764 # check for valid holding branch
765 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
766 my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'});
767 unless (defined $branch_name) {
768 # relies on fact that branches.branchname is a non-NULL column,
769 # so GetBranchName returns undef only if branch does not exist
770 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
774 return %errors;
778 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
780 The following functions provide various ways of
781 getting an item record, a set of item records, or
782 lists of authorized values for certain item fields.
784 Some of the functions in this group are candidates
785 for refactoring -- for example, some of the code
786 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
787 has copy-and-paste work.
789 =cut
791 =head2 GetItemStatus
793 $itemstatushash = GetItemStatus($fwkcode);
795 Returns a list of valid values for the
796 C<items.notforloan> field.
798 NOTE: does B<not> return an individual item's
799 status.
801 Can be MARC dependent.
802 fwkcode is optional.
803 But basically could be can be loan or not
804 Create a status selector with the following code
806 =head3 in PERL SCRIPT
808 my $itemstatushash = getitemstatus;
809 my @itemstatusloop;
810 foreach my $thisstatus (keys %$itemstatushash) {
811 my %row =(value => $thisstatus,
812 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
814 push @itemstatusloop, \%row;
816 $template->param(statusloop=>\@itemstatusloop);
818 =head3 in TEMPLATE
820 <select name="statusloop" id="statusloop">
821 <option value="">Default</option>
822 [% FOREACH statusloo IN statusloop %]
823 [% IF ( statusloo.selected ) %]
824 <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
825 [% ELSE %]
826 <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
827 [% END %]
828 [% END %]
829 </select>
831 =cut
833 sub GetItemStatus {
835 # returns a reference to a hash of references to status...
836 my ($fwk) = @_;
837 my %itemstatus;
838 my $dbh = C4::Context->dbh;
839 my $sth;
840 $fwk = '' unless ($fwk);
841 my ( $tag, $subfield ) =
842 GetMarcFromKohaField( "items.notforloan", $fwk );
843 if ( $tag and $subfield ) {
844 my $sth =
845 $dbh->prepare(
846 "SELECT authorised_value
847 FROM marc_subfield_structure
848 WHERE tagfield=?
849 AND tagsubfield=?
850 AND frameworkcode=?
853 $sth->execute( $tag, $subfield, $fwk );
854 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
855 my $authvalsth =
856 $dbh->prepare(
857 "SELECT authorised_value,lib
858 FROM authorised_values
859 WHERE category=?
860 ORDER BY lib
863 $authvalsth->execute($authorisedvaluecat);
864 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
865 $itemstatus{$authorisedvalue} = $lib;
867 return \%itemstatus;
868 exit 1;
870 else {
872 #No authvalue list
873 # build default
877 #No authvalue list
878 #build default
879 $itemstatus{"1"} = "Not For Loan";
880 return \%itemstatus;
883 =head2 GetItemLocation
885 $itemlochash = GetItemLocation($fwk);
887 Returns a list of valid values for the
888 C<items.location> field.
890 NOTE: does B<not> return an individual item's
891 location.
893 where fwk stands for an optional framework code.
894 Create a location selector with the following code
896 =head3 in PERL SCRIPT
898 my $itemlochash = getitemlocation;
899 my @itemlocloop;
900 foreach my $thisloc (keys %$itemlochash) {
901 my $selected = 1 if $thisbranch eq $branch;
902 my %row =(locval => $thisloc,
903 selected => $selected,
904 locname => $itemlochash->{$thisloc},
906 push @itemlocloop, \%row;
908 $template->param(itemlocationloop => \@itemlocloop);
910 =head3 in TEMPLATE
912 <select name="location">
913 <option value="">Default</option>
914 <!-- TMPL_LOOP name="itemlocationloop" -->
915 <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
916 <!-- /TMPL_LOOP -->
917 </select>
919 =cut
921 sub GetItemLocation {
923 # returns a reference to a hash of references to location...
924 my ($fwk) = @_;
925 my %itemlocation;
926 my $dbh = C4::Context->dbh;
927 my $sth;
928 $fwk = '' unless ($fwk);
929 my ( $tag, $subfield ) =
930 GetMarcFromKohaField( "items.location", $fwk );
931 if ( $tag and $subfield ) {
932 my $sth =
933 $dbh->prepare(
934 "SELECT authorised_value
935 FROM marc_subfield_structure
936 WHERE tagfield=?
937 AND tagsubfield=?
938 AND frameworkcode=?"
940 $sth->execute( $tag, $subfield, $fwk );
941 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
942 my $authvalsth =
943 $dbh->prepare(
944 "SELECT authorised_value,lib
945 FROM authorised_values
946 WHERE category=?
947 ORDER BY lib"
949 $authvalsth->execute($authorisedvaluecat);
950 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
951 $itemlocation{$authorisedvalue} = $lib;
953 return \%itemlocation;
954 exit 1;
956 else {
958 #No authvalue list
959 # build default
963 #No authvalue list
964 #build default
965 $itemlocation{"1"} = "Not For Loan";
966 return \%itemlocation;
969 =head2 GetLostItems
971 $items = GetLostItems( $where );
973 This function gets a list of lost items.
975 =over 2
977 =item input:
979 C<$where> is a hashref. it containts a field of the items table as key
980 and the value to match as value. For example:
982 { barcode => 'abc123',
983 homebranch => 'CPL', }
985 =item return:
987 C<$items> is a reference to an array full of hashrefs with columns
988 from the "items" table as keys.
990 =item usage in the perl script:
992 my $where = { barcode => '0001548' };
993 my $items = GetLostItems( $where );
994 $template->param( itemsloop => $items );
996 =back
998 =cut
1000 sub GetLostItems {
1001 # Getting input args.
1002 my $where = shift;
1003 my $dbh = C4::Context->dbh;
1005 my $query = "
1006 SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
1007 itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
1008 FROM items
1009 LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
1010 LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1011 LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1012 WHERE
1013 authorised_values.category = 'LOST'
1014 AND itemlost IS NOT NULL
1015 AND itemlost <> 0
1017 my @query_parameters;
1018 foreach my $key (keys %$where) {
1019 $query .= " AND $key LIKE ?";
1020 push @query_parameters, "%$where->{$key}%";
1023 my $sth = $dbh->prepare($query);
1024 $sth->execute( @query_parameters );
1025 my $items = [];
1026 while ( my $row = $sth->fetchrow_hashref ){
1027 push @$items, $row;
1029 return $items;
1032 =head2 GetItemsForInventory
1034 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
1035 minlocation => $minlocation,
1036 maxlocation => $maxlocation,
1037 location => $location,
1038 itemtype => $itemtype,
1039 ignoreissued => $ignoreissued,
1040 datelastseen => $datelastseen,
1041 branchcode => $branchcode,
1042 branch => $branch,
1043 offset => $offset,
1044 size => $size,
1045 statushash => $statushash,
1046 interface => $interface,
1047 } );
1049 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1051 The sub returns a reference to a list of hashes, each containing
1052 itemnumber, author, title, barcode, item callnumber, and date last
1053 seen. It is ordered by callnumber then title.
1055 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1056 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1057 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1058 $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.
1060 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1062 =cut
1064 sub GetItemsForInventory {
1065 my ( $parameters ) = @_;
1066 my $minlocation = $parameters->{'minlocation'} // '';
1067 my $maxlocation = $parameters->{'maxlocation'} // '';
1068 my $location = $parameters->{'location'} // '';
1069 my $itemtype = $parameters->{'itemtype'} // '';
1070 my $ignoreissued = $parameters->{'ignoreissued'} // '';
1071 my $datelastseen = $parameters->{'datelastseen'} // '';
1072 my $branchcode = $parameters->{'branchcode'} // '';
1073 my $branch = $parameters->{'branch'} // '';
1074 my $offset = $parameters->{'offset'} // '';
1075 my $size = $parameters->{'size'} // '';
1076 my $statushash = $parameters->{'statushash'} // '';
1077 my $interface = $parameters->{'interface'} // '';
1079 my $dbh = C4::Context->dbh;
1080 my ( @bind_params, @where_strings );
1082 my $select_columns = q{
1083 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1085 my $select_count = q{SELECT COUNT(*)};
1086 my $query = q{
1087 FROM items
1088 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1089 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1091 if ($statushash){
1092 for my $authvfield (keys %$statushash){
1093 if ( scalar @{$statushash->{$authvfield}} > 0 ){
1094 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1095 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1100 if ($minlocation) {
1101 push @where_strings, 'itemcallnumber >= ?';
1102 push @bind_params, $minlocation;
1105 if ($maxlocation) {
1106 push @where_strings, 'itemcallnumber <= ?';
1107 push @bind_params, $maxlocation;
1110 if ($datelastseen) {
1111 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1112 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1113 push @bind_params, $datelastseen;
1116 if ( $location ) {
1117 push @where_strings, 'items.location = ?';
1118 push @bind_params, $location;
1121 if ( $branchcode ) {
1122 if($branch eq "homebranch"){
1123 push @where_strings, 'items.homebranch = ?';
1124 }else{
1125 push @where_strings, 'items.holdingbranch = ?';
1127 push @bind_params, $branchcode;
1130 if ( $itemtype ) {
1131 push @where_strings, 'biblioitems.itemtype = ?';
1132 push @bind_params, $itemtype;
1135 if ( $ignoreissued) {
1136 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1137 push @where_strings, 'issues.date_due IS NULL';
1140 if ( @where_strings ) {
1141 $query .= 'WHERE ';
1142 $query .= join ' AND ', @where_strings;
1144 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1145 my $count_query = $select_count . $query;
1146 $query .= " LIMIT $offset, $size" if ($offset and $size);
1147 $query = $select_columns . $query;
1148 my $sth = $dbh->prepare($query);
1149 $sth->execute( @bind_params );
1151 my @results = ();
1152 my $tmpresults = $sth->fetchall_arrayref({});
1153 $sth = $dbh->prepare( $count_query );
1154 $sth->execute( @bind_params );
1155 my ($iTotalRecords) = $sth->fetchrow_array();
1157 my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( {
1158 interface => $interface
1159 } );
1160 foreach my $row (@$tmpresults) {
1162 # Auth values
1163 foreach (keys %$row) {
1164 if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1165 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1168 push @results, $row;
1171 return (\@results, $iTotalRecords);
1174 =head2 GetItemsCount
1176 $count = &GetItemsCount( $biblionumber);
1178 This function return count of item with $biblionumber
1180 =cut
1182 sub GetItemsCount {
1183 my ( $biblionumber ) = @_;
1184 my $dbh = C4::Context->dbh;
1185 my $query = "SELECT count(*)
1186 FROM items
1187 WHERE biblionumber=?";
1188 my $sth = $dbh->prepare($query);
1189 $sth->execute($biblionumber);
1190 my $count = $sth->fetchrow;
1191 return ($count);
1194 =head2 GetItemInfosOf
1196 GetItemInfosOf(@itemnumbers);
1198 =cut
1200 sub GetItemInfosOf {
1201 my @itemnumbers = @_;
1203 my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1205 my $query = "
1206 SELECT *
1207 FROM items
1208 WHERE itemnumber IN ($itemnumber_values)
1210 return get_infos_of( $query, 'itemnumber' );
1213 =head2 GetItemsByBiblioitemnumber
1215 GetItemsByBiblioitemnumber($biblioitemnumber);
1217 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1218 Called by C<C4::XISBN>
1220 =cut
1222 sub GetItemsByBiblioitemnumber {
1223 my ( $bibitem ) = @_;
1224 my $dbh = C4::Context->dbh;
1225 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1226 # Get all items attached to a biblioitem
1227 my $i = 0;
1228 my @results;
1229 $sth->execute($bibitem) || die $sth->errstr;
1230 while ( my $data = $sth->fetchrow_hashref ) {
1231 # Foreach item, get circulation information
1232 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1233 WHERE itemnumber = ?
1234 AND issues.borrowernumber = borrowers.borrowernumber"
1236 $sth2->execute( $data->{'itemnumber'} );
1237 if ( my $data2 = $sth2->fetchrow_hashref ) {
1238 # if item is out, set the due date and who it is out too
1239 $data->{'date_due'} = $data2->{'date_due'};
1240 $data->{'cardnumber'} = $data2->{'cardnumber'};
1241 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
1243 else {
1244 # set date_due to blank, so in the template we check itemlost, and withdrawn
1245 $data->{'date_due'} = '';
1246 } # else
1247 # Find the last 3 people who borrowed this item.
1248 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1249 AND old_issues.borrowernumber = borrowers.borrowernumber
1250 ORDER BY returndate desc,timestamp desc LIMIT 3";
1251 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1252 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1253 my $i2 = 0;
1254 while ( my $data2 = $sth2->fetchrow_hashref ) {
1255 $data->{"timestamp$i2"} = $data2->{'timestamp'};
1256 $data->{"card$i2"} = $data2->{'cardnumber'};
1257 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
1258 $i2++;
1260 push(@results,$data);
1262 return (\@results);
1265 =head2 GetItemsInfo
1267 @results = GetItemsInfo($biblionumber);
1269 Returns information about items with the given biblionumber.
1271 C<GetItemsInfo> returns a list of references-to-hash. Each element
1272 contains a number of keys. Most of them are attributes from the
1273 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1274 Koha database. Other keys include:
1276 =over 2
1278 =item C<$data-E<gt>{branchname}>
1280 The name (not the code) of the branch to which the book belongs.
1282 =item C<$data-E<gt>{datelastseen}>
1284 This is simply C<items.datelastseen>, except that while the date is
1285 stored in YYYY-MM-DD format in the database, here it is converted to
1286 DD/MM/YYYY format. A NULL date is returned as C<//>.
1288 =item C<$data-E<gt>{datedue}>
1290 =item C<$data-E<gt>{class}>
1292 This is the concatenation of C<biblioitems.classification>, the book's
1293 Dewey code, and C<biblioitems.subclass>.
1295 =item C<$data-E<gt>{ocount}>
1297 I think this is the number of copies of the book available.
1299 =item C<$data-E<gt>{order}>
1301 If this is set, it is set to C<One Order>.
1303 =back
1305 =cut
1307 sub GetItemsInfo {
1308 my ( $biblionumber ) = @_;
1309 my $dbh = C4::Context->dbh;
1310 # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1311 require C4::Languages;
1312 my $language = C4::Languages::getlanguage();
1313 my $query = "
1314 SELECT items.*,
1315 biblio.*,
1316 biblioitems.volume,
1317 biblioitems.number,
1318 biblioitems.itemtype,
1319 biblioitems.isbn,
1320 biblioitems.issn,
1321 biblioitems.publicationyear,
1322 biblioitems.publishercode,
1323 biblioitems.volumedate,
1324 biblioitems.volumedesc,
1325 biblioitems.lccn,
1326 biblioitems.url,
1327 items.notforloan as itemnotforloan,
1328 issues.borrowernumber,
1329 issues.date_due as datedue,
1330 issues.onsite_checkout,
1331 borrowers.cardnumber,
1332 borrowers.surname,
1333 borrowers.firstname,
1334 borrowers.branchcode as bcode,
1335 serial.serialseq,
1336 serial.publisheddate,
1337 itemtypes.description,
1338 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1339 itemtypes.notforloan as notforloan_per_itemtype,
1340 holding.branchurl,
1341 holding.branchname,
1342 holding.opac_info as holding_branch_opac_info,
1343 home.opac_info as home_branch_opac_info
1345 $query .= "
1346 FROM items
1347 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1348 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1349 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1350 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1351 LEFT JOIN issues USING (itemnumber)
1352 LEFT JOIN borrowers USING (borrowernumber)
1353 LEFT JOIN serialitems USING (itemnumber)
1354 LEFT JOIN serial USING (serialid)
1355 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1356 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1357 $query .= q|
1358 LEFT JOIN localization ON itemtypes.itemtype = localization.code
1359 AND localization.entity = 'itemtypes'
1360 AND localization.lang = ?
1363 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1364 my $sth = $dbh->prepare($query);
1365 $sth->execute($language, $biblionumber);
1366 my $i = 0;
1367 my @results;
1368 my $serial;
1370 my $userenv = C4::Context->userenv;
1371 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1372 while ( my $data = $sth->fetchrow_hashref ) {
1373 if ( $data->{borrowernumber} && $want_not_same_branch) {
1374 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1377 $serial ||= $data->{'serial'};
1379 # get notforloan complete status if applicable
1380 if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) {
1381 $data->{notforloanvalue} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan} );
1382 $data->{notforloanvalueopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan}, 1 );
1385 # get restricted status and description if applicable
1386 if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) {
1387 $data->{restrictedopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted}, 1 );
1388 $data->{restricted} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted} );
1391 # my stack procedures
1392 if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) {
1393 $data->{stack} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{stack} );
1396 # Find the last 3 people who borrowed this item.
1397 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1398 WHERE itemnumber = ?
1399 AND old_issues.borrowernumber = borrowers.borrowernumber
1400 ORDER BY returndate DESC
1401 LIMIT 3");
1402 $sth2->execute($data->{'itemnumber'});
1403 my $ii = 0;
1404 while (my $data2 = $sth2->fetchrow_hashref()) {
1405 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1406 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1407 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1408 $ii++;
1411 $results[$i] = $data;
1412 $i++;
1415 return $serial
1416 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1417 : @results;
1420 =head2 GetItemsLocationInfo
1422 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1424 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1426 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1428 =over 2
1430 =item C<$data-E<gt>{homebranch}>
1432 Branch Name of the item's homebranch
1434 =item C<$data-E<gt>{holdingbranch}>
1436 Branch Name of the item's holdingbranch
1438 =item C<$data-E<gt>{location}>
1440 Item's shelving location code
1442 =item C<$data-E<gt>{location_intranet}>
1444 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1446 =item C<$data-E<gt>{location_opac}>
1448 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1449 description is set.
1451 =item C<$data-E<gt>{itemcallnumber}>
1453 Item's itemcallnumber
1455 =item C<$data-E<gt>{cn_sort}>
1457 Item's call number normalized for sorting
1459 =back
1461 =cut
1463 sub GetItemsLocationInfo {
1464 my $biblionumber = shift;
1465 my @results;
1467 my $dbh = C4::Context->dbh;
1468 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1469 location, itemcallnumber, cn_sort
1470 FROM items, branches as a, branches as b
1471 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1472 AND biblionumber = ?
1473 ORDER BY cn_sort ASC";
1474 my $sth = $dbh->prepare($query);
1475 $sth->execute($biblionumber);
1477 while ( my $data = $sth->fetchrow_hashref ) {
1478 $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1479 $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1480 push @results, $data;
1482 return @results;
1485 =head2 GetHostItemsInfo
1487 $hostiteminfo = GetHostItemsInfo($hostfield);
1488 Returns the iteminfo for items linked to records via a host field
1490 =cut
1492 sub GetHostItemsInfo {
1493 my ($record) = @_;
1494 my @returnitemsInfo;
1496 if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1497 C4::Context->preference('marcflavour') eq 'NORMARC'){
1498 foreach my $hostfield ( $record->field('773') ) {
1499 my $hostbiblionumber = $hostfield->subfield("0");
1500 my $linkeditemnumber = $hostfield->subfield("9");
1501 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1502 foreach my $hostitemInfo (@hostitemInfos){
1503 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1504 push (@returnitemsInfo,$hostitemInfo);
1505 last;
1509 } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1510 foreach my $hostfield ( $record->field('461') ) {
1511 my $hostbiblionumber = $hostfield->subfield("0");
1512 my $linkeditemnumber = $hostfield->subfield("9");
1513 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1514 foreach my $hostitemInfo (@hostitemInfos){
1515 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1516 push (@returnitemsInfo,$hostitemInfo);
1517 last;
1522 return @returnitemsInfo;
1526 =head2 GetLastAcquisitions
1528 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1529 'itemtypes' => ('BK','BD')}, 10);
1531 =cut
1533 sub GetLastAcquisitions {
1534 my ($data,$max) = @_;
1536 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1538 my $number_of_branches = @{$data->{branches}};
1539 my $number_of_itemtypes = @{$data->{itemtypes}};
1542 my @where = ('WHERE 1 ');
1543 $number_of_branches and push @where
1544 , 'AND holdingbranch IN ('
1545 , join(',', ('?') x $number_of_branches )
1546 , ')'
1549 $number_of_itemtypes and push @where
1550 , "AND $itemtype IN ("
1551 , join(',', ('?') x $number_of_itemtypes )
1552 , ')'
1555 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1556 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1557 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1558 @where
1559 GROUP BY biblio.biblionumber
1560 ORDER BY dateaccessioned DESC LIMIT $max";
1562 my $dbh = C4::Context->dbh;
1563 my $sth = $dbh->prepare($query);
1565 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1567 my @results;
1568 while( my $row = $sth->fetchrow_hashref){
1569 push @results, {date => $row->{dateaccessioned}
1570 , biblionumber => $row->{biblionumber}
1571 , title => $row->{title}};
1574 return @results;
1577 =head2 GetItemnumbersForBiblio
1579 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1581 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1583 =cut
1585 sub GetItemnumbersForBiblio {
1586 my $biblionumber = shift;
1587 my @items;
1588 my $dbh = C4::Context->dbh;
1589 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1590 $sth->execute($biblionumber);
1591 while (my $result = $sth->fetchrow_hashref) {
1592 push @items, $result->{'itemnumber'};
1594 return \@items;
1597 =head2 get_itemnumbers_of
1599 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1601 Given a list of biblionumbers, return the list of corresponding itemnumbers
1602 for each biblionumber.
1604 Return a reference on a hash where keys are biblionumbers and values are
1605 references on array of itemnumbers.
1607 =cut
1609 sub get_itemnumbers_of {
1610 my @biblionumbers = @_;
1612 my $dbh = C4::Context->dbh;
1614 my $query = '
1615 SELECT itemnumber,
1616 biblionumber
1617 FROM items
1618 WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1620 my $sth = $dbh->prepare($query);
1621 $sth->execute(@biblionumbers);
1623 my %itemnumbers_of;
1625 while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1626 push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1629 return \%itemnumbers_of;
1632 =head2 get_hostitemnumbers_of
1634 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1636 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1638 Return a reference on a hash where key is a biblionumber and values are
1639 references on array of itemnumbers.
1641 =cut
1644 sub get_hostitemnumbers_of {
1645 my ($biblionumber) = @_;
1646 my $marcrecord = GetMarcBiblio($biblionumber);
1647 my (@returnhostitemnumbers,$tag, $biblio_s, $item_s);
1649 my $marcflavor = C4::Context->preference('marcflavour');
1650 if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1651 $tag='773';
1652 $biblio_s='0';
1653 $item_s='9';
1654 } elsif ($marcflavor eq 'UNIMARC') {
1655 $tag='461';
1656 $biblio_s='0';
1657 $item_s='9';
1660 foreach my $hostfield ( $marcrecord->field($tag) ) {
1661 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1662 my $linkeditemnumber = $hostfield->subfield($item_s);
1663 my @itemnumbers;
1664 if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber})
1666 @itemnumbers = @$itemnumbers;
1668 foreach my $itemnumber (@itemnumbers){
1669 if ($itemnumber eq $linkeditemnumber){
1670 push (@returnhostitemnumbers,$itemnumber);
1671 last;
1675 return @returnhostitemnumbers;
1679 =head2 GetItemnumberFromBarcode
1681 $result = GetItemnumberFromBarcode($barcode);
1683 =cut
1685 sub GetItemnumberFromBarcode {
1686 my ($barcode) = @_;
1687 my $dbh = C4::Context->dbh;
1689 my $rq =
1690 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1691 $rq->execute($barcode);
1692 my ($result) = $rq->fetchrow;
1693 return ($result);
1696 =head2 GetBarcodeFromItemnumber
1698 $result = GetBarcodeFromItemnumber($itemnumber);
1700 =cut
1702 sub GetBarcodeFromItemnumber {
1703 my ($itemnumber) = @_;
1704 my $dbh = C4::Context->dbh;
1706 my $rq =
1707 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1708 $rq->execute($itemnumber);
1709 my ($result) = $rq->fetchrow;
1710 return ($result);
1713 =head2 GetHiddenItemnumbers
1715 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1717 Given a list of items it checks which should be hidden from the OPAC given
1718 the current configuration. Returns a list of itemnumbers corresponding to
1719 those that should be hidden.
1721 =cut
1723 sub GetHiddenItemnumbers {
1724 my (@items) = @_;
1725 my @resultitems;
1727 my $yaml = C4::Context->preference('OpacHiddenItems');
1728 return () if (! $yaml =~ /\S/ );
1729 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1730 my $hidingrules;
1731 eval {
1732 $hidingrules = YAML::Load($yaml);
1734 if ($@) {
1735 warn "Unable to parse OpacHiddenItems syspref : $@";
1736 return ();
1738 my $dbh = C4::Context->dbh;
1740 # For each item
1741 foreach my $item (@items) {
1743 # We check each rule
1744 foreach my $field (keys %$hidingrules) {
1745 my $val;
1746 if (exists $item->{$field}) {
1747 $val = $item->{$field};
1749 else {
1750 my $query = "SELECT $field from items where itemnumber = ?";
1751 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1753 $val = '' unless defined $val;
1755 # If the results matches the values in the yaml file
1756 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1758 # We add the itemnumber to the list
1759 push @resultitems, $item->{'itemnumber'};
1761 # If at least one rule matched for an item, no need to test the others
1762 last;
1766 return @resultitems;
1769 =head1 LIMITED USE FUNCTIONS
1771 The following functions, while part of the public API,
1772 are not exported. This is generally because they are
1773 meant to be used by only one script for a specific
1774 purpose, and should not be used in any other context
1775 without careful thought.
1777 =cut
1779 =head2 GetMarcItem
1781 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1783 Returns MARC::Record of the item passed in parameter.
1784 This function is meant for use only in C<cataloguing/additem.pl>,
1785 where it is needed to support that script's MARC-like
1786 editor.
1788 =cut
1790 sub GetMarcItem {
1791 my ( $biblionumber, $itemnumber ) = @_;
1793 # GetMarcItem has been revised so that it does the following:
1794 # 1. Gets the item information from the items table.
1795 # 2. Converts it to a MARC field for storage in the bib record.
1797 # The previous behavior was:
1798 # 1. Get the bib record.
1799 # 2. Return the MARC tag corresponding to the item record.
1801 # The difference is that one treats the items row as authoritative,
1802 # while the other treats the MARC representation as authoritative
1803 # under certain circumstances.
1805 my $itemrecord = GetItem($itemnumber);
1807 # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1808 # Also, don't emit a subfield if the underlying field is blank.
1811 return Item2Marc($itemrecord,$biblionumber);
1814 sub Item2Marc {
1815 my ($itemrecord,$biblionumber)=@_;
1816 my $mungeditem = {
1817 map {
1818 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1819 } keys %{ $itemrecord }
1821 my $itemmarc = TransformKohaToMarc($mungeditem);
1822 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1824 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1825 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1826 foreach my $field ($itemmarc->field($itemtag)){
1827 $field->add_subfields(@$unlinked_item_subfields);
1830 return $itemmarc;
1833 =head1 PRIVATE FUNCTIONS AND VARIABLES
1835 The following functions are not meant to be called
1836 directly, but are documented in order to explain
1837 the inner workings of C<C4::Items>.
1839 =cut
1841 =head2 %derived_columns
1843 This hash keeps track of item columns that
1844 are strictly derived from other columns in
1845 the item record and are not meant to be set
1846 independently.
1848 Each key in the hash should be the name of a
1849 column (as named by TransformMarcToKoha). Each
1850 value should be hashref whose keys are the
1851 columns on which the derived column depends. The
1852 hashref should also contain a 'BUILDER' key
1853 that is a reference to a sub that calculates
1854 the derived value.
1856 =cut
1858 my %derived_columns = (
1859 'items.cn_sort' => {
1860 'itemcallnumber' => 1,
1861 'items.cn_source' => 1,
1862 'BUILDER' => \&_calc_items_cn_sort,
1866 =head2 _set_derived_columns_for_add
1868 _set_derived_column_for_add($item);
1870 Given an item hash representing a new item to be added,
1871 calculate any derived columns. Currently the only
1872 such column is C<items.cn_sort>.
1874 =cut
1876 sub _set_derived_columns_for_add {
1877 my $item = shift;
1879 foreach my $column (keys %derived_columns) {
1880 my $builder = $derived_columns{$column}->{'BUILDER'};
1881 my $source_values = {};
1882 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1883 next if $source_column eq 'BUILDER';
1884 $source_values->{$source_column} = $item->{$source_column};
1886 $builder->($item, $source_values);
1890 =head2 _set_derived_columns_for_mod
1892 _set_derived_column_for_mod($item);
1894 Given an item hash representing a new item to be modified.
1895 calculate any derived columns. Currently the only
1896 such column is C<items.cn_sort>.
1898 This routine differs from C<_set_derived_columns_for_add>
1899 in that it needs to handle partial item records. In other
1900 words, the caller of C<ModItem> may have supplied only one
1901 or two columns to be changed, so this function needs to
1902 determine whether any of the columns to be changed affect
1903 any of the derived columns. Also, if a derived column
1904 depends on more than one column, but the caller is not
1905 changing all of then, this routine retrieves the unchanged
1906 values from the database in order to ensure a correct
1907 calculation.
1909 =cut
1911 sub _set_derived_columns_for_mod {
1912 my $item = shift;
1914 foreach my $column (keys %derived_columns) {
1915 my $builder = $derived_columns{$column}->{'BUILDER'};
1916 my $source_values = {};
1917 my %missing_sources = ();
1918 my $must_recalc = 0;
1919 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1920 next if $source_column eq 'BUILDER';
1921 if (exists $item->{$source_column}) {
1922 $must_recalc = 1;
1923 $source_values->{$source_column} = $item->{$source_column};
1924 } else {
1925 $missing_sources{$source_column} = 1;
1928 if ($must_recalc) {
1929 foreach my $source_column (keys %missing_sources) {
1930 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1932 $builder->($item, $source_values);
1937 =head2 _do_column_fixes_for_mod
1939 _do_column_fixes_for_mod($item);
1941 Given an item hashref containing one or more
1942 columns to modify, fix up certain values.
1943 Specifically, set to 0 any passed value
1944 of C<notforloan>, C<damaged>, C<itemlost>, or
1945 C<withdrawn> that is either undefined or
1946 contains the empty string.
1948 =cut
1950 sub _do_column_fixes_for_mod {
1951 my $item = shift;
1953 if (exists $item->{'notforloan'} and
1954 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1955 $item->{'notforloan'} = 0;
1957 if (exists $item->{'damaged'} and
1958 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1959 $item->{'damaged'} = 0;
1961 if (exists $item->{'itemlost'} and
1962 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1963 $item->{'itemlost'} = 0;
1965 if (exists $item->{'withdrawn'} and
1966 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1967 $item->{'withdrawn'} = 0;
1969 if (exists $item->{location}
1970 and $item->{location} ne 'CART'
1971 and $item->{location} ne 'PROC'
1972 and not $item->{permanent_location}
1974 $item->{'permanent_location'} = $item->{'location'};
1976 if (exists $item->{'timestamp'}) {
1977 delete $item->{'timestamp'};
1981 =head2 _get_single_item_column
1983 _get_single_item_column($column, $itemnumber);
1985 Retrieves the value of a single column from an C<items>
1986 row specified by C<$itemnumber>.
1988 =cut
1990 sub _get_single_item_column {
1991 my $column = shift;
1992 my $itemnumber = shift;
1994 my $dbh = C4::Context->dbh;
1995 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1996 $sth->execute($itemnumber);
1997 my ($value) = $sth->fetchrow();
1998 return $value;
2001 =head2 _calc_items_cn_sort
2003 _calc_items_cn_sort($item, $source_values);
2005 Helper routine to calculate C<items.cn_sort>.
2007 =cut
2009 sub _calc_items_cn_sort {
2010 my $item = shift;
2011 my $source_values = shift;
2013 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
2016 =head2 _set_defaults_for_add
2018 _set_defaults_for_add($item_hash);
2020 Given an item hash representing an item to be added, set
2021 correct default values for columns whose default value
2022 is not handled by the DBMS. This includes the following
2023 columns:
2025 =over 2
2027 =item *
2029 C<items.dateaccessioned>
2031 =item *
2033 C<items.notforloan>
2035 =item *
2037 C<items.damaged>
2039 =item *
2041 C<items.itemlost>
2043 =item *
2045 C<items.withdrawn>
2047 =back
2049 =cut
2051 sub _set_defaults_for_add {
2052 my $item = shift;
2053 $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2054 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
2057 =head2 _koha_new_item
2059 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
2061 Perform the actual insert into the C<items> table.
2063 =cut
2065 sub _koha_new_item {
2066 my ( $item, $barcode ) = @_;
2067 my $dbh=C4::Context->dbh;
2068 my $error;
2069 $item->{permanent_location} //= $item->{location};
2070 my $query =
2071 "INSERT INTO items SET
2072 biblionumber = ?,
2073 biblioitemnumber = ?,
2074 barcode = ?,
2075 dateaccessioned = ?,
2076 booksellerid = ?,
2077 homebranch = ?,
2078 price = ?,
2079 replacementprice = ?,
2080 replacementpricedate = ?,
2081 datelastborrowed = ?,
2082 datelastseen = ?,
2083 stack = ?,
2084 notforloan = ?,
2085 damaged = ?,
2086 itemlost = ?,
2087 withdrawn = ?,
2088 itemcallnumber = ?,
2089 coded_location_qualifier = ?,
2090 restricted = ?,
2091 itemnotes = ?,
2092 itemnotes_nonpublic = ?,
2093 holdingbranch = ?,
2094 paidfor = ?,
2095 location = ?,
2096 permanent_location = ?,
2097 onloan = ?,
2098 issues = ?,
2099 renewals = ?,
2100 reserves = ?,
2101 cn_source = ?,
2102 cn_sort = ?,
2103 ccode = ?,
2104 itype = ?,
2105 materials = ?,
2106 uri = ?,
2107 enumchron = ?,
2108 more_subfields_xml = ?,
2109 copynumber = ?,
2110 stocknumber = ?,
2111 new_status = ?
2113 my $sth = $dbh->prepare($query);
2114 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2115 $sth->execute(
2116 $item->{'biblionumber'},
2117 $item->{'biblioitemnumber'},
2118 $barcode,
2119 $item->{'dateaccessioned'},
2120 $item->{'booksellerid'},
2121 $item->{'homebranch'},
2122 $item->{'price'},
2123 $item->{'replacementprice'},
2124 $item->{'replacementpricedate'} || $today,
2125 $item->{datelastborrowed},
2126 $item->{datelastseen} || $today,
2127 $item->{stack},
2128 $item->{'notforloan'},
2129 $item->{'damaged'},
2130 $item->{'itemlost'},
2131 $item->{'withdrawn'},
2132 $item->{'itemcallnumber'},
2133 $item->{'coded_location_qualifier'},
2134 $item->{'restricted'},
2135 $item->{'itemnotes'},
2136 $item->{'itemnotes_nonpublic'},
2137 $item->{'holdingbranch'},
2138 $item->{'paidfor'},
2139 $item->{'location'},
2140 $item->{'permanent_location'},
2141 $item->{'onloan'},
2142 $item->{'issues'},
2143 $item->{'renewals'},
2144 $item->{'reserves'},
2145 $item->{'items.cn_source'},
2146 $item->{'items.cn_sort'},
2147 $item->{'ccode'},
2148 $item->{'itype'},
2149 $item->{'materials'},
2150 $item->{'uri'},
2151 $item->{'enumchron'},
2152 $item->{'more_subfields_xml'},
2153 $item->{'copynumber'},
2154 $item->{'stocknumber'},
2155 $item->{'new_status'},
2158 my $itemnumber;
2159 if ( defined $sth->errstr ) {
2160 $error.="ERROR in _koha_new_item $query".$sth->errstr;
2162 else {
2163 $itemnumber = $dbh->{'mysql_insertid'};
2166 return ( $itemnumber, $error );
2169 =head2 MoveItemFromBiblio
2171 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2173 Moves an item from a biblio to another
2175 Returns undef if the move failed or the biblionumber of the destination record otherwise
2177 =cut
2179 sub MoveItemFromBiblio {
2180 my ($itemnumber, $frombiblio, $tobiblio) = @_;
2181 my $dbh = C4::Context->dbh;
2182 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
2183 SELECT biblioitemnumber
2184 FROM biblioitems
2185 WHERE biblionumber = ?
2186 |, undef, $tobiblio );
2187 my $return = $dbh->do(q|
2188 UPDATE items
2189 SET biblioitemnumber = ?,
2190 biblionumber = ?
2191 WHERE itemnumber = ?
2192 AND biblionumber = ?
2193 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2194 if ($return == 1) {
2195 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2196 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2197 # Checking if the item we want to move is in an order
2198 require C4::Acquisition;
2199 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2200 if ($order) {
2201 # Replacing the biblionumber within the order if necessary
2202 $order->{'biblionumber'} = $tobiblio;
2203 C4::Acquisition::ModOrder($order);
2206 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2207 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2208 $dbh->do( qq|
2209 UPDATE $table_name
2210 SET biblionumber = ?
2211 WHERE itemnumber = ?
2212 |, undef, $tobiblio, $itemnumber );
2214 return $tobiblio;
2216 return;
2219 =head2 DelItemCheck
2221 DelItemCheck($dbh, $biblionumber, $itemnumber);
2223 Exported function (core API) for deleting an item record in Koha if there no current issue.
2225 =cut
2227 sub DelItemCheck {
2228 my ( $dbh, $biblionumber, $itemnumber ) = @_;
2230 $dbh ||= C4::Context->dbh;
2232 my $error;
2234 my $countanalytics=GetAnalyticsCount($itemnumber);
2237 # check that there is no issue on this item before deletion.
2238 my $sth = $dbh->prepare(q{
2239 SELECT COUNT(*) FROM issues
2240 WHERE itemnumber = ?
2242 $sth->execute($itemnumber);
2243 my ($onloan) = $sth->fetchrow;
2245 my $item = GetItem($itemnumber);
2247 if ($onloan){
2248 $error = "book_on_loan"
2250 elsif ( defined C4::Context->userenv
2251 and !C4::Context->IsSuperLibrarian()
2252 and C4::Context->preference("IndependentBranches")
2253 and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2255 $error = "not_same_branch";
2257 else{
2258 # check it doesn't have a waiting reserve
2259 $sth = $dbh->prepare(q{
2260 SELECT COUNT(*) FROM reserves
2261 WHERE (found = 'W' OR found = 'T')
2262 AND itemnumber = ?
2264 $sth->execute($itemnumber);
2265 my ($reserve) = $sth->fetchrow;
2266 if ($reserve){
2267 $error = "book_reserved";
2268 } elsif ($countanalytics > 0){
2269 $error = "linked_analytics";
2270 } else {
2271 DelItem(
2273 biblionumber => $biblionumber,
2274 itemnumber => $itemnumber
2277 return 1;
2280 return $error;
2283 =head2 _koha_modify_item
2285 my ($itemnumber,$error) =_koha_modify_item( $item );
2287 Perform the actual update of the C<items> row. Note that this
2288 routine accepts a hashref specifying the columns to update.
2290 =cut
2292 sub _koha_modify_item {
2293 my ( $item ) = @_;
2294 my $dbh=C4::Context->dbh;
2295 my $error;
2297 my $query = "UPDATE items SET ";
2298 my @bind;
2299 for my $key ( keys %$item ) {
2300 next if ( $key eq 'itemnumber' );
2301 $query.="$key=?,";
2302 push @bind, $item->{$key};
2304 $query =~ s/,$//;
2305 $query .= " WHERE itemnumber=?";
2306 push @bind, $item->{'itemnumber'};
2307 my $sth = $dbh->prepare($query);
2308 $sth->execute(@bind);
2309 if ( $sth->err ) {
2310 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2311 warn $error;
2313 return ($item->{'itemnumber'},$error);
2316 =head2 _koha_delete_item
2318 _koha_delete_item( $itemnum );
2320 Internal function to delete an item record from the koha tables
2322 =cut
2324 sub _koha_delete_item {
2325 my ( $itemnum ) = @_;
2327 my $dbh = C4::Context->dbh;
2328 # save the deleted item to deleteditems table
2329 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2330 $sth->execute($itemnum);
2331 my $data = $sth->fetchrow_hashref();
2333 # There is no item to delete
2334 return 0 unless $data;
2336 my $query = "INSERT INTO deleteditems SET ";
2337 my @bind = ();
2338 foreach my $key ( keys %$data ) {
2339 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2340 $query .= "$key = ?,";
2341 push( @bind, $data->{$key} );
2343 $query =~ s/\,$//;
2344 $sth = $dbh->prepare($query);
2345 $sth->execute(@bind);
2347 # delete from items table
2348 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2349 my $deleted = $sth->execute($itemnum);
2350 return ( $deleted == 1 ) ? 1 : 0;
2353 =head2 _marc_from_item_hash
2355 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2357 Given an item hash representing a complete item record,
2358 create a C<MARC::Record> object containing an embedded
2359 tag representing that item.
2361 The third, optional parameter C<$unlinked_item_subfields> is
2362 an arrayref of subfields (not mapped to C<items> fields per the
2363 framework) to be added to the MARC representation
2364 of the item.
2366 =cut
2368 sub _marc_from_item_hash {
2369 my $item = shift;
2370 my $frameworkcode = shift;
2371 my $unlinked_item_subfields;
2372 if (@_) {
2373 $unlinked_item_subfields = shift;
2376 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2377 # Also, don't emit a subfield if the underlying field is blank.
2378 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2379 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2380 : () } keys %{ $item } };
2382 my $item_marc = MARC::Record->new();
2383 foreach my $item_field ( keys %{$mungeditem} ) {
2384 my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2385 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2386 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2387 foreach my $value (@values){
2388 if ( my $field = $item_marc->field($tag) ) {
2389 $field->add_subfields( $subfield => $value );
2390 } else {
2391 my $add_subfields = [];
2392 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2393 $add_subfields = $unlinked_item_subfields;
2395 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2400 return $item_marc;
2403 =head2 _repack_item_errors
2405 Add an error message hash generated by C<CheckItemPreSave>
2406 to a list of errors.
2408 =cut
2410 sub _repack_item_errors {
2411 my $item_sequence_num = shift;
2412 my $item_ref = shift;
2413 my $error_ref = shift;
2415 my @repacked_errors = ();
2417 foreach my $error_code (sort keys %{ $error_ref }) {
2418 my $repacked_error = {};
2419 $repacked_error->{'item_sequence'} = $item_sequence_num;
2420 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2421 $repacked_error->{'error_code'} = $error_code;
2422 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2423 push @repacked_errors, $repacked_error;
2426 return @repacked_errors;
2429 =head2 _get_unlinked_item_subfields
2431 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2433 =cut
2435 sub _get_unlinked_item_subfields {
2436 my $original_item_marc = shift;
2437 my $frameworkcode = shift;
2439 my $marcstructure = GetMarcStructure(1, $frameworkcode);
2441 # assume that this record has only one field, and that that
2442 # field contains only the item information
2443 my $subfields = [];
2444 my @fields = $original_item_marc->fields();
2445 if ($#fields > -1) {
2446 my $field = $fields[0];
2447 my $tag = $field->tag();
2448 foreach my $subfield ($field->subfields()) {
2449 if (defined $subfield->[1] and
2450 $subfield->[1] ne '' and
2451 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2452 push @$subfields, $subfield->[0] => $subfield->[1];
2456 return $subfields;
2459 =head2 _get_unlinked_subfields_xml
2461 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2463 =cut
2465 sub _get_unlinked_subfields_xml {
2466 my $unlinked_item_subfields = shift;
2468 my $xml;
2469 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2470 my $marc = MARC::Record->new();
2471 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2472 # used in the framework
2473 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2474 $marc->encoding("UTF-8");
2475 $xml = $marc->as_xml("USMARC");
2478 return $xml;
2481 =head2 _parse_unlinked_item_subfields_from_xml
2483 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2485 =cut
2487 sub _parse_unlinked_item_subfields_from_xml {
2488 my $xml = shift;
2489 require C4::Charset;
2490 return unless defined $xml and $xml ne "";
2491 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2492 my $unlinked_subfields = [];
2493 my @fields = $marc->fields();
2494 if ($#fields > -1) {
2495 foreach my $subfield ($fields[0]->subfields()) {
2496 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2499 return $unlinked_subfields;
2502 =head2 GetAnalyticsCount
2504 $count= &GetAnalyticsCount($itemnumber)
2506 counts Usage of itemnumber in Analytical bibliorecords.
2508 =cut
2510 sub GetAnalyticsCount {
2511 my ($itemnumber) = @_;
2513 ### ZOOM search here
2514 my $query;
2515 $query= "hi=".$itemnumber;
2516 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2517 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2518 return ($result);
2521 =head2 GetItemHolds
2523 $holds = &GetItemHolds($biblionumber, $itemnumber);
2525 This function return the count of holds with $biblionumber and $itemnumber
2527 =cut
2529 sub GetItemHolds {
2530 my ($biblionumber, $itemnumber) = @_;
2531 my $holds;
2532 my $dbh = C4::Context->dbh;
2533 my $query = "SELECT count(*)
2534 FROM reserves
2535 WHERE biblionumber=? AND itemnumber=?";
2536 my $sth = $dbh->prepare($query);
2537 $sth->execute($biblionumber, $itemnumber);
2538 $holds = $sth->fetchrow;
2539 return $holds;
2542 =head2 SearchItemsByField
2544 my $items = SearchItemsByField($field, $value);
2546 SearchItemsByField will search for items on a specific given field.
2547 For instance you can search all items with a specific stocknumber like this:
2549 my $items = SearchItemsByField('stocknumber', $stocknumber);
2551 =cut
2553 sub SearchItemsByField {
2554 my ($field, $value) = @_;
2556 my $filters = {
2557 field => $field,
2558 query => $value,
2561 my ($results) = SearchItems($filters);
2562 return $results;
2565 sub _SearchItems_build_where_fragment {
2566 my ($filter) = @_;
2568 my $dbh = C4::Context->dbh;
2570 my $where_fragment;
2571 if (exists($filter->{conjunction})) {
2572 my (@where_strs, @where_args);
2573 foreach my $f (@{ $filter->{filters} }) {
2574 my $fragment = _SearchItems_build_where_fragment($f);
2575 if ($fragment) {
2576 push @where_strs, $fragment->{str};
2577 push @where_args, @{ $fragment->{args} };
2580 my $where_str = '';
2581 if (@where_strs) {
2582 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2583 $where_fragment = {
2584 str => $where_str,
2585 args => \@where_args,
2588 } else {
2589 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2590 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2591 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2592 my @operators = qw(= != > < >= <= like);
2593 my $field = $filter->{field};
2594 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2595 my $op = $filter->{operator};
2596 my $query = $filter->{query};
2598 if (!$op or (0 == grep /^$op$/, @operators)) {
2599 $op = '='; # default operator
2602 my $column;
2603 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2604 my $marcfield = $1;
2605 my $marcsubfield = $2;
2606 my ($kohafield) = $dbh->selectrow_array(q|
2607 SELECT kohafield FROM marc_subfield_structure
2608 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2609 |, undef, $marcfield, $marcsubfield);
2611 if ($kohafield) {
2612 $column = $kohafield;
2613 } else {
2614 # MARC field is not linked to a DB field so we need to use
2615 # ExtractValue on biblioitems.marcxml or
2616 # items.more_subfields_xml, depending on the MARC field.
2617 my $xpath;
2618 my $sqlfield;
2619 my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2620 if ($marcfield eq $itemfield) {
2621 $sqlfield = 'more_subfields_xml';
2622 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2623 } else {
2624 $sqlfield = 'marcxml';
2625 if ($marcfield < 10) {
2626 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2627 } else {
2628 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2631 $column = "ExtractValue($sqlfield, '$xpath')";
2633 } else {
2634 $column = $field;
2637 if (ref $query eq 'ARRAY') {
2638 if ($op eq '=') {
2639 $op = 'IN';
2640 } elsif ($op eq '!=') {
2641 $op = 'NOT IN';
2643 $where_fragment = {
2644 str => "$column $op (" . join (',', ('?') x @$query) . ")",
2645 args => $query,
2647 } else {
2648 $where_fragment = {
2649 str => "$column $op ?",
2650 args => [ $query ],
2656 return $where_fragment;
2659 =head2 SearchItems
2661 my ($items, $total) = SearchItems($filter, $params);
2663 Perform a search among items
2665 $filter is a reference to a hash which can be a filter, or a combination of filters.
2667 A filter has the following keys:
2669 =over 2
2671 =item * field: the name of a SQL column in table items
2673 =item * query: the value to search in this column
2675 =item * operator: comparison operator. Can be one of = != > < >= <= like
2677 =back
2679 A combination of filters hash the following keys:
2681 =over 2
2683 =item * conjunction: 'AND' or 'OR'
2685 =item * filters: array ref of filters
2687 =back
2689 $params is a reference to a hash that can contain the following parameters:
2691 =over 2
2693 =item * rows: Number of items to return. 0 returns everything (default: 0)
2695 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2696 (default: 1)
2698 =item * sortby: A SQL column name in items table to sort on
2700 =item * sortorder: 'ASC' or 'DESC'
2702 =back
2704 =cut
2706 sub SearchItems {
2707 my ($filter, $params) = @_;
2709 $filter //= {};
2710 $params //= {};
2711 return unless ref $filter eq 'HASH';
2712 return unless ref $params eq 'HASH';
2714 # Default parameters
2715 $params->{rows} ||= 0;
2716 $params->{page} ||= 1;
2717 $params->{sortby} ||= 'itemnumber';
2718 $params->{sortorder} ||= 'ASC';
2720 my ($where_str, @where_args);
2721 my $where_fragment = _SearchItems_build_where_fragment($filter);
2722 if ($where_fragment) {
2723 $where_str = $where_fragment->{str};
2724 @where_args = @{ $where_fragment->{args} };
2727 my $dbh = C4::Context->dbh;
2728 my $query = q{
2729 SELECT SQL_CALC_FOUND_ROWS items.*
2730 FROM items
2731 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2732 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2734 if (defined $where_str and $where_str ne '') {
2735 $query .= qq{ WHERE $where_str };
2738 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2739 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2740 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2741 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2742 ? $params->{sortby} : 'itemnumber';
2743 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2744 $query .= qq{ ORDER BY $sortby $sortorder };
2746 my $rows = $params->{rows};
2747 my @limit_args;
2748 if ($rows > 0) {
2749 my $offset = $rows * ($params->{page}-1);
2750 $query .= qq { LIMIT ?, ? };
2751 push @limit_args, $offset, $rows;
2754 my $sth = $dbh->prepare($query);
2755 my $rv = $sth->execute(@where_args, @limit_args);
2757 return unless ($rv);
2758 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2760 return ($sth->fetchall_arrayref({}), $total_rows);
2764 =head1 OTHER FUNCTIONS
2766 =head2 _find_value
2768 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2770 Find the given $subfield in the given $tag in the given
2771 MARC::Record $record. If the subfield is found, returns
2772 the (indicators, value) pair; otherwise, (undef, undef) is
2773 returned.
2775 PROPOSITION :
2776 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2777 I suggest we export it from this module.
2779 =cut
2781 sub _find_value {
2782 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2783 my @result;
2784 my $indicator;
2785 if ( $tagfield < 10 ) {
2786 if ( $record->field($tagfield) ) {
2787 push @result, $record->field($tagfield)->data();
2788 } else {
2789 push @result, "";
2791 } else {
2792 foreach my $field ( $record->field($tagfield) ) {
2793 my @subfields = $field->subfields();
2794 foreach my $subfield (@subfields) {
2795 if ( @$subfield[0] eq $insubfield ) {
2796 push @result, @$subfield[1];
2797 $indicator = $field->indicator(1) . $field->indicator(2);
2802 return ( $indicator, @result );
2806 =head2 PrepareItemrecordDisplay
2808 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2810 Returns a hash with all the fields for Display a given item data in a template
2812 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2814 =cut
2816 sub PrepareItemrecordDisplay {
2818 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2820 my $dbh = C4::Context->dbh;
2821 $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2822 my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2823 my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2825 # return nothing if we don't have found an existing framework.
2826 return q{} unless $tagslib;
2827 my $itemrecord;
2828 if ($itemnum) {
2829 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2831 my @loop_data;
2833 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2834 my $query = qq{
2835 SELECT authorised_value,lib FROM authorised_values
2837 $query .= qq{
2838 LEFT JOIN authorised_values_branches ON ( id = av_id )
2839 } if $branch_limit;
2840 $query .= qq{
2841 WHERE category = ?
2843 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2844 $query .= qq{ ORDER BY lib};
2845 my $authorised_values_sth = $dbh->prepare( $query );
2846 foreach my $tag ( sort keys %{$tagslib} ) {
2847 my $previous_tag = '';
2848 if ( $tag ne '' ) {
2850 # loop through each subfield
2851 my $cntsubf;
2852 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2853 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2854 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2855 my %subfield_data;
2856 $subfield_data{tag} = $tag;
2857 $subfield_data{subfield} = $subfield;
2858 $subfield_data{countsubfield} = $cntsubf++;
2859 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2860 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2862 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2863 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2864 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2865 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2866 $subfield_data{hidden} = "display:none"
2867 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2868 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2869 my ( $x, $defaultvalue );
2870 if ($itemrecord) {
2871 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2873 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2874 if ( !defined $defaultvalue ) {
2875 $defaultvalue = q||;
2876 } else {
2877 $defaultvalue =~ s/"/&quot;/g;
2880 # search for itemcallnumber if applicable
2881 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2882 && C4::Context->preference('itemcallnumber') ) {
2883 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2884 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2885 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2886 $defaultvalue = $field->subfield($CNsubfield);
2889 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2890 && $defaultvalues
2891 && $defaultvalues->{'callnumber'} ) {
2892 if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2893 # if the item record exists, only use default value if the item has no callnumber
2894 $defaultvalue = $defaultvalues->{callnumber};
2895 } elsif ( !$itemrecord and $defaultvalues ) {
2896 # if the item record *doesn't* exists, always use the default value
2897 $defaultvalue = $defaultvalues->{callnumber};
2900 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2901 && $defaultvalues
2902 && $defaultvalues->{'branchcode'} ) {
2903 if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2904 $defaultvalue = $defaultvalues->{branchcode};
2907 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2908 && $defaultvalues
2909 && $defaultvalues->{'location'} ) {
2911 if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2912 # if the item record exists, only use default value if the item has no locationr
2913 $defaultvalue = $defaultvalues->{location};
2914 } elsif ( !$itemrecord and $defaultvalues ) {
2915 # if the item record *doesn't* exists, always use the default value
2916 $defaultvalue = $defaultvalues->{location};
2919 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2920 my @authorised_values;
2921 my %authorised_lib;
2923 # builds list, depending on authorised value...
2924 #---- branch
2925 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2926 if ( ( C4::Context->preference("IndependentBranches") )
2927 && !C4::Context->IsSuperLibrarian() ) {
2928 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2929 $sth->execute( C4::Context->userenv->{branch} );
2930 push @authorised_values, ""
2931 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2932 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2933 push @authorised_values, $branchcode;
2934 $authorised_lib{$branchcode} = $branchname;
2936 } else {
2937 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2938 $sth->execute;
2939 push @authorised_values, ""
2940 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2941 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2942 push @authorised_values, $branchcode;
2943 $authorised_lib{$branchcode} = $branchname;
2947 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2948 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2949 $defaultvalue = $defaultvalues->{branchcode};
2952 #----- itemtypes
2953 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2954 my $itemtypes = GetItemTypes( style => 'array' );
2955 push @authorised_values, ""
2956 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2957 for my $itemtype ( @$itemtypes ) {
2958 push @authorised_values, $itemtype->{itemtype};
2959 $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
2961 #---- class_sources
2962 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2963 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2965 my $class_sources = GetClassSources();
2966 my $default_source = C4::Context->preference("DefaultClassificationSource");
2968 foreach my $class_source (sort keys %$class_sources) {
2969 next unless $class_sources->{$class_source}->{'used'} or
2970 ($class_source eq $default_source);
2971 push @authorised_values, $class_source;
2972 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2975 $defaultvalue = $default_source;
2977 #---- "true" authorised value
2978 } else {
2979 $authorised_values_sth->execute(
2980 $tagslib->{$tag}->{$subfield}->{authorised_value},
2981 $branch_limit ? $branch_limit : ()
2983 push @authorised_values, ""
2984 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2985 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2986 push @authorised_values, $value;
2987 $authorised_lib{$value} = $lib;
2990 $subfield_data{marc_value} = {
2991 type => 'select',
2992 values => \@authorised_values,
2993 default => "$defaultvalue",
2994 labels => \%authorised_lib,
2996 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2997 # it is a plugin
2998 require Koha::FrameworkPlugin;
2999 my $plugin = Koha::FrameworkPlugin->new({
3000 name => $tagslib->{$tag}->{$subfield}->{value_builder},
3001 item_style => 1,
3003 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
3004 $plugin->build( $pars );
3005 if( !$plugin->errstr ) {
3006 #TODO Move html to template; see report 12176/13397
3007 my $tab= $plugin->noclick? '-1': '';
3008 my $class= $plugin->noclick? ' disabled': '';
3009 my $title= $plugin->noclick? 'No popup': 'Tag editor';
3010 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
3011 } else {
3012 warn $plugin->errstr;
3013 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" />); # supply default input form
3016 elsif ( $tag eq '' ) { # it's an hidden field
3017 $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" />);
3019 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
3020 $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" />);
3022 elsif ( length($defaultvalue) > 100
3023 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3024 300 <= $tag && $tag < 400 && $subfield eq 'a' )
3025 or (C4::Context->preference("marcflavour") eq "MARC21" and
3026 500 <= $tag && $tag < 600 )
3028 # oversize field (textarea)
3029 $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");
3030 } else {
3031 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3033 push( @loop_data, \%subfield_data );
3037 my $itemnumber;
3038 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3039 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3041 return {
3042 'itemtagfield' => $itemtagfield,
3043 'itemtagsubfield' => $itemtagsubfield,
3044 'itemnumber' => $itemnumber,
3045 'iteminformation' => \@loop_data
3049 =head2 columns
3051 my @columns = C4::Items::columns();
3053 Returns an array of items' table columns on success,
3054 and an empty array on failure.
3056 =cut
3058 sub columns {
3059 my $rs = Koha::Database->new->schema->resultset('Item');
3060 return $rs->result_source->columns;
3063 =head2 biblioitems_columns
3065 my @columns = C4::Items::biblioitems_columns();
3067 Returns an array of biblioitems' table columns on success,
3068 and an empty array on failure.
3070 =cut
3072 sub biblioitems_columns {
3073 my $rs = Koha::Database->new->schema->resultset('Biblioitem');
3074 return $rs->result_source->columns;
3077 sub ToggleNewStatus {
3078 my ( $params ) = @_;
3079 my @rules = @{ $params->{rules} };
3080 my $report_only = $params->{report_only};
3082 my $dbh = C4::Context->dbh;
3083 my @errors;
3084 my @item_columns = map { "items.$_" } C4::Items::columns;
3085 my @biblioitem_columns = map { "biblioitems.$_" } C4::Items::biblioitems_columns;
3086 my $report;
3087 for my $rule ( @rules ) {
3088 my $age = $rule->{age};
3089 my $conditions = $rule->{conditions};
3090 my $substitutions = $rule->{substitutions};
3091 my @params;
3093 my $query = q|
3094 SELECT items.biblionumber, items.itemnumber
3095 FROM items
3096 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
3097 WHERE 1
3099 for my $condition ( @$conditions ) {
3100 if (
3101 grep {/^$condition->{field}$/} @item_columns
3102 or grep {/^$condition->{field}$/} @biblioitem_columns
3104 if ( $condition->{value} =~ /\|/ ) {
3105 my @values = split /\|/, $condition->{value};
3106 $query .= qq| AND $condition->{field} IN (|
3107 . join( ',', ('?') x scalar @values )
3108 . q|)|;
3109 push @params, @values;
3110 } else {
3111 $query .= qq| AND $condition->{field} = ?|;
3112 push @params, $condition->{value};
3116 if ( defined $age ) {
3117 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
3118 push @params, $age;
3120 my $sth = $dbh->prepare($query);
3121 $sth->execute( @params );
3122 while ( my $values = $sth->fetchrow_hashref ) {
3123 my $biblionumber = $values->{biblionumber};
3124 my $itemnumber = $values->{itemnumber};
3125 my $item = C4::Items::GetItem( $itemnumber );
3126 for my $substitution ( @$substitutions ) {
3127 next unless $substitution->{field};
3128 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
3129 unless $report_only;
3130 push @{ $report->{$itemnumber} }, $substitution;
3135 return $report;