Bug 8977:replace unitialized directory var in printoverdues
[koha.git] / C4 / Items.pm
blob253078d4b53276cba7097f2057324772aea9597b
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 under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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 C4::Dates qw/format_date format_date_in_iso/;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use Data::Dumper; # used as part of logging item record changes, not just for
34 # debugging; so please don't remove this
36 use vars qw($VERSION @ISA @EXPORT);
38 BEGIN {
39 $VERSION = 3.07.00.049;
41 require Exporter;
42 @ISA = qw( Exporter );
44 # function exports
45 @EXPORT = qw(
46 GetItem
47 AddItemFromMarc
48 AddItem
49 AddItemBatchFromMarc
50 ModItemFromMarc
51 Item2Marc
52 ModItem
53 ModDateLastSeen
54 ModItemTransfer
55 DelItem
57 CheckItemPreSave
59 GetItemStatus
60 GetItemLocation
61 GetLostItems
62 GetItemsForInventory
63 GetItemsCount
64 GetItemInfosOf
65 GetItemsByBiblioitemnumber
66 GetItemsInfo
67 GetItemsLocationInfo
68 GetHostItemsInfo
69 GetItemnumbersForBiblio
70 get_itemnumbers_of
71 get_hostitemnumbers_of
72 GetItemnumberFromBarcode
73 GetBarcodeFromItemnumber
74 GetHiddenItemnumbers
75 DelItemCheck
76 MoveItemFromBiblio
77 GetLatestAcquisitions
79 CartToShelf
80 ShelfToCart
82 GetAnalyticsCount
83 GetItemHolds
85 SearchItems
87 PrepareItemrecordDisplay
92 =head1 NAME
94 C4::Items - item management functions
96 =head1 DESCRIPTION
98 This module contains an API for manipulating item
99 records in Koha, and is used by cataloguing, circulation,
100 acquisitions, and serials management.
102 A Koha item record is stored in two places: the
103 items table and embedded in a MARC tag in the XML
104 version of the associated bib record in C<biblioitems.marcxml>.
105 This is done to allow the item information to be readily
106 indexed (e.g., by Zebra), but means that each item
107 modification transaction must keep the items table
108 and the MARC XML in sync at all times.
110 Consequently, all code that creates, modifies, or deletes
111 item records B<must> use an appropriate function from
112 C<C4::Items>. If no existing function is suitable, it is
113 better to add one to C<C4::Items> than to use add
114 one-off SQL statements to add or modify items.
116 The items table will be considered authoritative. In other
117 words, if there is ever a discrepancy between the items
118 table and the MARC XML, the items table should be considered
119 accurate.
121 =head1 HISTORICAL NOTE
123 Most of the functions in C<C4::Items> were originally in
124 the C<C4::Biblio> module.
126 =head1 CORE EXPORTED FUNCTIONS
128 The following functions are meant for use by users
129 of C<C4::Items>
131 =cut
133 =head2 GetItem
135 $item = GetItem($itemnumber,$barcode,$serial);
137 Return item information, for a given itemnumber or barcode.
138 The return value is a hashref mapping item column
139 names to values. If C<$serial> is true, include serial publication data.
141 =cut
143 sub GetItem {
144 my ($itemnumber,$barcode, $serial) = @_;
145 my $dbh = C4::Context->dbh;
146 my $data;
147 if ($itemnumber) {
148 my $sth = $dbh->prepare("
149 SELECT * FROM items
150 WHERE itemnumber = ?");
151 $sth->execute($itemnumber);
152 $data = $sth->fetchrow_hashref;
153 } else {
154 my $sth = $dbh->prepare("
155 SELECT * FROM items
156 WHERE barcode = ?"
158 $sth->execute($barcode);
159 $data = $sth->fetchrow_hashref;
161 if ( $serial) {
162 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
163 $ssth->execute($data->{'itemnumber'}) ;
164 ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
166 #if we don't have an items.itype, use biblioitems.itemtype.
167 if( ! $data->{'itype'} ) {
168 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
169 $sth->execute($data->{'biblionumber'});
170 ($data->{'itype'}) = $sth->fetchrow_array;
172 return $data;
173 } # sub GetItem
175 =head2 CartToShelf
177 CartToShelf($itemnumber);
179 Set the current shelving location of the item record
180 to its stored permanent shelving location. This is
181 primarily used to indicate when an item whose current
182 location is a special processing ('PROC') or shelving cart
183 ('CART') location is back in the stacks.
185 =cut
187 sub CartToShelf {
188 my ( $itemnumber ) = @_;
190 unless ( $itemnumber ) {
191 croak "FAILED CartToShelf() - no itemnumber supplied";
194 my $item = GetItem($itemnumber);
195 if ( $item->{location} eq 'CART' ) {
196 $item->{location} = $item->{permanent_location};
197 ModItem($item, undef, $itemnumber);
201 =head2 ShelfToCart
203 ShelfToCart($itemnumber);
205 Set the current shelving location of the item
206 to shelving cart ('CART').
208 =cut
210 sub ShelfToCart {
211 my ( $itemnumber ) = @_;
213 unless ( $itemnumber ) {
214 croak "FAILED ShelfToCart() - no itemnumber supplied";
217 my $item = GetItem($itemnumber);
218 $item->{'location'} = 'CART';
219 ModItem($item, undef, $itemnumber);
222 =head2 AddItemFromMarc
224 my ($biblionumber, $biblioitemnumber, $itemnumber)
225 = AddItemFromMarc($source_item_marc, $biblionumber);
227 Given a MARC::Record object containing an embedded item
228 record and a biblionumber, create a new item record.
230 =cut
232 sub AddItemFromMarc {
233 my ( $source_item_marc, $biblionumber ) = @_;
234 my $dbh = C4::Context->dbh;
236 # parse item hash from MARC
237 my $frameworkcode = GetFrameworkCode( $biblionumber );
238 my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
240 my $localitemmarc=MARC::Record->new;
241 $localitemmarc->append_fields($source_item_marc->field($itemtag));
242 my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
243 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
244 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
247 =head2 AddItem
249 my ($biblionumber, $biblioitemnumber, $itemnumber)
250 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
252 Given a hash containing item column names as keys,
253 create a new Koha item record.
255 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
256 do not need to be supplied for general use; they exist
257 simply to allow them to be picked up from AddItemFromMarc.
259 The final optional parameter, C<$unlinked_item_subfields>, contains
260 an arrayref containing subfields present in the original MARC
261 representation of the item (e.g., from the item editor) that are
262 not mapped to C<items> columns directly but should instead
263 be stored in C<items.more_subfields_xml> and included in
264 the biblio items tag for display and indexing.
266 =cut
268 sub AddItem {
269 my $item = shift;
270 my $biblionumber = shift;
272 my $dbh = @_ ? shift : C4::Context->dbh;
273 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
274 my $unlinked_item_subfields;
275 if (@_) {
276 $unlinked_item_subfields = shift
279 # needs old biblionumber and biblioitemnumber
280 $item->{'biblionumber'} = $biblionumber;
281 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
282 $sth->execute( $item->{'biblionumber'} );
283 ($item->{'biblioitemnumber'}) = $sth->fetchrow;
285 _set_defaults_for_add($item);
286 _set_derived_columns_for_add($item);
287 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
288 # FIXME - checks here
289 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
290 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
291 $itype_sth->execute( $item->{'biblionumber'} );
292 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
295 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
296 $item->{'itemnumber'} = $itemnumber;
298 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
300 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
302 return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
305 =head2 AddItemBatchFromMarc
307 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
308 $biblionumber, $biblioitemnumber, $frameworkcode);
310 Efficiently create item records from a MARC biblio record with
311 embedded item fields. This routine is suitable for batch jobs.
313 This API assumes that the bib record has already been
314 saved to the C<biblio> and C<biblioitems> tables. It does
315 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
316 are populated, but it will do so via a call to ModBibiloMarc.
318 The goal of this API is to have a similar effect to using AddBiblio
319 and AddItems in succession, but without inefficient repeated
320 parsing of the MARC XML bib record.
322 This function returns an arrayref of new itemsnumbers and an arrayref of item
323 errors encountered during the processing. Each entry in the errors
324 list is a hashref containing the following keys:
326 =over
328 =item item_sequence
330 Sequence number of original item tag in the MARC record.
332 =item item_barcode
334 Item barcode, provide to assist in the construction of
335 useful error messages.
337 =item error_code
339 Code representing the error condition. Can be 'duplicate_barcode',
340 'invalid_homebranch', or 'invalid_holdingbranch'.
342 =item error_information
344 Additional information appropriate to the error condition.
346 =back
348 =cut
350 sub AddItemBatchFromMarc {
351 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
352 my $error;
353 my @itemnumbers = ();
354 my @errors = ();
355 my $dbh = C4::Context->dbh;
357 # We modify the record, so lets work on a clone so we don't change the
358 # original.
359 $record = $record->clone();
360 # loop through the item tags and start creating items
361 my @bad_item_fields = ();
362 my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
363 my $item_sequence_num = 0;
364 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
365 $item_sequence_num++;
366 # we take the item field and stick it into a new
367 # MARC record -- this is required so far because (FIXME)
368 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
369 # and there is no TransformMarcFieldToKoha
370 my $temp_item_marc = MARC::Record->new();
371 $temp_item_marc->append_fields($item_field);
373 # add biblionumber and biblioitemnumber
374 my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
375 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
376 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
377 $item->{'biblionumber'} = $biblionumber;
378 $item->{'biblioitemnumber'} = $biblioitemnumber;
380 # check for duplicate barcode
381 my %item_errors = CheckItemPreSave($item);
382 if (%item_errors) {
383 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
384 push @bad_item_fields, $item_field;
385 next ITEMFIELD;
388 _set_defaults_for_add($item);
389 _set_derived_columns_for_add($item);
390 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
391 warn $error if $error;
392 push @itemnumbers, $itemnumber; # FIXME not checking error
393 $item->{'itemnumber'} = $itemnumber;
395 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
397 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
398 $item_field->replace_with($new_item_marc->field($itemtag));
401 # remove any MARC item fields for rejected items
402 foreach my $item_field (@bad_item_fields) {
403 $record->delete_field($item_field);
406 # update the MARC biblio
407 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
409 return (\@itemnumbers, \@errors);
412 =head2 ModItemFromMarc
414 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
416 This function updates an item record based on a supplied
417 C<MARC::Record> object containing an embedded item field.
418 This API is meant for the use of C<additem.pl>; for
419 other purposes, C<ModItem> should be used.
421 This function uses the hash %default_values_for_mod_from_marc,
422 which contains default values for item fields to
423 apply when modifying an item. This is needed beccause
424 if an item field's value is cleared, TransformMarcToKoha
425 does not include the column in the
426 hash that's passed to ModItem, which without
427 use of this hash makes it impossible to clear
428 an item field's value. See bug 2466.
430 Note that only columns that can be directly
431 changed from the cataloging and serials
432 item editors are included in this hash.
434 Returns item record
436 =cut
438 my %default_values_for_mod_from_marc = (
439 barcode => undef,
440 booksellerid => undef,
441 ccode => undef,
442 'items.cn_source' => undef,
443 coded_location_qualifier => undef,
444 copynumber => undef,
445 damaged => 0,
446 # dateaccessioned => undef,
447 enumchron => undef,
448 holdingbranch => undef,
449 homebranch => undef,
450 itemcallnumber => undef,
451 itemlost => 0,
452 itemnotes => undef,
453 itype => undef,
454 location => undef,
455 permanent_location => undef,
456 materials => undef,
457 notforloan => 0,
458 paidfor => undef,
459 price => undef,
460 replacementprice => undef,
461 replacementpricedate => undef,
462 restricted => undef,
463 stack => undef,
464 stocknumber => undef,
465 uri => undef,
466 wthdrawn => 0,
469 sub ModItemFromMarc {
470 my $item_marc = shift;
471 my $biblionumber = shift;
472 my $itemnumber = shift;
474 my $dbh = C4::Context->dbh;
475 my $frameworkcode = GetFrameworkCode($biblionumber);
476 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
478 my $localitemmarc = MARC::Record->new;
479 $localitemmarc->append_fields( $item_marc->field($itemtag) );
480 my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' );
481 foreach my $item_field ( keys %default_values_for_mod_from_marc ) {
482 $item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless (exists $item->{$item_field});
484 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
486 ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
487 return $item;
490 =head2 ModItem
492 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
494 Change one or more columns in an item record and update
495 the MARC representation of the item.
497 The first argument is a hashref mapping from item column
498 names to the new values. The second and third arguments
499 are the biblionumber and itemnumber, respectively.
501 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
502 an arrayref containing subfields present in the original MARC
503 representation of the item (e.g., from the item editor) that are
504 not mapped to C<items> columns directly but should instead
505 be stored in C<items.more_subfields_xml> and included in
506 the biblio items tag for display and indexing.
508 If one of the changed columns is used to calculate
509 the derived value of a column such as C<items.cn_sort>,
510 this routine will perform the necessary calculation
511 and set the value.
513 =cut
515 sub ModItem {
516 my $item = shift;
517 my $biblionumber = shift;
518 my $itemnumber = shift;
520 # if $biblionumber is undefined, get it from the current item
521 unless (defined $biblionumber) {
522 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
525 my $dbh = @_ ? shift : C4::Context->dbh;
526 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
528 my $unlinked_item_subfields;
529 if (@_) {
530 $unlinked_item_subfields = shift;
531 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
534 $item->{'itemnumber'} = $itemnumber or return;
536 $item->{onloan} = undef if $item->{itemlost};
538 _set_derived_columns_for_mod($item);
539 _do_column_fixes_for_mod($item);
540 # FIXME add checks
541 # duplicate barcode
542 # attempt to change itemnumber
543 # attempt to change biblionumber (if we want
544 # an API to relink an item to a different bib,
545 # it should be a separate function)
547 # update items table
548 _koha_modify_item($item);
550 # request that bib be reindexed so that searching on current
551 # item status is possible
552 ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
554 logaction("CATALOGUING", "MODIFY", $itemnumber, Dumper($item)) if C4::Context->preference("CataloguingLog");
557 =head2 ModItemTransfer
559 ModItemTransfer($itenumber, $frombranch, $tobranch);
561 Marks an item as being transferred from one branch
562 to another.
564 =cut
566 sub ModItemTransfer {
567 my ( $itemnumber, $frombranch, $tobranch ) = @_;
569 my $dbh = C4::Context->dbh;
571 # Remove the 'shelving cart' location status if it is being used.
572 CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
574 #new entry in branchtransfers....
575 my $sth = $dbh->prepare(
576 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
577 VALUES (?, ?, NOW(), ?)");
578 $sth->execute($itemnumber, $frombranch, $tobranch);
580 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
581 ModDateLastSeen($itemnumber);
582 return;
585 =head2 ModDateLastSeen
587 ModDateLastSeen($itemnum);
589 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
590 C<$itemnum> is the item number
592 =cut
594 sub ModDateLastSeen {
595 my ($itemnumber) = @_;
597 my $today = C4::Dates->new();
598 ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
601 =head2 DelItem
603 DelItem($dbh, $biblionumber, $itemnumber);
605 Exported function (core API) for deleting an item record in Koha.
607 =cut
609 sub DelItem {
610 my ( $dbh, $biblionumber, $itemnumber ) = @_;
612 # FIXME check the item has no current issues
614 _koha_delete_item( $dbh, $itemnumber );
616 # get the MARC record
617 my $record = GetMarcBiblio($biblionumber);
618 ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
620 # backup the record
621 my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
622 $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
623 # This last update statement makes that the timestamp column in deleteditems is updated too. If you remove these lines, please add a line to update the timestamp separately. See Bugzilla report 7146 and Biblio.pm (DelBiblio).
625 #search item field code
626 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
629 =head2 CheckItemPreSave
631 my $item_ref = TransformMarcToKoha($marc, 'items');
632 # do stuff
633 my %errors = CheckItemPreSave($item_ref);
634 if (exists $errors{'duplicate_barcode'}) {
635 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
636 } elsif (exists $errors{'invalid_homebranch'}) {
637 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
638 } elsif (exists $errors{'invalid_holdingbranch'}) {
639 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
640 } else {
641 print "item is OK";
644 Given a hashref containing item fields, determine if it can be
645 inserted or updated in the database. Specifically, checks for
646 database integrity issues, and returns a hash containing any
647 of the following keys, if applicable.
649 =over 2
651 =item duplicate_barcode
653 Barcode, if it duplicates one already found in the database.
655 =item invalid_homebranch
657 Home branch, if not defined in branches table.
659 =item invalid_holdingbranch
661 Holding branch, if not defined in branches table.
663 =back
665 This function does NOT implement any policy-related checks,
666 e.g., whether current operator is allowed to save an
667 item that has a given branch code.
669 =cut
671 sub CheckItemPreSave {
672 my $item_ref = shift;
673 require C4::Branch;
675 my %errors = ();
677 # check for duplicate barcode
678 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
679 my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
680 if ($existing_itemnumber) {
681 if (!exists $item_ref->{'itemnumber'} # new item
682 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
683 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
688 # check for valid home branch
689 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
690 my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'});
691 unless (defined $branch_name) {
692 # relies on fact that branches.branchname is a non-NULL column,
693 # so GetBranchName returns undef only if branch does not exist
694 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
698 # check for valid holding branch
699 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
700 my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'});
701 unless (defined $branch_name) {
702 # relies on fact that branches.branchname is a non-NULL column,
703 # so GetBranchName returns undef only if branch does not exist
704 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
708 return %errors;
712 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
714 The following functions provide various ways of
715 getting an item record, a set of item records, or
716 lists of authorized values for certain item fields.
718 Some of the functions in this group are candidates
719 for refactoring -- for example, some of the code
720 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
721 has copy-and-paste work.
723 =cut
725 =head2 GetItemStatus
727 $itemstatushash = GetItemStatus($fwkcode);
729 Returns a list of valid values for the
730 C<items.notforloan> field.
732 NOTE: does B<not> return an individual item's
733 status.
735 Can be MARC dependant.
736 fwkcode is optional.
737 But basically could be can be loan or not
738 Create a status selector with the following code
740 =head3 in PERL SCRIPT
742 my $itemstatushash = getitemstatus;
743 my @itemstatusloop;
744 foreach my $thisstatus (keys %$itemstatushash) {
745 my %row =(value => $thisstatus,
746 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
748 push @itemstatusloop, \%row;
750 $template->param(statusloop=>\@itemstatusloop);
752 =head3 in TEMPLATE
754 <select name="statusloop">
755 <option value="">Default</option>
756 <!-- TMPL_LOOP name="statusloop" -->
757 <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
758 <!-- /TMPL_LOOP -->
759 </select>
761 =cut
763 sub GetItemStatus {
765 # returns a reference to a hash of references to status...
766 my ($fwk) = @_;
767 my %itemstatus;
768 my $dbh = C4::Context->dbh;
769 my $sth;
770 $fwk = '' unless ($fwk);
771 my ( $tag, $subfield ) =
772 GetMarcFromKohaField( "items.notforloan", $fwk );
773 if ( $tag and $subfield ) {
774 my $sth =
775 $dbh->prepare(
776 "SELECT authorised_value
777 FROM marc_subfield_structure
778 WHERE tagfield=?
779 AND tagsubfield=?
780 AND frameworkcode=?
783 $sth->execute( $tag, $subfield, $fwk );
784 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
785 my $authvalsth =
786 $dbh->prepare(
787 "SELECT authorised_value,lib
788 FROM authorised_values
789 WHERE category=?
790 ORDER BY lib
793 $authvalsth->execute($authorisedvaluecat);
794 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
795 $itemstatus{$authorisedvalue} = $lib;
797 return \%itemstatus;
798 exit 1;
800 else {
802 #No authvalue list
803 # build default
807 #No authvalue list
808 #build default
809 $itemstatus{"1"} = "Not For Loan";
810 return \%itemstatus;
813 =head2 GetItemLocation
815 $itemlochash = GetItemLocation($fwk);
817 Returns a list of valid values for the
818 C<items.location> field.
820 NOTE: does B<not> return an individual item's
821 location.
823 where fwk stands for an optional framework code.
824 Create a location selector with the following code
826 =head3 in PERL SCRIPT
828 my $itemlochash = getitemlocation;
829 my @itemlocloop;
830 foreach my $thisloc (keys %$itemlochash) {
831 my $selected = 1 if $thisbranch eq $branch;
832 my %row =(locval => $thisloc,
833 selected => $selected,
834 locname => $itemlochash->{$thisloc},
836 push @itemlocloop, \%row;
838 $template->param(itemlocationloop => \@itemlocloop);
840 =head3 in TEMPLATE
842 <select name="location">
843 <option value="">Default</option>
844 <!-- TMPL_LOOP name="itemlocationloop" -->
845 <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
846 <!-- /TMPL_LOOP -->
847 </select>
849 =cut
851 sub GetItemLocation {
853 # returns a reference to a hash of references to location...
854 my ($fwk) = @_;
855 my %itemlocation;
856 my $dbh = C4::Context->dbh;
857 my $sth;
858 $fwk = '' unless ($fwk);
859 my ( $tag, $subfield ) =
860 GetMarcFromKohaField( "items.location", $fwk );
861 if ( $tag and $subfield ) {
862 my $sth =
863 $dbh->prepare(
864 "SELECT authorised_value
865 FROM marc_subfield_structure
866 WHERE tagfield=?
867 AND tagsubfield=?
868 AND frameworkcode=?"
870 $sth->execute( $tag, $subfield, $fwk );
871 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
872 my $authvalsth =
873 $dbh->prepare(
874 "SELECT authorised_value,lib
875 FROM authorised_values
876 WHERE category=?
877 ORDER BY lib"
879 $authvalsth->execute($authorisedvaluecat);
880 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
881 $itemlocation{$authorisedvalue} = $lib;
883 return \%itemlocation;
884 exit 1;
886 else {
888 #No authvalue list
889 # build default
893 #No authvalue list
894 #build default
895 $itemlocation{"1"} = "Not For Loan";
896 return \%itemlocation;
899 =head2 GetLostItems
901 $items = GetLostItems( $where, $orderby );
903 This function gets a list of lost items.
905 =over 2
907 =item input:
909 C<$where> is a hashref. it containts a field of the items table as key
910 and the value to match as value. For example:
912 { barcode => 'abc123',
913 homebranch => 'CPL', }
915 C<$orderby> is a field of the items table by which the resultset
916 should be orderd.
918 =item return:
920 C<$items> is a reference to an array full of hashrefs with columns
921 from the "items" table as keys.
923 =item usage in the perl script:
925 my $where = { barcode => '0001548' };
926 my $items = GetLostItems( $where, "homebranch" );
927 $template->param( itemsloop => $items );
929 =back
931 =cut
933 sub GetLostItems {
934 # Getting input args.
935 my $where = shift;
936 my $orderby = shift;
937 my $dbh = C4::Context->dbh;
939 my $query = "
940 SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
941 itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber
942 FROM items
943 LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
944 LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
945 LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
946 WHERE
947 authorised_values.category = 'LOST'
948 AND itemlost IS NOT NULL
949 AND itemlost <> 0
951 my @query_parameters;
952 foreach my $key (keys %$where) {
953 $query .= " AND $key LIKE ?";
954 push @query_parameters, "%$where->{$key}%";
956 my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
958 if ( defined $orderby && grep($orderby, @ordervalues)) {
959 $query .= ' ORDER BY '.$orderby;
962 my $sth = $dbh->prepare($query);
963 $sth->execute( @query_parameters );
964 my $items = [];
965 while ( my $row = $sth->fetchrow_hashref ){
966 push @$items, $row;
968 return $items;
971 =head2 GetItemsForInventory
973 $itemlist = GetItemsForInventory($minlocation, $maxlocation,
974 $location, $itemtype $datelastseen, $branch,
975 $offset, $size, $statushash);
977 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
979 The sub returns a reference to a list of hashes, each containing
980 itemnumber, author, title, barcode, item callnumber, and date last
981 seen. It is ordered by callnumber then title.
983 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
984 the datelastseen can be used to specify that you want to see items not seen since a past date only.
985 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
986 $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.
988 =cut
990 sub GetItemsForInventory {
991 my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $branch, $offset, $size, $statushash ) = @_;
992 my $dbh = C4::Context->dbh;
993 my ( @bind_params, @where_strings );
995 my $query = <<'END_SQL';
996 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
997 FROM items
998 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
999 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1000 END_SQL
1001 if ($statushash){
1002 for my $authvfield (keys %$statushash){
1003 if ( scalar @{$statushash->{$authvfield}} > 0 ){
1004 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1005 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1010 if ($minlocation) {
1011 push @where_strings, 'itemcallnumber >= ?';
1012 push @bind_params, $minlocation;
1015 if ($maxlocation) {
1016 push @where_strings, 'itemcallnumber <= ?';
1017 push @bind_params, $maxlocation;
1020 if ($datelastseen) {
1021 $datelastseen = format_date_in_iso($datelastseen);
1022 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1023 push @bind_params, $datelastseen;
1026 if ( $location ) {
1027 push @where_strings, 'items.location = ?';
1028 push @bind_params, $location;
1031 if ( $branchcode ) {
1032 if($branch eq "homebranch"){
1033 push @where_strings, 'items.homebranch = ?';
1034 }else{
1035 push @where_strings, 'items.holdingbranch = ?';
1037 push @bind_params, $branchcode;
1040 if ( $itemtype ) {
1041 push @where_strings, 'biblioitems.itemtype = ?';
1042 push @bind_params, $itemtype;
1045 if ( $ignoreissued) {
1046 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1047 push @where_strings, 'issues.date_due IS NULL';
1050 if ( @where_strings ) {
1051 $query .= 'WHERE ';
1052 $query .= join ' AND ', @where_strings;
1054 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1055 my $sth = $dbh->prepare($query);
1056 $sth->execute( @bind_params );
1058 my @results;
1059 $size--;
1060 while ( my $row = $sth->fetchrow_hashref ) {
1061 $offset-- if ($offset);
1062 $row->{datelastseen}=format_date($row->{datelastseen});
1063 if ( ( !$offset ) && $size ) {
1064 push @results, $row;
1065 $size--;
1068 return \@results;
1071 =head2 GetItemsCount
1073 $count = &GetItemsCount( $biblionumber);
1075 This function return count of item with $biblionumber
1077 =cut
1079 sub GetItemsCount {
1080 my ( $biblionumber ) = @_;
1081 my $dbh = C4::Context->dbh;
1082 my $query = "SELECT count(*)
1083 FROM items
1084 WHERE biblionumber=?";
1085 my $sth = $dbh->prepare($query);
1086 $sth->execute($biblionumber);
1087 my $count = $sth->fetchrow;
1088 return ($count);
1091 =head2 GetItemInfosOf
1093 GetItemInfosOf(@itemnumbers);
1095 =cut
1097 sub GetItemInfosOf {
1098 my @itemnumbers = @_;
1100 my $query = '
1101 SELECT *
1102 FROM items
1103 WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1105 return get_infos_of( $query, 'itemnumber' );
1108 =head2 GetItemsByBiblioitemnumber
1110 GetItemsByBiblioitemnumber($biblioitemnumber);
1112 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1113 Called by C<C4::XISBN>
1115 =cut
1117 sub GetItemsByBiblioitemnumber {
1118 my ( $bibitem ) = @_;
1119 my $dbh = C4::Context->dbh;
1120 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1121 # Get all items attached to a biblioitem
1122 my $i = 0;
1123 my @results;
1124 $sth->execute($bibitem) || die $sth->errstr;
1125 while ( my $data = $sth->fetchrow_hashref ) {
1126 # Foreach item, get circulation information
1127 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1128 WHERE itemnumber = ?
1129 AND issues.borrowernumber = borrowers.borrowernumber"
1131 $sth2->execute( $data->{'itemnumber'} );
1132 if ( my $data2 = $sth2->fetchrow_hashref ) {
1133 # if item is out, set the due date and who it is out too
1134 $data->{'date_due'} = $data2->{'date_due'};
1135 $data->{'cardnumber'} = $data2->{'cardnumber'};
1136 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
1138 else {
1139 # set date_due to blank, so in the template we check itemlost, and wthdrawn
1140 $data->{'date_due'} = '';
1141 } # else
1142 # Find the last 3 people who borrowed this item.
1143 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1144 AND old_issues.borrowernumber = borrowers.borrowernumber
1145 ORDER BY returndate desc,timestamp desc LIMIT 3";
1146 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1147 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1148 my $i2 = 0;
1149 while ( my $data2 = $sth2->fetchrow_hashref ) {
1150 $data->{"timestamp$i2"} = $data2->{'timestamp'};
1151 $data->{"card$i2"} = $data2->{'cardnumber'};
1152 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
1153 $i2++;
1155 push(@results,$data);
1157 return (\@results);
1160 =head2 GetItemsInfo
1162 @results = GetItemsInfo($biblionumber);
1164 Returns information about items with the given biblionumber.
1166 C<GetItemsInfo> returns a list of references-to-hash. Each element
1167 contains a number of keys. Most of them are attributes from the
1168 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1169 Koha database. Other keys include:
1171 =over 2
1173 =item C<$data-E<gt>{branchname}>
1175 The name (not the code) of the branch to which the book belongs.
1177 =item C<$data-E<gt>{datelastseen}>
1179 This is simply C<items.datelastseen>, except that while the date is
1180 stored in YYYY-MM-DD format in the database, here it is converted to
1181 DD/MM/YYYY format. A NULL date is returned as C<//>.
1183 =item C<$data-E<gt>{datedue}>
1185 =item C<$data-E<gt>{class}>
1187 This is the concatenation of C<biblioitems.classification>, the book's
1188 Dewey code, and C<biblioitems.subclass>.
1190 =item C<$data-E<gt>{ocount}>
1192 I think this is the number of copies of the book available.
1194 =item C<$data-E<gt>{order}>
1196 If this is set, it is set to C<One Order>.
1198 =back
1200 =cut
1202 sub GetItemsInfo {
1203 my ( $biblionumber ) = @_;
1204 my $dbh = C4::Context->dbh;
1205 # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1206 my $query = "
1207 SELECT items.*,
1208 biblio.*,
1209 biblioitems.volume,
1210 biblioitems.number,
1211 biblioitems.itemtype,
1212 biblioitems.isbn,
1213 biblioitems.issn,
1214 biblioitems.publicationyear,
1215 biblioitems.publishercode,
1216 biblioitems.volumedate,
1217 biblioitems.volumedesc,
1218 biblioitems.lccn,
1219 biblioitems.url,
1220 items.notforloan as itemnotforloan,
1221 itemtypes.description,
1222 itemtypes.notforloan as notforloan_per_itemtype,
1223 holding.branchurl,
1224 holding.branchname,
1225 holding.opac_info as branch_opac_info
1226 FROM items
1227 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1228 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1229 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1230 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1231 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1232 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1233 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1234 my $sth = $dbh->prepare($query);
1235 $sth->execute($biblionumber);
1236 my $i = 0;
1237 my @results;
1238 my $serial;
1240 my $isth = $dbh->prepare(
1241 "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1242 FROM issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1243 WHERE itemnumber = ?"
1245 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? ");
1246 while ( my $data = $sth->fetchrow_hashref ) {
1247 my $datedue = '';
1248 $isth->execute( $data->{'itemnumber'} );
1249 if ( my $idata = $isth->fetchrow_hashref ) {
1250 $data->{borrowernumber} = $idata->{borrowernumber};
1251 $data->{cardnumber} = $idata->{cardnumber};
1252 $data->{surname} = $idata->{surname};
1253 $data->{firstname} = $idata->{firstname};
1254 $data->{lastreneweddate} = $idata->{lastreneweddate};
1255 $datedue = $idata->{'date_due'};
1256 if (C4::Context->preference("IndependantBranches")){
1257 my $userenv = C4::Context->userenv;
1258 if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
1259 $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1263 if ( $data->{'serial'}) {
1264 $ssth->execute($data->{'itemnumber'}) ;
1265 ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1266 $serial = 1;
1268 #get branch information.....
1269 my $bsth = $dbh->prepare(
1270 "SELECT * FROM branches WHERE branchcode = ?
1273 $bsth->execute( $data->{'holdingbranch'} );
1274 if ( my $bdata = $bsth->fetchrow_hashref ) {
1275 $data->{'branchname'} = $bdata->{'branchname'};
1277 $data->{'datedue'} = $datedue;
1279 # get notforloan complete status if applicable
1280 if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) {
1281 $data->{notforloanvalue} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan} );
1282 $data->{notforloanvalueopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan}, 1 );
1285 # get restricted status and description if applicable
1286 if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) {
1287 $data->{restricted} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted} );
1288 $data->{restrictedopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted}, 1 );
1291 # my stack procedures
1292 if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) {
1293 $data->{stack} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{stack} );
1295 # Find the last 3 people who borrowed this item.
1296 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1297 WHERE itemnumber = ?
1298 AND old_issues.borrowernumber = borrowers.borrowernumber
1299 ORDER BY returndate DESC
1300 LIMIT 3");
1301 $sth2->execute($data->{'itemnumber'});
1302 my $ii = 0;
1303 while (my $data2 = $sth2->fetchrow_hashref()) {
1304 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1305 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1306 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1307 $ii++;
1310 $results[$i] = $data;
1311 $i++;
1313 if($serial) {
1314 return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
1315 } else {
1316 return (@results);
1320 =head2 GetItemsLocationInfo
1322 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1324 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1326 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1328 =over 2
1330 =item C<$data-E<gt>{homebranch}>
1332 Branch Name of the item's homebranch
1334 =item C<$data-E<gt>{holdingbranch}>
1336 Branch Name of the item's holdingbranch
1338 =item C<$data-E<gt>{location}>
1340 Item's shelving location code
1342 =item C<$data-E<gt>{location_intranet}>
1344 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1346 =item C<$data-E<gt>{location_opac}>
1348 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1349 description is set.
1351 =item C<$data-E<gt>{itemcallnumber}>
1353 Item's itemcallnumber
1355 =item C<$data-E<gt>{cn_sort}>
1357 Item's call number normalized for sorting
1359 =back
1361 =cut
1363 sub GetItemsLocationInfo {
1364 my $biblionumber = shift;
1365 my @results;
1367 my $dbh = C4::Context->dbh;
1368 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1369 location, itemcallnumber, cn_sort
1370 FROM items, branches as a, branches as b
1371 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1372 AND biblionumber = ?
1373 ORDER BY cn_sort ASC";
1374 my $sth = $dbh->prepare($query);
1375 $sth->execute($biblionumber);
1377 while ( my $data = $sth->fetchrow_hashref ) {
1378 $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1379 $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1380 push @results, $data;
1382 return @results;
1385 =head2 GetHostItemsInfo
1387 $hostiteminfo = GetHostItemsInfo($hostfield);
1388 Returns the iteminfo for items linked to records via a host field
1390 =cut
1392 sub GetHostItemsInfo {
1393 my ($record) = @_;
1394 my @returnitemsInfo;
1396 if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1397 C4::Context->preference('marcflavour') eq 'NORMARC'){
1398 foreach my $hostfield ( $record->field('773') ) {
1399 my $hostbiblionumber = $hostfield->subfield("0");
1400 my $linkeditemnumber = $hostfield->subfield("9");
1401 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1402 foreach my $hostitemInfo (@hostitemInfos){
1403 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1404 push (@returnitemsInfo,$hostitemInfo);
1405 last;
1409 } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1410 foreach my $hostfield ( $record->field('461') ) {
1411 my $hostbiblionumber = $hostfield->subfield("0");
1412 my $linkeditemnumber = $hostfield->subfield("9");
1413 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1414 foreach my $hostitemInfo (@hostitemInfos){
1415 if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1416 push (@returnitemsInfo,$hostitemInfo);
1417 last;
1422 return @returnitemsInfo;
1426 =head2 GetLastAcquisitions
1428 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1429 'itemtypes' => ('BK','BD')}, 10);
1431 =cut
1433 sub GetLastAcquisitions {
1434 my ($data,$max) = @_;
1436 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1438 my $number_of_branches = @{$data->{branches}};
1439 my $number_of_itemtypes = @{$data->{itemtypes}};
1442 my @where = ('WHERE 1 ');
1443 $number_of_branches and push @where
1444 , 'AND holdingbranch IN ('
1445 , join(',', ('?') x $number_of_branches )
1446 , ')'
1449 $number_of_itemtypes and push @where
1450 , "AND $itemtype IN ("
1451 , join(',', ('?') x $number_of_itemtypes )
1452 , ')'
1455 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1456 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1457 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1458 @where
1459 GROUP BY biblio.biblionumber
1460 ORDER BY dateaccessioned DESC LIMIT $max";
1462 my $dbh = C4::Context->dbh;
1463 my $sth = $dbh->prepare($query);
1465 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1467 my @results;
1468 while( my $row = $sth->fetchrow_hashref){
1469 push @results, {date => $row->{dateaccessioned}
1470 , biblionumber => $row->{biblionumber}
1471 , title => $row->{title}};
1474 return @results;
1477 =head2 GetItemnumbersForBiblio
1479 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1481 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1483 =cut
1485 sub GetItemnumbersForBiblio {
1486 my $biblionumber = shift;
1487 my @items;
1488 my $dbh = C4::Context->dbh;
1489 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1490 $sth->execute($biblionumber);
1491 while (my $result = $sth->fetchrow_hashref) {
1492 push @items, $result->{'itemnumber'};
1494 return \@items;
1497 =head2 get_itemnumbers_of
1499 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1501 Given a list of biblionumbers, return the list of corresponding itemnumbers
1502 for each biblionumber.
1504 Return a reference on a hash where keys are biblionumbers and values are
1505 references on array of itemnumbers.
1507 =cut
1509 sub get_itemnumbers_of {
1510 my @biblionumbers = @_;
1512 my $dbh = C4::Context->dbh;
1514 my $query = '
1515 SELECT itemnumber,
1516 biblionumber
1517 FROM items
1518 WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1520 my $sth = $dbh->prepare($query);
1521 $sth->execute(@biblionumbers);
1523 my %itemnumbers_of;
1525 while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1526 push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1529 return \%itemnumbers_of;
1532 =head2 get_hostitemnumbers_of
1534 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1536 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1538 Return a reference on a hash where key is a biblionumber and values are
1539 references on array of itemnumbers.
1541 =cut
1544 sub get_hostitemnumbers_of {
1545 my ($biblionumber) = @_;
1546 my $marcrecord = GetMarcBiblio($biblionumber);
1547 my (@returnhostitemnumbers,$tag, $biblio_s, $item_s);
1549 my $marcflavor = C4::Context->preference('marcflavour');
1550 if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
1551 $tag='773';
1552 $biblio_s='0';
1553 $item_s='9';
1554 } elsif ($marcflavor eq 'UNIMARC') {
1555 $tag='461';
1556 $biblio_s='0';
1557 $item_s='9';
1560 foreach my $hostfield ( $marcrecord->field($tag) ) {
1561 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1562 my $linkeditemnumber = $hostfield->subfield($item_s);
1563 my @itemnumbers;
1564 if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber})
1566 @itemnumbers = @$itemnumbers;
1568 foreach my $itemnumber (@itemnumbers){
1569 if ($itemnumber eq $linkeditemnumber){
1570 push (@returnhostitemnumbers,$itemnumber);
1571 last;
1575 return @returnhostitemnumbers;
1579 =head2 GetItemnumberFromBarcode
1581 $result = GetItemnumberFromBarcode($barcode);
1583 =cut
1585 sub GetItemnumberFromBarcode {
1586 my ($barcode) = @_;
1587 my $dbh = C4::Context->dbh;
1589 my $rq =
1590 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1591 $rq->execute($barcode);
1592 my ($result) = $rq->fetchrow;
1593 return ($result);
1596 =head2 GetBarcodeFromItemnumber
1598 $result = GetBarcodeFromItemnumber($itemnumber);
1600 =cut
1602 sub GetBarcodeFromItemnumber {
1603 my ($itemnumber) = @_;
1604 my $dbh = C4::Context->dbh;
1606 my $rq =
1607 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1608 $rq->execute($itemnumber);
1609 my ($result) = $rq->fetchrow;
1610 return ($result);
1613 =head2 GetHiddenItemnumbers
1615 =over 4
1617 $result = GetHiddenItemnumbers(@items);
1619 =back
1621 =cut
1623 sub GetHiddenItemnumbers {
1624 my (@items) = @_;
1625 my @resultitems;
1627 my $yaml = C4::Context->preference('OpacHiddenItems');
1628 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1629 my $hidingrules;
1630 eval {
1631 $hidingrules = YAML::Load($yaml);
1633 if ($@) {
1634 warn "Unable to parse OpacHiddenItems syspref : $@";
1635 return ();
1637 my $dbh = C4::Context->dbh;
1639 # For each item
1640 foreach my $item (@items) {
1642 # We check each rule
1643 foreach my $field (keys %$hidingrules) {
1644 my $val;
1645 if (exists $item->{$field}) {
1646 $val = $item->{$field};
1648 else {
1649 my $query = "SELECT $field from items where itemnumber = ?";
1650 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1652 $val = '' unless defined $val;
1654 # If the results matches the values in the yaml file
1655 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1657 # We add the itemnumber to the list
1658 push @resultitems, $item->{'itemnumber'};
1660 # If at least one rule matched for an item, no need to test the others
1661 last;
1665 return @resultitems;
1668 =head3 get_item_authorised_values
1670 find the types and values for all authorised values assigned to this item.
1672 parameters: itemnumber
1674 returns: a hashref malling the authorised value to the value set for this itemnumber
1676 $authorised_values = {
1677 'CCODE' => undef,
1678 'DAMAGED' => '0',
1679 'LOC' => '3',
1680 'LOST' => '0'
1681 'NOT_LOAN' => '0',
1682 'RESTRICTED' => undef,
1683 'STACK' => undef,
1684 'WITHDRAWN' => '0',
1685 'branches' => 'CPL',
1686 'cn_source' => undef,
1687 'itemtypes' => 'SER',
1690 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1692 =cut
1694 sub get_item_authorised_values {
1695 my $itemnumber = shift;
1697 # assume that these entries in the authorised_value table are item level.
1698 my $query = q(SELECT distinct authorised_value, kohafield
1699 FROM marc_subfield_structure
1700 WHERE kohafield like 'item%'
1701 AND authorised_value != '' );
1703 my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1704 my $iteminfo = GetItem( $itemnumber );
1705 # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1706 my $return;
1707 foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1708 my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1709 $field =~ s/^items\.//;
1710 if ( exists $iteminfo->{ $field } ) {
1711 $return->{ $this_authorised_value } = $iteminfo->{ $field };
1714 # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1715 return $return;
1718 =head3 get_authorised_value_images
1720 find a list of icons that are appropriate for display based on the
1721 authorised values for a biblio.
1723 parameters: listref of authorised values, such as comes from
1724 get_item_authorised_values or
1725 from C4::Biblio::get_biblio_authorised_values
1727 returns: listref of hashrefs for each image. Each hashref looks like this:
1729 { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1730 label => '',
1731 category => '',
1732 value => '', }
1734 Notes: Currently, I put on the full path to the images on the staff
1735 side. This should either be configurable or not done at all. Since I
1736 have to deal with 'intranet' or 'opac' in
1737 get_biblio_authorised_values, perhaps I should be passing it in.
1739 =cut
1741 sub get_authorised_value_images {
1742 my $authorised_values = shift;
1744 my @imagelist;
1746 my $authorised_value_list = GetAuthorisedValues();
1747 # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1748 foreach my $this_authorised_value ( @$authorised_value_list ) {
1749 if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1750 && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1751 # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1752 if ( defined $this_authorised_value->{'imageurl'} ) {
1753 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1754 label => $this_authorised_value->{'lib'},
1755 category => $this_authorised_value->{'category'},
1756 value => $this_authorised_value->{'authorised_value'}, };
1761 # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1762 return \@imagelist;
1766 =head1 LIMITED USE FUNCTIONS
1768 The following functions, while part of the public API,
1769 are not exported. This is generally because they are
1770 meant to be used by only one script for a specific
1771 purpose, and should not be used in any other context
1772 without careful thought.
1774 =cut
1776 =head2 GetMarcItem
1778 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1780 Returns MARC::Record of the item passed in parameter.
1781 This function is meant for use only in C<cataloguing/additem.pl>,
1782 where it is needed to support that script's MARC-like
1783 editor.
1785 =cut
1787 sub GetMarcItem {
1788 my ( $biblionumber, $itemnumber ) = @_;
1790 # GetMarcItem has been revised so that it does the following:
1791 # 1. Gets the item information from the items table.
1792 # 2. Converts it to a MARC field for storage in the bib record.
1794 # The previous behavior was:
1795 # 1. Get the bib record.
1796 # 2. Return the MARC tag corresponding to the item record.
1798 # The difference is that one treats the items row as authoritative,
1799 # while the other treats the MARC representation as authoritative
1800 # under certain circumstances.
1802 my $itemrecord = GetItem($itemnumber);
1804 # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1805 # Also, don't emit a subfield if the underlying field is blank.
1808 return Item2Marc($itemrecord,$biblionumber);
1811 sub Item2Marc {
1812 my ($itemrecord,$biblionumber)=@_;
1813 my $mungeditem = {
1814 map {
1815 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1816 } keys %{ $itemrecord }
1818 my $itemmarc = TransformKohaToMarc($mungeditem);
1819 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1821 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1822 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1823 foreach my $field ($itemmarc->field($itemtag)){
1824 $field->add_subfields(@$unlinked_item_subfields);
1827 return $itemmarc;
1830 =head1 PRIVATE FUNCTIONS AND VARIABLES
1832 The following functions are not meant to be called
1833 directly, but are documented in order to explain
1834 the inner workings of C<C4::Items>.
1836 =cut
1838 =head2 %derived_columns
1840 This hash keeps track of item columns that
1841 are strictly derived from other columns in
1842 the item record and are not meant to be set
1843 independently.
1845 Each key in the hash should be the name of a
1846 column (as named by TransformMarcToKoha). Each
1847 value should be hashref whose keys are the
1848 columns on which the derived column depends. The
1849 hashref should also contain a 'BUILDER' key
1850 that is a reference to a sub that calculates
1851 the derived value.
1853 =cut
1855 my %derived_columns = (
1856 'items.cn_sort' => {
1857 'itemcallnumber' => 1,
1858 'items.cn_source' => 1,
1859 'BUILDER' => \&_calc_items_cn_sort,
1863 =head2 _set_derived_columns_for_add
1865 _set_derived_column_for_add($item);
1867 Given an item hash representing a new item to be added,
1868 calculate any derived columns. Currently the only
1869 such column is C<items.cn_sort>.
1871 =cut
1873 sub _set_derived_columns_for_add {
1874 my $item = shift;
1876 foreach my $column (keys %derived_columns) {
1877 my $builder = $derived_columns{$column}->{'BUILDER'};
1878 my $source_values = {};
1879 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1880 next if $source_column eq 'BUILDER';
1881 $source_values->{$source_column} = $item->{$source_column};
1883 $builder->($item, $source_values);
1887 =head2 _set_derived_columns_for_mod
1889 _set_derived_column_for_mod($item);
1891 Given an item hash representing a new item to be modified.
1892 calculate any derived columns. Currently the only
1893 such column is C<items.cn_sort>.
1895 This routine differs from C<_set_derived_columns_for_add>
1896 in that it needs to handle partial item records. In other
1897 words, the caller of C<ModItem> may have supplied only one
1898 or two columns to be changed, so this function needs to
1899 determine whether any of the columns to be changed affect
1900 any of the derived columns. Also, if a derived column
1901 depends on more than one column, but the caller is not
1902 changing all of then, this routine retrieves the unchanged
1903 values from the database in order to ensure a correct
1904 calculation.
1906 =cut
1908 sub _set_derived_columns_for_mod {
1909 my $item = shift;
1911 foreach my $column (keys %derived_columns) {
1912 my $builder = $derived_columns{$column}->{'BUILDER'};
1913 my $source_values = {};
1914 my %missing_sources = ();
1915 my $must_recalc = 0;
1916 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1917 next if $source_column eq 'BUILDER';
1918 if (exists $item->{$source_column}) {
1919 $must_recalc = 1;
1920 $source_values->{$source_column} = $item->{$source_column};
1921 } else {
1922 $missing_sources{$source_column} = 1;
1925 if ($must_recalc) {
1926 foreach my $source_column (keys %missing_sources) {
1927 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1929 $builder->($item, $source_values);
1934 =head2 _do_column_fixes_for_mod
1936 _do_column_fixes_for_mod($item);
1938 Given an item hashref containing one or more
1939 columns to modify, fix up certain values.
1940 Specifically, set to 0 any passed value
1941 of C<notforloan>, C<damaged>, C<itemlost>, or
1942 C<wthdrawn> that is either undefined or
1943 contains the empty string.
1945 =cut
1947 sub _do_column_fixes_for_mod {
1948 my $item = shift;
1950 if (exists $item->{'notforloan'} and
1951 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1952 $item->{'notforloan'} = 0;
1954 if (exists $item->{'damaged'} and
1955 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1956 $item->{'damaged'} = 0;
1958 if (exists $item->{'itemlost'} and
1959 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1960 $item->{'itemlost'} = 0;
1962 if (exists $item->{'wthdrawn'} and
1963 (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1964 $item->{'wthdrawn'} = 0;
1966 if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1967 $item->{'permanent_location'} = $item->{'location'};
1969 if (exists $item->{'timestamp'}) {
1970 delete $item->{'timestamp'};
1974 =head2 _get_single_item_column
1976 _get_single_item_column($column, $itemnumber);
1978 Retrieves the value of a single column from an C<items>
1979 row specified by C<$itemnumber>.
1981 =cut
1983 sub _get_single_item_column {
1984 my $column = shift;
1985 my $itemnumber = shift;
1987 my $dbh = C4::Context->dbh;
1988 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1989 $sth->execute($itemnumber);
1990 my ($value) = $sth->fetchrow();
1991 return $value;
1994 =head2 _calc_items_cn_sort
1996 _calc_items_cn_sort($item, $source_values);
1998 Helper routine to calculate C<items.cn_sort>.
2000 =cut
2002 sub _calc_items_cn_sort {
2003 my $item = shift;
2004 my $source_values = shift;
2006 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
2009 =head2 _set_defaults_for_add
2011 _set_defaults_for_add($item_hash);
2013 Given an item hash representing an item to be added, set
2014 correct default values for columns whose default value
2015 is not handled by the DBMS. This includes the following
2016 columns:
2018 =over 2
2020 =item *
2022 C<items.dateaccessioned>
2024 =item *
2026 C<items.notforloan>
2028 =item *
2030 C<items.damaged>
2032 =item *
2034 C<items.itemlost>
2036 =item *
2038 C<items.wthdrawn>
2040 =back
2042 =cut
2044 sub _set_defaults_for_add {
2045 my $item = shift;
2046 $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
2047 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
2050 =head2 _koha_new_item
2052 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
2054 Perform the actual insert into the C<items> table.
2056 =cut
2058 sub _koha_new_item {
2059 my ( $item, $barcode ) = @_;
2060 my $dbh=C4::Context->dbh;
2061 my $error;
2062 my $query =
2063 "INSERT INTO items SET
2064 biblionumber = ?,
2065 biblioitemnumber = ?,
2066 barcode = ?,
2067 dateaccessioned = ?,
2068 booksellerid = ?,
2069 homebranch = ?,
2070 price = ?,
2071 replacementprice = ?,
2072 replacementpricedate = ?,
2073 datelastborrowed = ?,
2074 datelastseen = ?,
2075 stack = ?,
2076 notforloan = ?,
2077 damaged = ?,
2078 itemlost = ?,
2079 wthdrawn = ?,
2080 itemcallnumber = ?,
2081 coded_location_qualifier = ?,
2082 restricted = ?,
2083 itemnotes = ?,
2084 holdingbranch = ?,
2085 paidfor = ?,
2086 location = ?,
2087 permanent_location = ?,
2088 onloan = ?,
2089 issues = ?,
2090 renewals = ?,
2091 reserves = ?,
2092 cn_source = ?,
2093 cn_sort = ?,
2094 ccode = ?,
2095 itype = ?,
2096 materials = ?,
2097 uri = ?,
2098 enumchron = ?,
2099 more_subfields_xml = ?,
2100 copynumber = ?,
2101 stocknumber = ?
2103 my $sth = $dbh->prepare($query);
2104 my $today = C4::Dates->today('iso');
2105 $sth->execute(
2106 $item->{'biblionumber'},
2107 $item->{'biblioitemnumber'},
2108 $barcode,
2109 $item->{'dateaccessioned'},
2110 $item->{'booksellerid'},
2111 $item->{'homebranch'},
2112 $item->{'price'},
2113 $item->{'replacementprice'},
2114 $item->{'replacementpricedate'} || $today,
2115 $item->{datelastborrowed},
2116 $item->{datelastseen} || $today,
2117 $item->{stack},
2118 $item->{'notforloan'},
2119 $item->{'damaged'},
2120 $item->{'itemlost'},
2121 $item->{'wthdrawn'},
2122 $item->{'itemcallnumber'},
2123 $item->{'coded_location_qualifier'},
2124 $item->{'restricted'},
2125 $item->{'itemnotes'},
2126 $item->{'holdingbranch'},
2127 $item->{'paidfor'},
2128 $item->{'location'},
2129 $item->{'permanent_location'},
2130 $item->{'onloan'},
2131 $item->{'issues'},
2132 $item->{'renewals'},
2133 $item->{'reserves'},
2134 $item->{'items.cn_source'},
2135 $item->{'items.cn_sort'},
2136 $item->{'ccode'},
2137 $item->{'itype'},
2138 $item->{'materials'},
2139 $item->{'uri'},
2140 $item->{'enumchron'},
2141 $item->{'more_subfields_xml'},
2142 $item->{'copynumber'},
2143 $item->{'stocknumber'},
2146 my $itemnumber;
2147 if ( defined $sth->errstr ) {
2148 $error.="ERROR in _koha_new_item $query".$sth->errstr;
2150 else {
2151 $itemnumber = $dbh->{'mysql_insertid'};
2154 return ( $itemnumber, $error );
2157 =head2 MoveItemFromBiblio
2159 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2161 Moves an item from a biblio to another
2163 Returns undef if the move failed or the biblionumber of the destination record otherwise
2165 =cut
2167 sub MoveItemFromBiblio {
2168 my ($itemnumber, $frombiblio, $tobiblio) = @_;
2169 my $dbh = C4::Context->dbh;
2170 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
2171 $sth->execute( $tobiblio );
2172 my ( $tobiblioitem ) = $sth->fetchrow();
2173 $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
2174 my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
2175 if ($return == 1) {
2176 ModZebra( $tobiblio, "specialUpdate", "biblioserver", undef, undef );
2177 ModZebra( $frombiblio, "specialUpdate", "biblioserver", undef, undef );
2178 # Checking if the item we want to move is in an order
2179 require C4::Acquisition;
2180 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2181 if ($order) {
2182 # Replacing the biblionumber within the order if necessary
2183 $order->{'biblionumber'} = $tobiblio;
2184 C4::Acquisition::ModOrder($order);
2186 return $tobiblio;
2188 return;
2191 =head2 DelItemCheck
2193 DelItemCheck($dbh, $biblionumber, $itemnumber);
2195 Exported function (core API) for deleting an item record in Koha if there no current issue.
2197 =cut
2199 sub DelItemCheck {
2200 my ( $dbh, $biblionumber, $itemnumber ) = @_;
2201 my $error;
2203 my $countanalytics=GetAnalyticsCount($itemnumber);
2206 # check that there is no issue on this item before deletion.
2207 my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
2208 $sth->execute($itemnumber);
2210 my $item = GetItem($itemnumber);
2211 my $onloan=$sth->fetchrow;
2213 if ($onloan){
2214 $error = "book_on_loan"
2216 elsif ( !(C4::Context->userenv->{flags} & 1) and
2217 C4::Context->preference("IndependantBranches") and
2218 (C4::Context->userenv->{branch} ne
2219 $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'}) )
2221 $error = "not_same_branch";
2223 else{
2224 # check it doesnt have a waiting reserve
2225 $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
2226 $sth->execute($itemnumber);
2227 my $reserve=$sth->fetchrow;
2228 if ($reserve){
2229 $error = "book_reserved";
2230 } elsif ($countanalytics > 0){
2231 $error = "linked_analytics";
2232 } else {
2233 DelItem($dbh, $biblionumber, $itemnumber);
2234 return 1;
2237 return $error;
2240 =head2 _koha_modify_item
2242 my ($itemnumber,$error) =_koha_modify_item( $item );
2244 Perform the actual update of the C<items> row. Note that this
2245 routine accepts a hashref specifying the columns to update.
2247 =cut
2249 sub _koha_modify_item {
2250 my ( $item ) = @_;
2251 my $dbh=C4::Context->dbh;
2252 my $error;
2254 my $query = "UPDATE items SET ";
2255 my @bind;
2256 for my $key ( keys %$item ) {
2257 $query.="$key=?,";
2258 push @bind, $item->{$key};
2260 $query =~ s/,$//;
2261 $query .= " WHERE itemnumber=?";
2262 push @bind, $item->{'itemnumber'};
2263 my $sth = C4::Context->dbh->prepare($query);
2264 $sth->execute(@bind);
2265 if ( C4::Context->dbh->errstr ) {
2266 $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
2267 warn $error;
2269 return ($item->{'itemnumber'},$error);
2272 =head2 _koha_delete_item
2274 _koha_delete_item( $dbh, $itemnum );
2276 Internal function to delete an item record from the koha tables
2278 =cut
2280 sub _koha_delete_item {
2281 my ( $dbh, $itemnum ) = @_;
2283 # save the deleted item to deleteditems table
2284 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2285 $sth->execute($itemnum);
2286 my $data = $sth->fetchrow_hashref();
2287 my $query = "INSERT INTO deleteditems SET ";
2288 my @bind = ();
2289 foreach my $key ( keys %$data ) {
2290 $query .= "$key = ?,";
2291 push( @bind, $data->{$key} );
2293 $query =~ s/\,$//;
2294 $sth = $dbh->prepare($query);
2295 $sth->execute(@bind);
2297 # delete from items table
2298 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2299 $sth->execute($itemnum);
2300 return;
2303 =head2 _marc_from_item_hash
2305 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2307 Given an item hash representing a complete item record,
2308 create a C<MARC::Record> object containing an embedded
2309 tag representing that item.
2311 The third, optional parameter C<$unlinked_item_subfields> is
2312 an arrayref of subfields (not mapped to C<items> fields per the
2313 framework) to be added to the MARC representation
2314 of the item.
2316 =cut
2318 sub _marc_from_item_hash {
2319 my $item = shift;
2320 my $frameworkcode = shift;
2321 my $unlinked_item_subfields;
2322 if (@_) {
2323 $unlinked_item_subfields = shift;
2326 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2327 # Also, don't emit a subfield if the underlying field is blank.
2328 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2329 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2330 : () } keys %{ $item } };
2332 my $item_marc = MARC::Record->new();
2333 foreach my $item_field ( keys %{$mungeditem} ) {
2334 my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2335 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2336 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2337 foreach my $value (@values){
2338 if ( my $field = $item_marc->field($tag) ) {
2339 $field->add_subfields( $subfield => $value );
2340 } else {
2341 my $add_subfields = [];
2342 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2343 $add_subfields = $unlinked_item_subfields;
2345 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2350 return $item_marc;
2353 =head2 _repack_item_errors
2355 Add an error message hash generated by C<CheckItemPreSave>
2356 to a list of errors.
2358 =cut
2360 sub _repack_item_errors {
2361 my $item_sequence_num = shift;
2362 my $item_ref = shift;
2363 my $error_ref = shift;
2365 my @repacked_errors = ();
2367 foreach my $error_code (sort keys %{ $error_ref }) {
2368 my $repacked_error = {};
2369 $repacked_error->{'item_sequence'} = $item_sequence_num;
2370 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2371 $repacked_error->{'error_code'} = $error_code;
2372 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2373 push @repacked_errors, $repacked_error;
2376 return @repacked_errors;
2379 =head2 _get_unlinked_item_subfields
2381 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2383 =cut
2385 sub _get_unlinked_item_subfields {
2386 my $original_item_marc = shift;
2387 my $frameworkcode = shift;
2389 my $marcstructure = GetMarcStructure(1, $frameworkcode);
2391 # assume that this record has only one field, and that that
2392 # field contains only the item information
2393 my $subfields = [];
2394 my @fields = $original_item_marc->fields();
2395 if ($#fields > -1) {
2396 my $field = $fields[0];
2397 my $tag = $field->tag();
2398 foreach my $subfield ($field->subfields()) {
2399 if (defined $subfield->[1] and
2400 $subfield->[1] ne '' and
2401 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2402 push @$subfields, $subfield->[0] => $subfield->[1];
2406 return $subfields;
2409 =head2 _get_unlinked_subfields_xml
2411 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2413 =cut
2415 sub _get_unlinked_subfields_xml {
2416 my $unlinked_item_subfields = shift;
2418 my $xml;
2419 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2420 my $marc = MARC::Record->new();
2421 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2422 # used in the framework
2423 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2424 $marc->encoding("UTF-8");
2425 $xml = $marc->as_xml("USMARC");
2428 return $xml;
2431 =head2 _parse_unlinked_item_subfields_from_xml
2433 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2435 =cut
2437 sub _parse_unlinked_item_subfields_from_xml {
2438 my $xml = shift;
2439 require C4::Charset;
2440 return unless defined $xml and $xml ne "";
2441 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2442 my $unlinked_subfields = [];
2443 my @fields = $marc->fields();
2444 if ($#fields > -1) {
2445 foreach my $subfield ($fields[0]->subfields()) {
2446 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2449 return $unlinked_subfields;
2452 =head2 GetAnalyticsCount
2454 $count= &GetAnalyticsCount($itemnumber)
2456 counts Usage of itemnumber in Analytical bibliorecords.
2458 =cut
2460 sub GetAnalyticsCount {
2461 my ($itemnumber) = @_;
2462 require C4::Search;
2463 if (C4::Context->preference('NoZebra')) {
2464 # Read the index Koha-Auth-Number for this authid and count the lines
2465 my $result = C4::Search::NZanalyse("hi=$itemnumber");
2466 my @tab = split /;/,$result;
2467 return scalar @tab;
2468 } else {
2469 ### ZOOM search here
2470 my $query;
2471 $query= "hi=".$itemnumber;
2472 my ($err,$res,$result) = C4::Search::SimpleSearch($query,0,10);
2473 return ($result);
2477 =head2 GetItemHolds
2479 =over 4
2480 $holds = &GetItemHolds($biblionumber, $itemnumber);
2482 =back
2484 This function return the count of holds with $biblionumber and $itemnumber
2486 =cut
2488 sub GetItemHolds {
2489 my ($biblionumber, $itemnumber) = @_;
2490 my $holds;
2491 my $dbh = C4::Context->dbh;
2492 my $query = "SELECT count(*)
2493 FROM reserves
2494 WHERE biblionumber=? AND itemnumber=?";
2495 my $sth = $dbh->prepare($query);
2496 $sth->execute($biblionumber, $itemnumber);
2497 $holds = $sth->fetchrow;
2498 return $holds;
2501 # Return the list of the column names of items table
2502 sub _get_items_columns {
2503 my $dbh = C4::Context->dbh;
2504 my $sth = $dbh->column_info(undef, undef, 'items', '%');
2505 $sth->execute;
2506 my $results = $sth->fetchall_hashref('COLUMN_NAME');
2507 return keys %$results;
2510 =head2 SearchItems
2512 my $items = SearchItems($field, $value);
2514 SearchItems will search for items on a specific given field.
2515 For instance you can search all items with a specific stocknumber like this:
2517 my $items = SearchItems('stocknumber', $stocknumber);
2519 =cut
2521 sub SearchItems {
2522 my ($field, $value) = @_;
2524 my $dbh = C4::Context->dbh;
2525 my @columns = _get_items_columns;
2526 my $results = [];
2527 if(0 < grep /^$field$/, @columns) {
2528 my $query = "SELECT $field FROM items WHERE $field = ?";
2529 my $sth = $dbh->prepare( $query );
2530 $sth->execute( $value );
2531 $results = $sth->fetchall_arrayref({});
2533 return $results;
2537 =head1 OTHER FUNCTIONS
2539 =head2 _find_value
2541 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2543 Find the given $subfield in the given $tag in the given
2544 MARC::Record $record. If the subfield is found, returns
2545 the (indicators, value) pair; otherwise, (undef, undef) is
2546 returned.
2548 PROPOSITION :
2549 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2550 I suggest we export it from this module.
2552 =cut
2554 sub _find_value {
2555 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2556 my @result;
2557 my $indicator;
2558 if ( $tagfield < 10 ) {
2559 if ( $record->field($tagfield) ) {
2560 push @result, $record->field($tagfield)->data();
2561 } else {
2562 push @result, "";
2564 } else {
2565 foreach my $field ( $record->field($tagfield) ) {
2566 my @subfields = $field->subfields();
2567 foreach my $subfield (@subfields) {
2568 if ( @$subfield[0] eq $insubfield ) {
2569 push @result, @$subfield[1];
2570 $indicator = $field->indicator(1) . $field->indicator(2);
2575 return ( $indicator, @result );
2579 =head2 PrepareItemrecordDisplay
2581 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2583 Returns a hash with all the fields for Display a given item data in a template
2585 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2587 =cut
2589 sub PrepareItemrecordDisplay {
2591 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2593 my $dbh = C4::Context->dbh;
2594 $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2595 my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2596 my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2598 # return nothing if we don't have found an existing framework.
2599 return q{} unless $tagslib;
2600 my $itemrecord;
2601 if ($itemnum) {
2602 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2604 my @loop_data;
2605 my $authorised_values_sth = $dbh->prepare( "SELECT authorised_value,lib FROM authorised_values WHERE category=? ORDER BY lib" );
2606 foreach my $tag ( sort keys %{$tagslib} ) {
2607 my $previous_tag = '';
2608 if ( $tag ne '' ) {
2610 # loop through each subfield
2611 my $cntsubf;
2612 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2613 next if ( subfield_is_koha_internal_p($subfield) );
2614 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2615 my %subfield_data;
2616 $subfield_data{tag} = $tag;
2617 $subfield_data{subfield} = $subfield;
2618 $subfield_data{countsubfield} = $cntsubf++;
2619 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2620 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2622 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2623 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2624 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2625 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2626 $subfield_data{hidden} = "display:none"
2627 if $tagslib->{$tag}->{$subfield}->{hidden};
2628 my ( $x, $defaultvalue );
2629 if ($itemrecord) {
2630 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2632 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2633 if ( !defined $defaultvalue ) {
2634 $defaultvalue = q||;
2636 $defaultvalue =~ s/"/&quot;/g;
2638 # search for itemcallnumber if applicable
2639 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2640 && C4::Context->preference('itemcallnumber') ) {
2641 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2642 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2643 if ($itemrecord) {
2644 my $temp = $itemrecord->field($CNtag);
2645 if ($temp) {
2646 $defaultvalue = $temp->subfield($CNsubfield);
2650 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2651 && $defaultvalues
2652 && $defaultvalues->{'callnumber'} ) {
2653 my $temp;
2654 if ($itemrecord) {
2655 $temp = $itemrecord->field($subfield);
2657 unless ($temp) {
2658 $defaultvalue = $defaultvalues->{'callnumber'} if $defaultvalues;
2661 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2662 && $defaultvalues
2663 && $defaultvalues->{'branchcode'} ) {
2664 my $temp;
2665 if ($itemrecord) {
2666 $temp = $itemrecord->field($subfield);
2668 unless ($temp) {
2669 $defaultvalue = $defaultvalues->{branchcode} if $defaultvalues;
2672 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2673 && $defaultvalues
2674 && $defaultvalues->{'location'} ) {
2676 my $temp; # make perlcritic happy :)
2677 $temp = $itemrecord->field($subfield) if ($itemrecord);
2679 unless ($temp) {
2680 $defaultvalue = $defaultvalues->{location} if $defaultvalues;
2683 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2684 my @authorised_values;
2685 my %authorised_lib;
2687 # builds list, depending on authorised value...
2688 #---- branch
2689 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2690 if ( ( C4::Context->preference("IndependantBranches") )
2691 && ( C4::Context->userenv->{flags} % 2 != 1 ) ) {
2692 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2693 $sth->execute( C4::Context->userenv->{branch} );
2694 push @authorised_values, ""
2695 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2696 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2697 push @authorised_values, $branchcode;
2698 $authorised_lib{$branchcode} = $branchname;
2700 } else {
2701 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2702 $sth->execute;
2703 push @authorised_values, ""
2704 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2705 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2706 push @authorised_values, $branchcode;
2707 $authorised_lib{$branchcode} = $branchname;
2711 #----- itemtypes
2712 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2713 my $sth = $dbh->prepare( "SELECT itemtype,description FROM itemtypes ORDER BY description" );
2714 $sth->execute;
2715 push @authorised_values, ""
2716 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2717 while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
2718 push @authorised_values, $itemtype;
2719 $authorised_lib{$itemtype} = $description;
2721 #---- class_sources
2722 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2723 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2725 my $class_sources = GetClassSources();
2726 my $default_source = C4::Context->preference("DefaultClassificationSource");
2728 foreach my $class_source (sort keys %$class_sources) {
2729 next unless $class_sources->{$class_source}->{'used'} or
2730 ($class_source eq $default_source);
2731 push @authorised_values, $class_source;
2732 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2735 #---- "true" authorised value
2736 } else {
2737 $authorised_values_sth->execute( $tagslib->{$tag}->{$subfield}->{authorised_value} );
2738 push @authorised_values, ""
2739 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2740 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2741 push @authorised_values, $value;
2742 $authorised_lib{$value} = $lib;
2745 $subfield_data{marc_value} = CGI::scrolling_list(
2746 -name => 'field_value',
2747 -values => \@authorised_values,
2748 -default => "$defaultvalue",
2749 -labels => \%authorised_lib,
2750 -size => 1,
2751 -tabindex => '',
2752 -multiple => 0,
2754 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2755 # opening plugin
2756 my $plugin = C4::Context->intranetdir . "/cataloguing/value_builder/" . $tagslib->{$tag}->{$subfield}->{'value_builder'};
2757 if (do $plugin) {
2758 my $temp;
2759 my $extended_param = plugin_parameters( $dbh, $temp, $tagslib, $subfield_data{id}, undef );
2760 my ( $function_name, $javascript ) = plugin_javascript( $dbh, $temp, $tagslib, $subfield_data{id}, undef );
2761 $subfield_data{random} = int(rand(1000000)); # why do we need 2 different randoms?
2762 $subfield_data{marc_value} = qq[<input tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255"
2763 onfocus="Focus$function_name($subfield_data{random}, '$subfield_data{id}');"
2764 onblur=" Blur$function_name($subfield_data{random}, '$subfield_data{id}');" />
2765 <a href="#" class="buttonDot" onclick="Clic$function_name('$subfield_data{id}'); return false;" title="Tag Editor">...</a>
2766 $javascript];
2767 } else {
2768 warn "Plugin Failed: $plugin";
2769 $subfield_data{marc_value} = qq(<input tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" />); # supply default input form
2772 elsif ( $tag eq '' ) { # it's an hidden field
2773 $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" value="$defaultvalue" />);
2775 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2776 $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" value="$defaultvalue" />);
2778 elsif ( length($defaultvalue) > 100
2779 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2780 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2781 or (C4::Context->preference("marcflavour") eq "MARC21" and
2782 500 <= $tag && $tag < 600 )
2784 # oversize field (textarea)
2785 $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255">$defaultvalue</textarea>\n");
2786 } else {
2787 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2789 push( @loop_data, \%subfield_data );
2793 my $itemnumber;
2794 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2795 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2797 return {
2798 'itemtagfield' => $itemtagfield,
2799 'itemtagsubfield' => $itemtagsubfield,
2800 'itemnumber' => $itemnumber,
2801 'iteminformation' => \@loop_data