3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22 #use warnings; FIXME - Bug 2505
32 use List
::MoreUtils qw
/any/;
34 use DateTime
::Format
::MySQL
;
35 use Data
::Dumper
; # used as part of logging item record changes, not just for
36 # debugging; so please don't remove this
38 use Koha
::AuthorisedValues
;
39 use Koha
::DateUtils qw
/dt_from_string/;
42 use Koha
::Biblioitems
;
45 use Koha
::SearchEngine
;
46 use Koha
::SearchEngine
::Search
;
49 use vars
qw(@ISA @EXPORT);
54 @ISA = qw( Exporter );
74 GetItemsByBiblioitemnumber
78 GetItemnumbersForBiblio
80 get_hostitemnumbers_of
81 GetItemnumberFromBarcode
82 GetBarcodeFromItemnumber
97 PrepareItemrecordDisplay
104 C4::Items - item management functions
108 This module contains an API for manipulating item
109 records in Koha, and is used by cataloguing, circulation,
110 acquisitions, and serials management.
112 # FIXME This POD is not up-to-date
113 A Koha item record is stored in two places: the
114 items table and embedded in a MARC tag in the XML
115 version of the associated bib record in C<biblioitems.marcxml>.
116 This is done to allow the item information to be readily
117 indexed (e.g., by Zebra), but means that each item
118 modification transaction must keep the items table
119 and the MARC XML in sync at all times.
121 Consequently, all code that creates, modifies, or deletes
122 item records B<must> use an appropriate function from
123 C<C4::Items>. If no existing function is suitable, it is
124 better to add one to C<C4::Items> than to use add
125 one-off SQL statements to add or modify items.
127 The items table will be considered authoritative. In other
128 words, if there is ever a discrepancy between the items
129 table and the MARC XML, the items table should be considered
132 =head1 HISTORICAL NOTE
134 Most of the functions in C<C4::Items> were originally in
135 the C<C4::Biblio> module.
137 =head1 CORE EXPORTED FUNCTIONS
139 The following functions are meant for use by users
146 $item = GetItem($itemnumber,$barcode,$serial);
148 Return item information, for a given itemnumber or barcode.
149 The return value is a hashref mapping item column
150 names to values. If C<$serial> is true, include serial publication data.
155 my ($itemnumber,$barcode, $serial) = @_;
156 my $dbh = C4
::Context
->dbh;
160 $item = Koha
::Items
->find( $itemnumber );
162 $item = Koha
::Items
->find( { barcode
=> $barcode } );
165 return unless ( $item );
167 my $data = $item->unblessed();
168 $data->{itype
} = $item->effective_itemtype(); # set the correct itype
171 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
172 $ssth->execute( $data->{'itemnumber'} );
173 ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
181 CartToShelf($itemnumber);
183 Set the current shelving location of the item record
184 to its stored permanent shelving location. This is
185 primarily used to indicate when an item whose current
186 location is a special processing ('PROC') or shelving cart
187 ('CART') location is back in the stacks.
192 my ( $itemnumber ) = @_;
194 unless ( $itemnumber ) {
195 croak
"FAILED CartToShelf() - no itemnumber supplied";
198 my $item = GetItem
($itemnumber);
199 if ( $item->{location
} eq 'CART' ) {
200 $item->{location
} = $item->{permanent_location
};
201 ModItem
($item, undef, $itemnumber);
207 ShelfToCart($itemnumber);
209 Set the current shelving location of the item
210 to shelving cart ('CART').
215 my ( $itemnumber ) = @_;
217 unless ( $itemnumber ) {
218 croak
"FAILED ShelfToCart() - no itemnumber supplied";
221 my $item = GetItem
($itemnumber);
222 $item->{'location'} = 'CART';
223 ModItem
($item, undef, $itemnumber);
226 =head2 AddItemFromMarc
228 my ($biblionumber, $biblioitemnumber, $itemnumber)
229 = AddItemFromMarc($source_item_marc, $biblionumber);
231 Given a MARC::Record object containing an embedded item
232 record and a biblionumber, create a new item record.
236 sub AddItemFromMarc
{
237 my ( $source_item_marc, $biblionumber ) = @_;
238 my $dbh = C4
::Context
->dbh;
240 # parse item hash from MARC
241 my $frameworkcode = GetFrameworkCode
( $biblionumber );
242 my ($itemtag,$itemsubfield)=GetMarcFromKohaField
("items.itemnumber",$frameworkcode);
244 my $localitemmarc=MARC
::Record
->new;
245 $localitemmarc->append_fields($source_item_marc->field($itemtag));
246 my $item = &TransformMarcToKoha
( $localitemmarc, $frameworkcode ,'items');
247 my $unlinked_item_subfields = _get_unlinked_item_subfields
($localitemmarc, $frameworkcode);
248 return AddItem
($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
253 my ($biblionumber, $biblioitemnumber, $itemnumber)
254 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
256 Given a hash containing item column names as keys,
257 create a new Koha item record.
259 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
260 do not need to be supplied for general use; they exist
261 simply to allow them to be picked up from AddItemFromMarc.
263 The final optional parameter, C<$unlinked_item_subfields>, contains
264 an arrayref containing subfields present in the original MARC
265 representation of the item (e.g., from the item editor) that are
266 not mapped to C<items> columns directly but should instead
267 be stored in C<items.more_subfields_xml> and included in
268 the biblio items tag for display and indexing.
274 my $biblionumber = shift;
276 my $dbh = @_ ?
shift : C4
::Context
->dbh;
277 my $frameworkcode = @_ ?
shift : GetFrameworkCode
($biblionumber);
278 my $unlinked_item_subfields;
280 $unlinked_item_subfields = shift;
283 # needs old biblionumber and biblioitemnumber
284 $item->{'biblionumber'} = $biblionumber;
285 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
286 $sth->execute( $item->{'biblionumber'} );
287 ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
289 _set_defaults_for_add
($item);
290 _set_derived_columns_for_add
($item);
291 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml
($unlinked_item_subfields);
293 # FIXME - checks here
294 unless ( $item->{itype
} ) { # default to biblioitem.itemtype if no itype
295 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
296 $itype_sth->execute( $item->{'biblionumber'} );
297 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
300 my ( $itemnumber, $error ) = _koha_new_item
( $item, $item->{barcode
} );
303 $item->{'itemnumber'} = $itemnumber;
305 ModZebra
( $item->{biblionumber
}, "specialUpdate", "biblioserver" );
307 logaction
( "CATALOGUING", "ADD", $itemnumber, "item" )
308 if C4
::Context
->preference("CataloguingLog");
310 return ( $item->{biblionumber
}, $item->{biblioitemnumber
}, $itemnumber );
313 =head2 AddItemBatchFromMarc
315 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
316 $biblionumber, $biblioitemnumber, $frameworkcode);
318 Efficiently create item records from a MARC biblio record with
319 embedded item fields. This routine is suitable for batch jobs.
321 This API assumes that the bib record has already been
322 saved to the C<biblio> and C<biblioitems> tables. It does
323 not expect that C<biblio_metadata.metadata> is populated, but it
324 will do so via a call to ModBibiloMarc.
326 The goal of this API is to have a similar effect to using AddBiblio
327 and AddItems in succession, but without inefficient repeated
328 parsing of the MARC XML bib record.
330 This function returns an arrayref of new itemsnumbers and an arrayref of item
331 errors encountered during the processing. Each entry in the errors
332 list is a hashref containing the following keys:
338 Sequence number of original item tag in the MARC record.
342 Item barcode, provide to assist in the construction of
343 useful error messages.
347 Code representing the error condition. Can be 'duplicate_barcode',
348 'invalid_homebranch', or 'invalid_holdingbranch'.
350 =item error_information
352 Additional information appropriate to the error condition.
358 sub AddItemBatchFromMarc
{
359 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
361 my @itemnumbers = ();
363 my $dbh = C4
::Context
->dbh;
365 # We modify the record, so lets work on a clone so we don't change the
367 $record = $record->clone();
368 # loop through the item tags and start creating items
369 my @bad_item_fields = ();
370 my ($itemtag, $itemsubfield) = &GetMarcFromKohaField
("items.itemnumber",'');
371 my $item_sequence_num = 0;
372 ITEMFIELD
: foreach my $item_field ($record->field($itemtag)) {
373 $item_sequence_num++;
374 # we take the item field and stick it into a new
375 # MARC record -- this is required so far because (FIXME)
376 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
377 # and there is no TransformMarcFieldToKoha
378 my $temp_item_marc = MARC
::Record
->new();
379 $temp_item_marc->append_fields($item_field);
381 # add biblionumber and biblioitemnumber
382 my $item = TransformMarcToKoha
( $temp_item_marc, $frameworkcode, 'items' );
383 my $unlinked_item_subfields = _get_unlinked_item_subfields
($temp_item_marc, $frameworkcode);
384 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml
($unlinked_item_subfields);
385 $item->{'biblionumber'} = $biblionumber;
386 $item->{'biblioitemnumber'} = $biblioitemnumber;
388 # check for duplicate barcode
389 my %item_errors = CheckItemPreSave
($item);
391 push @errors, _repack_item_errors
($item_sequence_num, $item, \
%item_errors);
392 push @bad_item_fields, $item_field;
396 _set_defaults_for_add
($item);
397 _set_derived_columns_for_add
($item);
398 my ( $itemnumber, $error ) = _koha_new_item
( $item, $item->{barcode
} );
399 warn $error if $error;
400 push @itemnumbers, $itemnumber; # FIXME not checking error
401 $item->{'itemnumber'} = $itemnumber;
403 logaction
("CATALOGUING", "ADD", $itemnumber, "item") if C4
::Context
->preference("CataloguingLog");
405 my $new_item_marc = _marc_from_item_hash
($item, $frameworkcode, $unlinked_item_subfields);
406 $item_field->replace_with($new_item_marc->field($itemtag));
409 # remove any MARC item fields for rejected items
410 foreach my $item_field (@bad_item_fields) {
411 $record->delete_field($item_field);
414 # update the MARC biblio
415 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
417 return (\
@itemnumbers, \
@errors);
420 =head2 ModItemFromMarc
422 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
424 This function updates an item record based on a supplied
425 C<MARC::Record> object containing an embedded item field.
426 This API is meant for the use of C<additem.pl>; for
427 other purposes, C<ModItem> should be used.
429 This function uses the hash %default_values_for_mod_from_marc,
430 which contains default values for item fields to
431 apply when modifying an item. This is needed because
432 if an item field's value is cleared, TransformMarcToKoha
433 does not include the column in the
434 hash that's passed to ModItem, which without
435 use of this hash makes it impossible to clear
436 an item field's value. See bug 2466.
438 Note that only columns that can be directly
439 changed from the cataloging and serials
440 item editors are included in this hash.
446 sub _build_default_values_for_mod_marc
{
447 my ($frameworkcode) = @_;
449 my $cache = Koha
::Caches
->get_instance();
450 my $cache_key = "default_value_for_mod_marc-$frameworkcode";
451 my $cached = $cache->get_from_cache($cache_key);
452 return $cached if $cached;
454 my $default_values = {
456 booksellerid
=> undef,
458 'items.cn_source' => undef,
459 coded_location_qualifier
=> undef,
463 holdingbranch
=> undef,
465 itemcallnumber
=> undef,
468 itemnotes_nonpublic
=> undef,
471 permanent_location
=> undef,
475 # paidfor => undef, # commented, see bug 12817
477 replacementprice
=> undef,
478 replacementpricedate
=> undef,
481 stocknumber
=> undef,
485 my %default_values_for_mod_from_marc;
486 while ( my ( $field, $default_value ) = each %$default_values ) {
487 my $kohafield = $field;
488 $kohafield =~ s
|^([^\
.]+)$|items
.$1|;
489 $default_values_for_mod_from_marc{$field} =
491 if C4
::Koha
::IsKohaFieldLinked
(
492 { kohafield
=> $kohafield, frameworkcode
=> $frameworkcode } );
495 $cache->set_in_cache($cache_key, \
%default_values_for_mod_from_marc);
496 return \
%default_values_for_mod_from_marc;
499 sub ModItemFromMarc
{
500 my $item_marc = shift;
501 my $biblionumber = shift;
502 my $itemnumber = shift;
504 my $dbh = C4
::Context
->dbh;
505 my $frameworkcode = GetFrameworkCode
($biblionumber);
506 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField
( "items.itemnumber", $frameworkcode );
508 my $localitemmarc = MARC
::Record
->new;
509 $localitemmarc->append_fields( $item_marc->field($itemtag) );
510 my $item = &TransformMarcToKoha
( $localitemmarc, $frameworkcode, 'items' );
511 my $default_values = _build_default_values_for_mod_marc
($frameworkcode);
512 foreach my $item_field ( keys %$default_values ) {
513 $item->{$item_field} = $default_values->{$item_field}
514 unless exists $item->{$item_field};
516 my $unlinked_item_subfields = _get_unlinked_item_subfields
( $localitemmarc, $frameworkcode );
518 ModItem
($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
524 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
526 Change one or more columns in an item record and update
527 the MARC representation of the item.
529 The first argument is a hashref mapping from item column
530 names to the new values. The second and third arguments
531 are the biblionumber and itemnumber, respectively.
533 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
534 an arrayref containing subfields present in the original MARC
535 representation of the item (e.g., from the item editor) that are
536 not mapped to C<items> columns directly but should instead
537 be stored in C<items.more_subfields_xml> and included in
538 the biblio items tag for display and indexing.
540 If one of the changed columns is used to calculate
541 the derived value of a column such as C<items.cn_sort>,
542 this routine will perform the necessary calculation
549 my $biblionumber = shift;
550 my $itemnumber = shift;
552 # if $biblionumber is undefined, get it from the current item
553 unless (defined $biblionumber) {
554 $biblionumber = _get_single_item_column
('biblionumber', $itemnumber);
557 my $dbh = @_ ?
shift : C4
::Context
->dbh;
558 my $frameworkcode = @_ ?
shift : GetFrameworkCode
( $biblionumber );
560 my $unlinked_item_subfields;
562 $unlinked_item_subfields = shift;
563 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml
($unlinked_item_subfields);
566 $item->{'itemnumber'} = $itemnumber or return;
568 my @fields = qw( itemlost withdrawn );
570 # Only call GetItem if we need to set an "on" date field
571 if ( $item->{itemlost
} || $item->{withdrawn
} ) {
572 my $pre_mod_item = GetItem
( $item->{'itemnumber'} );
573 for my $field (@fields) {
574 if ( defined( $item->{$field} )
575 and not $pre_mod_item->{$field}
576 and $item->{$field} )
578 $item->{ $field . '_on' } =
579 DateTime
::Format
::MySQL
->format_datetime( dt_from_string
() );
584 # If the field is defined but empty, we are removing and,
585 # and thus need to clear out the 'on' field as well
586 for my $field (@fields) {
587 if ( defined( $item->{$field} ) && !$item->{$field} ) {
588 $item->{ $field . '_on' } = undef;
593 _set_derived_columns_for_mod
($item);
594 _do_column_fixes_for_mod
($item);
597 # attempt to change itemnumber
598 # attempt to change biblionumber (if we want
599 # an API to relink an item to a different bib,
600 # it should be a separate function)
603 _koha_modify_item
($item);
605 # request that bib be reindexed so that searching on current
606 # item status is possible
607 ModZebra
( $biblionumber, "specialUpdate", "biblioserver" );
609 logaction
("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper
($item)) if C4
::Context
->preference("CataloguingLog");
612 =head2 ModItemTransfer
614 ModItemTransfer($itenumber, $frombranch, $tobranch);
616 Marks an item as being transferred from one branch
621 sub ModItemTransfer
{
622 my ( $itemnumber, $frombranch, $tobranch ) = @_;
624 my $dbh = C4
::Context
->dbh;
626 # Remove the 'shelving cart' location status if it is being used.
627 CartToShelf
( $itemnumber ) if ( C4
::Context
->preference("ReturnToShelvingCart") );
629 #new entry in branchtransfers....
630 my $sth = $dbh->prepare(
631 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
632 VALUES (?, ?, NOW(), ?)");
633 $sth->execute($itemnumber, $frombranch, $tobranch);
635 ModItem
({ holdingbranch
=> $tobranch }, undef, $itemnumber);
636 ModDateLastSeen
($itemnumber);
640 =head2 ModDateLastSeen
642 ModDateLastSeen($itemnum);
644 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
645 C<$itemnum> is the item number
649 sub ModDateLastSeen
{
650 my ($itemnumber) = @_;
652 my $today = output_pref
({ dt
=> dt_from_string
, dateformat
=> 'iso', dateonly
=> 1 });
653 ModItem
({ itemlost
=> 0, datelastseen
=> $today }, undef, $itemnumber);
658 DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
660 Exported function (core API) for deleting an item record in Koha.
667 my $itemnumber = $params->{itemnumber
};
668 my $biblionumber = $params->{biblionumber
};
670 unless ($biblionumber) {
671 $biblionumber = C4
::Biblio
::GetBiblionumberFromItemnumber
($itemnumber);
674 # If there is no biblionumber for the given itemnumber, there is nothing to delete
675 return 0 unless $biblionumber;
677 # FIXME check the item has no current issues
678 my $deleted = _koha_delete_item
( $itemnumber );
680 ModZebra
( $biblionumber, "specialUpdate", "biblioserver" );
682 #search item field code
683 logaction
("CATALOGUING", "DELETE", $itemnumber, "item") if C4
::Context
->preference("CataloguingLog");
687 =head2 CheckItemPreSave
689 my $item_ref = TransformMarcToKoha($marc, 'items');
691 my %errors = CheckItemPreSave($item_ref);
692 if (exists $errors{'duplicate_barcode'}) {
693 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
694 } elsif (exists $errors{'invalid_homebranch'}) {
695 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
696 } elsif (exists $errors{'invalid_holdingbranch'}) {
697 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
702 Given a hashref containing item fields, determine if it can be
703 inserted or updated in the database. Specifically, checks for
704 database integrity issues, and returns a hash containing any
705 of the following keys, if applicable.
709 =item duplicate_barcode
711 Barcode, if it duplicates one already found in the database.
713 =item invalid_homebranch
715 Home branch, if not defined in branches table.
717 =item invalid_holdingbranch
719 Holding branch, if not defined in branches table.
723 This function does NOT implement any policy-related checks,
724 e.g., whether current operator is allowed to save an
725 item that has a given branch code.
729 sub CheckItemPreSave
{
730 my $item_ref = shift;
734 # check for duplicate barcode
735 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
736 my $existing_itemnumber = GetItemnumberFromBarcode
($item_ref->{'barcode'});
737 if ($existing_itemnumber) {
738 if (!exists $item_ref->{'itemnumber'} # new item
739 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
740 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
745 # check for valid home branch
746 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
747 my $home_library = Koha
::Libraries
->find( $item_ref->{homebranch
} );
748 unless (defined $home_library) {
749 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
753 # check for valid holding branch
754 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
755 my $holding_library = Koha
::Libraries
->find( $item_ref->{holdingbranch
} );
756 unless (defined $holding_library) {
757 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
765 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
767 The following functions provide various ways of
768 getting an item record, a set of item records, or
769 lists of authorized values for certain item fields.
771 Some of the functions in this group are candidates
772 for refactoring -- for example, some of the code
773 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
774 has copy-and-paste work.
780 $items = GetLostItems( $where );
782 This function gets a list of lost items.
788 C<$where> is a hashref. it containts a field of the items table as key
789 and the value to match as value. For example:
791 { barcode => 'abc123',
792 homebranch => 'CPL', }
796 C<$items> is a reference to an array full of hashrefs with columns
797 from the "items" table as keys.
799 =item usage in the perl script:
801 my $where = { barcode => '0001548' };
802 my $items = GetLostItems( $where );
803 $template->param( itemsloop => $items );
810 # Getting input args.
812 my $dbh = C4
::Context
->dbh;
815 SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
816 itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
818 LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
819 LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
820 LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
822 authorised_values.category = 'LOST'
823 AND itemlost IS NOT NULL
826 my @query_parameters;
827 foreach my $key (keys %$where) {
828 $query .= " AND $key LIKE ?";
829 push @query_parameters, "%$where->{$key}%";
832 my $sth = $dbh->prepare($query);
833 $sth->execute( @query_parameters );
835 while ( my $row = $sth->fetchrow_hashref ){
841 =head2 GetItemsForInventory
843 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
844 minlocation => $minlocation,
845 maxlocation => $maxlocation,
846 location => $location,
847 itemtype => $itemtype,
848 ignoreissued => $ignoreissued,
849 datelastseen => $datelastseen,
850 branchcode => $branchcode,
854 statushash => $statushash,
857 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
859 The sub returns a reference to a list of hashes, each containing
860 itemnumber, author, title, barcode, item callnumber, and date last
861 seen. It is ordered by callnumber then title.
863 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
864 the datelastseen can be used to specify that you want to see items not seen since a past date only.
865 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
866 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
868 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
872 sub GetItemsForInventory
{
873 my ( $parameters ) = @_;
874 my $minlocation = $parameters->{'minlocation'} // '';
875 my $maxlocation = $parameters->{'maxlocation'} // '';
876 my $location = $parameters->{'location'} // '';
877 my $itemtype = $parameters->{'itemtype'} // '';
878 my $ignoreissued = $parameters->{'ignoreissued'} // '';
879 my $datelastseen = $parameters->{'datelastseen'} // '';
880 my $branchcode = $parameters->{'branchcode'} // '';
881 my $branch = $parameters->{'branch'} // '';
882 my $offset = $parameters->{'offset'} // '';
883 my $size = $parameters->{'size'} // '';
884 my $statushash = $parameters->{'statushash'} // '';
886 my $dbh = C4
::Context
->dbh;
887 my ( @bind_params, @where_strings );
889 my $select_columns = q{
890 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
892 my $select_count = q{SELECT COUNT(*)};
895 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
896 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
899 for my $authvfield (keys %$statushash){
900 if ( scalar @
{$statushash->{$authvfield}} > 0 ){
901 my $joinedvals = join ',', @
{$statushash->{$authvfield}};
902 push @where_strings, "$authvfield in (" . $joinedvals . ")";
908 push @where_strings, 'itemcallnumber >= ?';
909 push @bind_params, $minlocation;
913 push @where_strings, 'itemcallnumber <= ?';
914 push @bind_params, $maxlocation;
918 $datelastseen = output_pref
({ str
=> $datelastseen, dateformat
=> 'iso', dateonly
=> 1 });
919 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
920 push @bind_params, $datelastseen;
924 push @where_strings, 'items.location = ?';
925 push @bind_params, $location;
929 if($branch eq "homebranch"){
930 push @where_strings, 'items.homebranch = ?';
932 push @where_strings, 'items.holdingbranch = ?';
934 push @bind_params, $branchcode;
938 push @where_strings, 'biblioitems.itemtype = ?';
939 push @bind_params, $itemtype;
942 if ( $ignoreissued) {
943 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
944 push @where_strings, 'issues.date_due IS NULL';
947 if ( @where_strings ) {
949 $query .= join ' AND ', @where_strings;
951 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
952 my $count_query = $select_count . $query;
953 $query .= " LIMIT $offset, $size" if ($offset and $size);
954 $query = $select_columns . $query;
955 my $sth = $dbh->prepare($query);
956 $sth->execute( @bind_params );
959 my $tmpresults = $sth->fetchall_arrayref({});
960 $sth = $dbh->prepare( $count_query );
961 $sth->execute( @bind_params );
962 my ($iTotalRecords) = $sth->fetchrow_array();
964 my @avs = Koha
::AuthorisedValues
->search(
965 { 'marc_subfield_structures.kohafield' => { '>' => '' },
966 'me.authorised_value' => { '>' => '' },
968 { join => { category
=> 'marc_subfield_structures' },
969 distinct
=> ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
970 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
971 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
975 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
977 foreach my $row (@
$tmpresults) {
980 foreach (keys %$row) {
981 if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
982 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
988 return (\
@results, $iTotalRecords);
991 =head2 GetItemInfosOf
993 GetItemInfosOf(@itemnumbers);
998 my @itemnumbers = @_;
1000 my $itemnumber_values = @itemnumbers ?
join( ',', @itemnumbers ) : "''";
1002 my $dbh = C4
::Context
->dbh;
1006 WHERE itemnumber IN ($itemnumber_values)
1008 return $dbh->selectall_hashref($query, 'itemnumber');
1011 =head2 GetItemsByBiblioitemnumber
1013 GetItemsByBiblioitemnumber($biblioitemnumber);
1015 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1016 Called by C<C4::XISBN>
1020 sub GetItemsByBiblioitemnumber
{
1021 my ( $bibitem ) = @_;
1022 my $dbh = C4
::Context
->dbh;
1023 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1024 # Get all items attached to a biblioitem
1027 $sth->execute($bibitem) || die $sth->errstr;
1028 while ( my $data = $sth->fetchrow_hashref ) {
1029 # Foreach item, get circulation information
1030 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1031 WHERE itemnumber = ?
1032 AND issues.borrowernumber = borrowers.borrowernumber"
1034 $sth2->execute( $data->{'itemnumber'} );
1035 if ( my $data2 = $sth2->fetchrow_hashref ) {
1036 # if item is out, set the due date and who it is out too
1037 $data->{'date_due'} = $data2->{'date_due'};
1038 $data->{'cardnumber'} = $data2->{'cardnumber'};
1039 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
1042 # set date_due to blank, so in the template we check itemlost, and withdrawn
1043 $data->{'date_due'} = '';
1045 # Find the last 3 people who borrowed this item.
1046 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1047 AND old_issues.borrowernumber = borrowers.borrowernumber
1048 ORDER BY returndate desc,timestamp desc LIMIT 3";
1049 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1050 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1052 while ( my $data2 = $sth2->fetchrow_hashref ) {
1053 $data->{"timestamp$i2"} = $data2->{'timestamp'};
1054 $data->{"card$i2"} = $data2->{'cardnumber'};
1055 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
1058 push(@results,$data);
1065 @results = GetItemsInfo($biblionumber);
1067 Returns information about items with the given biblionumber.
1069 C<GetItemsInfo> returns a list of references-to-hash. Each element
1070 contains a number of keys. Most of them are attributes from the
1071 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1072 Koha database. Other keys include:
1076 =item C<$data-E<gt>{branchname}>
1078 The name (not the code) of the branch to which the book belongs.
1080 =item C<$data-E<gt>{datelastseen}>
1082 This is simply C<items.datelastseen>, except that while the date is
1083 stored in YYYY-MM-DD format in the database, here it is converted to
1084 DD/MM/YYYY format. A NULL date is returned as C<//>.
1086 =item C<$data-E<gt>{datedue}>
1088 =item C<$data-E<gt>{class}>
1090 This is the concatenation of C<biblioitems.classification>, the book's
1091 Dewey code, and C<biblioitems.subclass>.
1093 =item C<$data-E<gt>{ocount}>
1095 I think this is the number of copies of the book available.
1097 =item C<$data-E<gt>{order}>
1099 If this is set, it is set to C<One Order>.
1106 my ( $biblionumber ) = @_;
1107 my $dbh = C4
::Context
->dbh;
1108 require C4
::Languages
;
1109 my $language = C4
::Languages
::getlanguage
();
1115 biblioitems.itemtype,
1118 biblioitems.publicationyear,
1119 biblioitems.publishercode,
1120 biblioitems.volumedate,
1121 biblioitems.volumedesc,
1124 items.notforloan as itemnotforloan,
1125 issues.borrowernumber,
1126 issues.date_due as datedue,
1127 issues.onsite_checkout,
1128 borrowers.cardnumber,
1130 borrowers.firstname,
1131 borrowers.branchcode as bcode,
1133 serial.publisheddate,
1134 itemtypes.description,
1135 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1136 itemtypes.notforloan as notforloan_per_itemtype,
1140 holding.opac_info as holding_branch_opac_info,
1141 home.opac_info as home_branch_opac_info
1145 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1146 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1147 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1148 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1149 LEFT JOIN issues USING (itemnumber)
1150 LEFT JOIN borrowers USING (borrowernumber)
1151 LEFT JOIN serialitems USING (itemnumber)
1152 LEFT JOIN serial USING (serialid)
1153 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1154 . (C4
::Context
->preference('item-level_itypes') ?
'items.itype' : 'biblioitems.itemtype');
1156 LEFT JOIN localization ON itemtypes
.itemtype
= localization
.code
1157 AND localization
.entity
= 'itemtypes'
1158 AND localization
.lang
= ?
1161 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1162 my $sth = $dbh->prepare($query);
1163 $sth->execute($language, $biblionumber);
1168 my $userenv = C4
::Context
->userenv;
1169 my $want_not_same_branch = C4
::Context
->preference("IndependentBranches") && !C4
::Context
->IsSuperLibrarian();
1170 while ( my $data = $sth->fetchrow_hashref ) {
1171 if ( $data->{borrowernumber
} && $want_not_same_branch) {
1172 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch
};
1175 $serial ||= $data->{'serial'};
1178 # get notforloan complete status if applicable
1179 $descriptions = Koha
::AuthorisedValues
->get_description_by_koha_field({frameworkcode
=> $data->{frameworkcode
}, kohafield
=> 'items.notforloan', authorised_value
=> $data->{itemnotforloan
} });
1180 $data->{notforloanvalue
} = $descriptions->{lib
} // '';
1181 $data->{notforloanvalueopac
} = $descriptions->{opac_description
} // '';
1183 # get restricted status and description if applicable
1184 $descriptions = Koha
::AuthorisedValues
->get_description_by_koha_field({frameworkcode
=> $data->{frameworkcode
}, kohafield
=> 'items.restricted', authorised_value
=> $data->{restricted
} });
1185 $data->{restricted
} = $descriptions->{lib
} // '';
1186 $data->{restrictedopac
} = $descriptions->{opac_description
} // '';
1188 # my stack procedures
1189 $descriptions = Koha
::AuthorisedValues
->get_description_by_koha_field({frameworkcode
=> $data->{frameworkcode
}, kohafield
=> 'items.stack', authorised_value
=> $data->{stack
} });
1190 $data->{stack
} = $descriptions->{lib
} // '';
1192 # Find the last 3 people who borrowed this item.
1193 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1194 WHERE itemnumber = ?
1195 AND old_issues.borrowernumber = borrowers.borrowernumber
1196 ORDER BY returndate DESC
1198 $sth2->execute($data->{'itemnumber'});
1200 while (my $data2 = $sth2->fetchrow_hashref()) {
1201 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1202 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1203 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1207 $results[$i] = $data;
1212 ?
sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1216 =head2 GetItemsLocationInfo
1218 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1220 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1222 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1226 =item C<$data-E<gt>{homebranch}>
1228 Branch Name of the item's homebranch
1230 =item C<$data-E<gt>{holdingbranch}>
1232 Branch Name of the item's holdingbranch
1234 =item C<$data-E<gt>{location}>
1236 Item's shelving location code
1238 =item C<$data-E<gt>{location_intranet}>
1240 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1242 =item C<$data-E<gt>{location_opac}>
1244 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1247 =item C<$data-E<gt>{itemcallnumber}>
1249 Item's itemcallnumber
1251 =item C<$data-E<gt>{cn_sort}>
1253 Item's call number normalized for sorting
1259 sub GetItemsLocationInfo
{
1260 my $biblionumber = shift;
1263 my $dbh = C4
::Context
->dbh;
1264 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1265 location, itemcallnumber, cn_sort
1266 FROM items, branches as a, branches as b
1267 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1268 AND biblionumber = ?
1269 ORDER BY cn_sort ASC";
1270 my $sth = $dbh->prepare($query);
1271 $sth->execute($biblionumber);
1273 while ( my $data = $sth->fetchrow_hashref ) {
1274 my $av = Koha
::AuthorisedValues
->search({ category
=> 'LOC', authorised_value
=> $data->{location
} });
1275 $av = $av->count ?
$av->next : undef;
1276 $data->{location_intranet
} = $av ?
$av->lib : '';
1277 $data->{location_opac
} = $av ?
$av->opac_description : '';
1278 push @results, $data;
1283 =head2 GetHostItemsInfo
1285 $hostiteminfo = GetHostItemsInfo($hostfield);
1286 Returns the iteminfo for items linked to records via a host field
1290 sub GetHostItemsInfo
{
1292 my @returnitemsInfo;
1294 if (C4
::Context
->preference('marcflavour') eq 'MARC21' ||
1295 C4
::Context
->preference('marcflavour') eq 'NORMARC'){
1296 foreach my $hostfield ( $record->field('773') ) {
1297 my $hostbiblionumber = $hostfield->subfield("0");
1298 my $linkeditemnumber = $hostfield->subfield("9");
1299 my @hostitemInfos = GetItemsInfo
($hostbiblionumber);
1300 foreach my $hostitemInfo (@hostitemInfos){
1301 if ($hostitemInfo->{itemnumber
} eq $linkeditemnumber){
1302 push (@returnitemsInfo,$hostitemInfo);
1307 } elsif ( C4
::Context
->preference('marcflavour') eq 'UNIMARC'){
1308 foreach my $hostfield ( $record->field('461') ) {
1309 my $hostbiblionumber = $hostfield->subfield("0");
1310 my $linkeditemnumber = $hostfield->subfield("9");
1311 my @hostitemInfos = GetItemsInfo
($hostbiblionumber);
1312 foreach my $hostitemInfo (@hostitemInfos){
1313 if ($hostitemInfo->{itemnumber
} eq $linkeditemnumber){
1314 push (@returnitemsInfo,$hostitemInfo);
1320 return @returnitemsInfo;
1324 =head2 GetLastAcquisitions
1326 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1327 'itemtypes' => ('BK','BD')}, 10);
1331 sub GetLastAcquisitions
{
1332 my ($data,$max) = @_;
1334 my $itemtype = C4
::Context
->preference('item-level_itypes') ?
'itype' : 'itemtype';
1336 my $number_of_branches = @
{$data->{branches
}};
1337 my $number_of_itemtypes = @
{$data->{itemtypes
}};
1340 my @where = ('WHERE 1 ');
1341 $number_of_branches and push @where
1342 , 'AND holdingbranch IN ('
1343 , join(',', ('?') x
$number_of_branches )
1347 $number_of_itemtypes and push @where
1348 , "AND $itemtype IN ("
1349 , join(',', ('?') x
$number_of_itemtypes )
1353 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1354 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1355 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1357 GROUP BY biblio.biblionumber
1358 ORDER BY dateaccessioned DESC LIMIT $max";
1360 my $dbh = C4
::Context
->dbh;
1361 my $sth = $dbh->prepare($query);
1363 $sth->execute((@
{$data->{branches
}}, @
{$data->{itemtypes
}}));
1366 while( my $row = $sth->fetchrow_hashref){
1367 push @results, {date
=> $row->{dateaccessioned
}
1368 , biblionumber
=> $row->{biblionumber
}
1369 , title
=> $row->{title
}};
1375 =head2 GetItemnumbersForBiblio
1377 my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1379 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1383 sub GetItemnumbersForBiblio
{
1384 my $biblionumber = shift;
1386 my $dbh = C4
::Context
->dbh;
1387 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1388 $sth->execute($biblionumber);
1389 while (my $result = $sth->fetchrow_hashref) {
1390 push @items, $result->{'itemnumber'};
1395 =head2 get_itemnumbers_of
1397 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1399 Given a list of biblionumbers, return the list of corresponding itemnumbers
1400 for each biblionumber.
1402 Return a reference on a hash where keys are biblionumbers and values are
1403 references on array of itemnumbers.
1407 sub get_itemnumbers_of
{
1408 my @biblionumbers = @_;
1410 my $dbh = C4
::Context
->dbh;
1416 WHERE biblionumber IN (?' . ( ',?' x
scalar @biblionumbers - 1 ) . ')
1418 my $sth = $dbh->prepare($query);
1419 $sth->execute(@biblionumbers);
1423 while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1424 push @
{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1427 return \
%itemnumbers_of;
1430 =head2 get_hostitemnumbers_of
1432 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1434 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1436 Return a reference on a hash where key is a biblionumber and values are
1437 references on array of itemnumbers.
1442 sub get_hostitemnumbers_of
{
1443 my ($biblionumber) = @_;
1444 my $marcrecord = GetMarcBiblio
($biblionumber);
1446 return unless $marcrecord;
1448 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1450 my $marcflavor = C4
::Context
->preference('marcflavour');
1451 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1456 elsif ( $marcflavor eq 'UNIMARC' ) {
1462 foreach my $hostfield ( $marcrecord->field($tag) ) {
1463 my $hostbiblionumber = $hostfield->subfield($biblio_s);
1464 my $linkeditemnumber = $hostfield->subfield($item_s);
1466 if ( my $itemnumbers =
1467 get_itemnumbers_of
($hostbiblionumber)->{$hostbiblionumber} )
1469 @itemnumbers = @
$itemnumbers;
1471 foreach my $itemnumber (@itemnumbers) {
1472 if ( $itemnumber eq $linkeditemnumber ) {
1473 push( @returnhostitemnumbers, $itemnumber );
1479 return @returnhostitemnumbers;
1483 =head2 GetItemnumberFromBarcode
1485 $result = GetItemnumberFromBarcode($barcode);
1489 sub GetItemnumberFromBarcode
{
1491 my $dbh = C4
::Context
->dbh;
1494 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1495 $rq->execute($barcode);
1496 my ($result) = $rq->fetchrow;
1500 =head2 GetBarcodeFromItemnumber
1502 $result = GetBarcodeFromItemnumber($itemnumber);
1506 sub GetBarcodeFromItemnumber
{
1507 my ($itemnumber) = @_;
1508 my $dbh = C4
::Context
->dbh;
1511 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1512 $rq->execute($itemnumber);
1513 my ($result) = $rq->fetchrow;
1517 =head2 GetHiddenItemnumbers
1519 my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1521 Given a list of items it checks which should be hidden from the OPAC given
1522 the current configuration. Returns a list of itemnumbers corresponding to
1523 those that should be hidden.
1527 sub GetHiddenItemnumbers
{
1531 my $yaml = C4
::Context
->preference('OpacHiddenItems');
1532 return () if (! $yaml =~ /\S/ );
1533 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1536 $hidingrules = YAML
::Load
($yaml);
1539 warn "Unable to parse OpacHiddenItems syspref : $@";
1542 my $dbh = C4
::Context
->dbh;
1545 foreach my $item (@items) {
1547 # We check each rule
1548 foreach my $field (keys %$hidingrules) {
1550 if (exists $item->{$field}) {
1551 $val = $item->{$field};
1554 my $query = "SELECT $field from items where itemnumber = ?";
1555 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1557 $val = '' unless defined $val;
1559 # If the results matches the values in the yaml file
1560 if (any
{ $val eq $_ } @
{$hidingrules->{$field}}) {
1562 # We add the itemnumber to the list
1563 push @resultitems, $item->{'itemnumber'};
1565 # If at least one rule matched for an item, no need to test the others
1570 return @resultitems;
1573 =head1 LIMITED USE FUNCTIONS
1575 The following functions, while part of the public API,
1576 are not exported. This is generally because they are
1577 meant to be used by only one script for a specific
1578 purpose, and should not be used in any other context
1579 without careful thought.
1585 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1587 Returns MARC::Record of the item passed in parameter.
1588 This function is meant for use only in C<cataloguing/additem.pl>,
1589 where it is needed to support that script's MARC-like
1595 my ( $biblionumber, $itemnumber ) = @_;
1597 # GetMarcItem has been revised so that it does the following:
1598 # 1. Gets the item information from the items table.
1599 # 2. Converts it to a MARC field for storage in the bib record.
1601 # The previous behavior was:
1602 # 1. Get the bib record.
1603 # 2. Return the MARC tag corresponding to the item record.
1605 # The difference is that one treats the items row as authoritative,
1606 # while the other treats the MARC representation as authoritative
1607 # under certain circumstances.
1609 my $itemrecord = GetItem
($itemnumber);
1611 # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1612 # Also, don't emit a subfield if the underlying field is blank.
1615 return Item2Marc
($itemrecord,$biblionumber);
1619 my ($itemrecord,$biblionumber)=@_;
1622 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ?
("items.$_" => $itemrecord->{$_}) : ()
1623 } keys %{ $itemrecord }
1625 my $itemmarc = TransformKohaToMarc
($mungeditem);
1626 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField
("items.itemnumber",GetFrameworkCode
($biblionumber)||'');
1628 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml
($mungeditem->{'items.more_subfields_xml'});
1629 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1630 foreach my $field ($itemmarc->field($itemtag)){
1631 $field->add_subfields(@
$unlinked_item_subfields);
1637 =head1 PRIVATE FUNCTIONS AND VARIABLES
1639 The following functions are not meant to be called
1640 directly, but are documented in order to explain
1641 the inner workings of C<C4::Items>.
1645 =head2 %derived_columns
1647 This hash keeps track of item columns that
1648 are strictly derived from other columns in
1649 the item record and are not meant to be set
1652 Each key in the hash should be the name of a
1653 column (as named by TransformMarcToKoha). Each
1654 value should be hashref whose keys are the
1655 columns on which the derived column depends. The
1656 hashref should also contain a 'BUILDER' key
1657 that is a reference to a sub that calculates
1662 my %derived_columns = (
1663 'items.cn_sort' => {
1664 'itemcallnumber' => 1,
1665 'items.cn_source' => 1,
1666 'BUILDER' => \
&_calc_items_cn_sort
,
1670 =head2 _set_derived_columns_for_add
1672 _set_derived_column_for_add($item);
1674 Given an item hash representing a new item to be added,
1675 calculate any derived columns. Currently the only
1676 such column is C<items.cn_sort>.
1680 sub _set_derived_columns_for_add
{
1683 foreach my $column (keys %derived_columns) {
1684 my $builder = $derived_columns{$column}->{'BUILDER'};
1685 my $source_values = {};
1686 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1687 next if $source_column eq 'BUILDER';
1688 $source_values->{$source_column} = $item->{$source_column};
1690 $builder->($item, $source_values);
1694 =head2 _set_derived_columns_for_mod
1696 _set_derived_column_for_mod($item);
1698 Given an item hash representing a new item to be modified.
1699 calculate any derived columns. Currently the only
1700 such column is C<items.cn_sort>.
1702 This routine differs from C<_set_derived_columns_for_add>
1703 in that it needs to handle partial item records. In other
1704 words, the caller of C<ModItem> may have supplied only one
1705 or two columns to be changed, so this function needs to
1706 determine whether any of the columns to be changed affect
1707 any of the derived columns. Also, if a derived column
1708 depends on more than one column, but the caller is not
1709 changing all of then, this routine retrieves the unchanged
1710 values from the database in order to ensure a correct
1715 sub _set_derived_columns_for_mod
{
1718 foreach my $column (keys %derived_columns) {
1719 my $builder = $derived_columns{$column}->{'BUILDER'};
1720 my $source_values = {};
1721 my %missing_sources = ();
1722 my $must_recalc = 0;
1723 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1724 next if $source_column eq 'BUILDER';
1725 if (exists $item->{$source_column}) {
1727 $source_values->{$source_column} = $item->{$source_column};
1729 $missing_sources{$source_column} = 1;
1733 foreach my $source_column (keys %missing_sources) {
1734 $source_values->{$source_column} = _get_single_item_column
($source_column, $item->{'itemnumber'});
1736 $builder->($item, $source_values);
1741 =head2 _do_column_fixes_for_mod
1743 _do_column_fixes_for_mod($item);
1745 Given an item hashref containing one or more
1746 columns to modify, fix up certain values.
1747 Specifically, set to 0 any passed value
1748 of C<notforloan>, C<damaged>, C<itemlost>, or
1749 C<withdrawn> that is either undefined or
1750 contains the empty string.
1754 sub _do_column_fixes_for_mod
{
1757 if (exists $item->{'notforloan'} and
1758 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1759 $item->{'notforloan'} = 0;
1761 if (exists $item->{'damaged'} and
1762 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1763 $item->{'damaged'} = 0;
1765 if (exists $item->{'itemlost'} and
1766 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1767 $item->{'itemlost'} = 0;
1769 if (exists $item->{'withdrawn'} and
1770 (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1771 $item->{'withdrawn'} = 0;
1773 if (exists $item->{location
}
1774 and $item->{location
} ne 'CART'
1775 and $item->{location
} ne 'PROC'
1776 and not $item->{permanent_location
}
1778 $item->{'permanent_location'} = $item->{'location'};
1780 if (exists $item->{'timestamp'}) {
1781 delete $item->{'timestamp'};
1785 =head2 _get_single_item_column
1787 _get_single_item_column($column, $itemnumber);
1789 Retrieves the value of a single column from an C<items>
1790 row specified by C<$itemnumber>.
1794 sub _get_single_item_column
{
1796 my $itemnumber = shift;
1798 my $dbh = C4
::Context
->dbh;
1799 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1800 $sth->execute($itemnumber);
1801 my ($value) = $sth->fetchrow();
1805 =head2 _calc_items_cn_sort
1807 _calc_items_cn_sort($item, $source_values);
1809 Helper routine to calculate C<items.cn_sort>.
1813 sub _calc_items_cn_sort
{
1815 my $source_values = shift;
1817 $item->{'items.cn_sort'} = GetClassSort
($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1820 =head2 _set_defaults_for_add
1822 _set_defaults_for_add($item_hash);
1824 Given an item hash representing an item to be added, set
1825 correct default values for columns whose default value
1826 is not handled by the DBMS. This includes the following
1833 C<items.dateaccessioned>
1855 sub _set_defaults_for_add
{
1857 $item->{dateaccessioned
} ||= output_pref
({ dt
=> dt_from_string
, dateformat
=> 'iso', dateonly
=> 1 });
1858 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
1861 =head2 _koha_new_item
1863 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1865 Perform the actual insert into the C<items> table.
1869 sub _koha_new_item
{
1870 my ( $item, $barcode ) = @_;
1871 my $dbh=C4
::Context
->dbh;
1873 $item->{permanent_location
} //= $item->{location
};
1874 _mod_item_dates
( $item );
1876 "INSERT INTO items SET
1878 biblioitemnumber = ?,
1880 dateaccessioned = ?,
1884 replacementprice = ?,
1885 replacementpricedate = ?,
1886 datelastborrowed = ?,
1894 coded_location_qualifier = ?,
1897 itemnotes_nonpublic = ?,
1901 permanent_location = ?,
1913 more_subfields_xml = ?,
1918 my $sth = $dbh->prepare($query);
1919 my $today = output_pref
({ dt
=> dt_from_string
, dateformat
=> 'iso', dateonly
=> 1 });
1921 $item->{'biblionumber'},
1922 $item->{'biblioitemnumber'},
1924 $item->{'dateaccessioned'},
1925 $item->{'booksellerid'},
1926 $item->{'homebranch'},
1928 $item->{'replacementprice'},
1929 $item->{'replacementpricedate'} || $today,
1930 $item->{datelastborrowed
},
1931 $item->{datelastseen
} || $today,
1933 $item->{'notforloan'},
1935 $item->{'itemlost'},
1936 $item->{'withdrawn'},
1937 $item->{'itemcallnumber'},
1938 $item->{'coded_location_qualifier'},
1939 $item->{'restricted'},
1940 $item->{'itemnotes'},
1941 $item->{'itemnotes_nonpublic'},
1942 $item->{'holdingbranch'},
1944 $item->{'location'},
1945 $item->{'permanent_location'},
1948 $item->{'renewals'},
1949 $item->{'reserves'},
1950 $item->{'items.cn_source'},
1951 $item->{'items.cn_sort'},
1954 $item->{'materials'},
1956 $item->{'enumchron'},
1957 $item->{'more_subfields_xml'},
1958 $item->{'copynumber'},
1959 $item->{'stocknumber'},
1960 $item->{'new_status'},
1964 if ( defined $sth->errstr ) {
1965 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1968 $itemnumber = $dbh->{'mysql_insertid'};
1971 return ( $itemnumber, $error );
1974 =head2 MoveItemFromBiblio
1976 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1978 Moves an item from a biblio to another
1980 Returns undef if the move failed or the biblionumber of the destination record otherwise
1984 sub MoveItemFromBiblio
{
1985 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1986 my $dbh = C4
::Context
->dbh;
1987 my ( $tobiblioitem ) = $dbh->selectrow_array(q
|
1988 SELECT biblioitemnumber
1990 WHERE biblionumber
= ?
1991 |, undef, $tobiblio );
1992 my $return = $dbh->do(q
|
1994 SET biblioitemnumber
= ?
,
1996 WHERE itemnumber
= ?
1997 AND biblionumber
= ?
1998 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2000 ModZebra
( $tobiblio, "specialUpdate", "biblioserver" );
2001 ModZebra
( $frombiblio, "specialUpdate", "biblioserver" );
2002 # Checking if the item we want to move is in an order
2003 require C4
::Acquisition
;
2004 my $order = C4
::Acquisition
::GetOrderFromItemnumber
($itemnumber);
2006 # Replacing the biblionumber within the order if necessary
2007 $order->{'biblionumber'} = $tobiblio;
2008 C4
::Acquisition
::ModOrder
($order);
2011 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2012 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2015 SET biblionumber
= ?
2016 WHERE itemnumber
= ?
2017 |, undef, $tobiblio, $itemnumber );
2024 =head2 ItemSafeToDelete
2026 ItemSafeToDelete( $biblionumber, $itemnumber);
2028 Exported function (core API) for checking whether an item record is safe to delete.
2030 returns 1 if the item is safe to delete,
2032 "book_on_loan" if the item is checked out,
2034 "not_same_branch" if the item is blocked by independent branches,
2036 "book_reserved" if the there are holds aganst the item, or
2038 "linked_analytics" if the item has linked analytic records.
2042 sub ItemSafeToDelete
{
2043 my ( $biblionumber, $itemnumber ) = @_;
2045 my $dbh = C4
::Context
->dbh;
2049 my $countanalytics = GetAnalyticsCount
($itemnumber);
2051 # check that there is no issue on this item before deletion.
2052 my $sth = $dbh->prepare(
2054 SELECT COUNT(*) FROM issues
2055 WHERE itemnumber = ?
2058 $sth->execute($itemnumber);
2059 my ($onloan) = $sth->fetchrow;
2061 my $item = GetItem
($itemnumber);
2064 $status = "book_on_loan";
2066 elsif ( defined C4
::Context
->userenv
2067 and !C4
::Context
->IsSuperLibrarian()
2068 and C4
::Context
->preference("IndependentBranches")
2069 and ( C4
::Context
->userenv->{branch
} ne $item->{'homebranch'} ) )
2071 $status = "not_same_branch";
2074 # check it doesn't have a waiting reserve
2075 $sth = $dbh->prepare(
2077 SELECT COUNT(*) FROM reserves
2078 WHERE (found = 'W' OR found = 'T')
2082 $sth->execute($itemnumber);
2083 my ($reserve) = $sth->fetchrow;
2085 $status = "book_reserved";
2087 elsif ( $countanalytics > 0 ) {
2088 $status = "linked_analytics";
2099 DelItemCheck( $biblionumber, $itemnumber);
2101 Exported function (core API) for deleting an item record in Koha if there no current issue.
2103 DelItemCheck wraps ItemSafeToDelete around DelItem.
2108 my ( $biblionumber, $itemnumber ) = @_;
2109 my $status = ItemSafeToDelete
( $biblionumber, $itemnumber );
2111 if ( $status == 1 ) {
2114 biblionumber
=> $biblionumber,
2115 itemnumber
=> $itemnumber
2122 =head2 _koha_modify_item
2124 my ($itemnumber,$error) =_koha_modify_item( $item );
2126 Perform the actual update of the C<items> row. Note that this
2127 routine accepts a hashref specifying the columns to update.
2131 sub _koha_modify_item
{
2133 my $dbh=C4
::Context
->dbh;
2136 my $query = "UPDATE items SET ";
2138 _mod_item_dates
( $item );
2139 for my $key ( keys %$item ) {
2140 next if ( $key eq 'itemnumber' );
2142 push @bind, $item->{$key};
2145 $query .= " WHERE itemnumber=?";
2146 push @bind, $item->{'itemnumber'};
2147 my $sth = $dbh->prepare($query);
2148 $sth->execute(@bind);
2150 $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2153 return ($item->{'itemnumber'},$error);
2156 sub _mod_item_dates
{ # date formatting for date fields in item hash
2158 return if !$item || ref($item) ne 'HASH';
2161 { $_ =~ /^onloan$|^date|date$|datetime$/ }
2163 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
2164 # NOTE: We do not (yet) have items fields ending with datetime
2165 # Fields with _on$ have been handled already
2167 foreach my $key ( @keys ) {
2168 next if !defined $item->{$key}; # skip undefs
2169 my $dt = eval { dt_from_string
( $item->{$key} ) };
2170 # eval: dt_from_string will die on us if we pass illegal dates
2173 if( defined $dt && ref($dt) eq 'DateTime' ) {
2174 if( $key =~ /datetime/ ) {
2175 $newstr = DateTime
::Format
::MySQL
->format_datetime($dt);
2177 $newstr = DateTime
::Format
::MySQL
->format_date($dt);
2180 $item->{$key} = $newstr; # might be undef to clear garbage
2184 =head2 _koha_delete_item
2186 _koha_delete_item( $itemnum );
2188 Internal function to delete an item record from the koha tables
2192 sub _koha_delete_item
{
2193 my ( $itemnum ) = @_;
2195 my $dbh = C4
::Context
->dbh;
2196 # save the deleted item to deleteditems table
2197 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2198 $sth->execute($itemnum);
2199 my $data = $sth->fetchrow_hashref();
2201 # There is no item to delete
2202 return 0 unless $data;
2204 my $query = "INSERT INTO deleteditems SET ";
2206 foreach my $key ( keys %$data ) {
2207 next if ( $key eq 'timestamp' ); # timestamp will be set by db
2208 $query .= "$key = ?,";
2209 push( @bind, $data->{$key} );
2212 $sth = $dbh->prepare($query);
2213 $sth->execute(@bind);
2215 # delete from items table
2216 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2217 my $deleted = $sth->execute($itemnum);
2218 return ( $deleted == 1 ) ?
1 : 0;
2221 =head2 _marc_from_item_hash
2223 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2225 Given an item hash representing a complete item record,
2226 create a C<MARC::Record> object containing an embedded
2227 tag representing that item.
2229 The third, optional parameter C<$unlinked_item_subfields> is
2230 an arrayref of subfields (not mapped to C<items> fields per the
2231 framework) to be added to the MARC representation
2236 sub _marc_from_item_hash
{
2238 my $frameworkcode = shift;
2239 my $unlinked_item_subfields;
2241 $unlinked_item_subfields = shift;
2244 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2245 # Also, don't emit a subfield if the underlying field is blank.
2246 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2247 (/^items\./ ?
($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2248 : () } keys %{ $item } };
2250 my $item_marc = MARC
::Record
->new();
2251 foreach my $item_field ( keys %{$mungeditem} ) {
2252 my ( $tag, $subfield ) = GetMarcFromKohaField
( $item_field, $frameworkcode );
2253 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2254 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2255 foreach my $value (@values){
2256 if ( my $field = $item_marc->field($tag) ) {
2257 $field->add_subfields( $subfield => $value );
2259 my $add_subfields = [];
2260 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2261 $add_subfields = $unlinked_item_subfields;
2263 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @
$add_subfields );
2271 =head2 _repack_item_errors
2273 Add an error message hash generated by C<CheckItemPreSave>
2274 to a list of errors.
2278 sub _repack_item_errors
{
2279 my $item_sequence_num = shift;
2280 my $item_ref = shift;
2281 my $error_ref = shift;
2283 my @repacked_errors = ();
2285 foreach my $error_code (sort keys %{ $error_ref }) {
2286 my $repacked_error = {};
2287 $repacked_error->{'item_sequence'} = $item_sequence_num;
2288 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ?
$item_ref->{'barcode'} : '';
2289 $repacked_error->{'error_code'} = $error_code;
2290 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2291 push @repacked_errors, $repacked_error;
2294 return @repacked_errors;
2297 =head2 _get_unlinked_item_subfields
2299 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2303 sub _get_unlinked_item_subfields
{
2304 my $original_item_marc = shift;
2305 my $frameworkcode = shift;
2307 my $marcstructure = GetMarcStructure
(1, $frameworkcode, { unsafe
=> 1 });
2309 # assume that this record has only one field, and that that
2310 # field contains only the item information
2312 my @fields = $original_item_marc->fields();
2313 if ($#fields > -1) {
2314 my $field = $fields[0];
2315 my $tag = $field->tag();
2316 foreach my $subfield ($field->subfields()) {
2317 if (defined $subfield->[1] and
2318 $subfield->[1] ne '' and
2319 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2320 push @
$subfields, $subfield->[0] => $subfield->[1];
2327 =head2 _get_unlinked_subfields_xml
2329 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2333 sub _get_unlinked_subfields_xml
{
2334 my $unlinked_item_subfields = shift;
2337 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2338 my $marc = MARC
::Record
->new();
2339 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2340 # used in the framework
2341 $marc->append_fields(MARC
::Field
->new('999', ' ', ' ', @
$unlinked_item_subfields));
2342 $marc->encoding("UTF-8");
2343 $xml = $marc->as_xml("USMARC");
2349 =head2 _parse_unlinked_item_subfields_from_xml
2351 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2355 sub _parse_unlinked_item_subfields_from_xml
{
2357 require C4
::Charset
;
2358 return unless defined $xml and $xml ne "";
2359 my $marc = MARC
::Record
->new_from_xml(C4
::Charset
::StripNonXmlChars
($xml),'UTF-8');
2360 my $unlinked_subfields = [];
2361 my @fields = $marc->fields();
2362 if ($#fields > -1) {
2363 foreach my $subfield ($fields[0]->subfields()) {
2364 push @
$unlinked_subfields, $subfield->[0] => $subfield->[1];
2367 return $unlinked_subfields;
2370 =head2 GetAnalyticsCount
2372 $count= &GetAnalyticsCount($itemnumber)
2374 counts Usage of itemnumber in Analytical bibliorecords.
2378 sub GetAnalyticsCount
{
2379 my ($itemnumber) = @_;
2381 ### ZOOM search here
2383 $query= "hi=".$itemnumber;
2384 my $searcher = Koha
::SearchEngine
::Search
->new({index => $Koha::SearchEngine
::BIBLIOS_INDEX
});
2385 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2389 =head2 SearchItemsByField
2391 my $items = SearchItemsByField($field, $value);
2393 SearchItemsByField will search for items on a specific given field.
2394 For instance you can search all items with a specific stocknumber like this:
2396 my $items = SearchItemsByField('stocknumber', $stocknumber);
2400 sub SearchItemsByField
{
2401 my ($field, $value) = @_;
2408 my ($results) = SearchItems
($filters);
2412 sub _SearchItems_build_where_fragment
{
2415 my $dbh = C4
::Context
->dbh;
2418 if (exists($filter->{conjunction
})) {
2419 my (@where_strs, @where_args);
2420 foreach my $f (@
{ $filter->{filters
} }) {
2421 my $fragment = _SearchItems_build_where_fragment
($f);
2423 push @where_strs, $fragment->{str
};
2424 push @where_args, @
{ $fragment->{args
} };
2429 $where_str = '(' . join (' ' . $filter->{conjunction
} . ' ', @where_strs) . ')';
2432 args
=> \
@where_args,
2436 my @columns = Koha
::Database
->new()->schema()->resultset('Item')->result_source->columns;
2437 push @columns, Koha
::Database
->new()->schema()->resultset('Biblio')->result_source->columns;
2438 push @columns, Koha
::Database
->new()->schema()->resultset('Biblioitem')->result_source->columns;
2439 my @operators = qw(= != > < >= <= like);
2440 my $field = $filter->{field
};
2441 if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2442 my $op = $filter->{operator
};
2443 my $query = $filter->{query
};
2445 if (!$op or (0 == grep /^$op$/, @operators)) {
2446 $op = '='; # default operator
2450 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2452 my $marcsubfield = $2;
2453 my ($kohafield) = $dbh->selectrow_array(q
|
2454 SELECT kohafield FROM marc_subfield_structure
2455 WHERE tagfield
=? AND tagsubfield
=? AND frameworkcode
=''
2456 |, undef, $marcfield, $marcsubfield);
2459 $column = $kohafield;
2461 # MARC field is not linked to a DB field so we need to use
2462 # ExtractValue on marcxml from biblio_metadata or
2463 # items.more_subfields_xml, depending on the MARC field.
2466 my ($itemfield) = GetMarcFromKohaField
('items.itemnumber');
2467 if ($marcfield eq $itemfield) {
2468 $sqlfield = 'more_subfields_xml';
2469 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2471 $sqlfield = 'metadata'; # From biblio_metadata
2472 if ($marcfield < 10) {
2473 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2475 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2478 $column = "ExtractValue($sqlfield, '$xpath')";
2484 if (ref $query eq 'ARRAY') {
2487 } elsif ($op eq '!=') {
2491 str
=> "$column $op (" . join (',', ('?') x @
$query) . ")",
2496 str
=> "$column $op ?",
2503 return $where_fragment;
2508 my ($items, $total) = SearchItems($filter, $params);
2510 Perform a search among items
2512 $filter is a reference to a hash which can be a filter, or a combination of filters.
2514 A filter has the following keys:
2518 =item * field: the name of a SQL column in table items
2520 =item * query: the value to search in this column
2522 =item * operator: comparison operator. Can be one of = != > < >= <= like
2526 A combination of filters hash the following keys:
2530 =item * conjunction: 'AND' or 'OR'
2532 =item * filters: array ref of filters
2536 $params is a reference to a hash that can contain the following parameters:
2540 =item * rows: Number of items to return. 0 returns everything (default: 0)
2542 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2545 =item * sortby: A SQL column name in items table to sort on
2547 =item * sortorder: 'ASC' or 'DESC'
2554 my ($filter, $params) = @_;
2558 return unless ref $filter eq 'HASH';
2559 return unless ref $params eq 'HASH';
2561 # Default parameters
2562 $params->{rows
} ||= 0;
2563 $params->{page
} ||= 1;
2564 $params->{sortby
} ||= 'itemnumber';
2565 $params->{sortorder
} ||= 'ASC';
2567 my ($where_str, @where_args);
2568 my $where_fragment = _SearchItems_build_where_fragment
($filter);
2569 if ($where_fragment) {
2570 $where_str = $where_fragment->{str
};
2571 @where_args = @
{ $where_fragment->{args
} };
2574 my $dbh = C4
::Context
->dbh;
2576 SELECT SQL_CALC_FOUND_ROWS items.*
2578 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2579 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2580 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
2583 if (defined $where_str and $where_str ne '') {
2584 $query .= qq{ AND
$where_str };
2587 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
2588 push @where_args, C4
::Context
->preference('marcflavour');
2590 my @columns = Koha
::Database
->new()->schema()->resultset('Item')->result_source->columns;
2591 push @columns, Koha
::Database
->new()->schema()->resultset('Biblio')->result_source->columns;
2592 push @columns, Koha
::Database
->new()->schema()->resultset('Biblioitem')->result_source->columns;
2593 my $sortby = (0 < grep {$params->{sortby
} eq $_} @columns)
2594 ?
$params->{sortby
} : 'itemnumber';
2595 my $sortorder = (uc($params->{sortorder
}) eq 'ASC') ?
'ASC' : 'DESC';
2596 $query .= qq{ ORDER BY
$sortby $sortorder };
2598 my $rows = $params->{rows
};
2601 my $offset = $rows * ($params->{page
}-1);
2602 $query .= qq { LIMIT ?
, ?
};
2603 push @limit_args, $offset, $rows;
2606 my $sth = $dbh->prepare($query);
2607 my $rv = $sth->execute(@where_args, @limit_args);
2609 return unless ($rv);
2610 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2612 return ($sth->fetchall_arrayref({}), $total_rows);
2616 =head1 OTHER FUNCTIONS
2620 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2622 Find the given $subfield in the given $tag in the given
2623 MARC::Record $record. If the subfield is found, returns
2624 the (indicators, value) pair; otherwise, (undef, undef) is
2628 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2629 I suggest we export it from this module.
2634 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2637 if ( $tagfield < 10 ) {
2638 if ( $record->field($tagfield) ) {
2639 push @result, $record->field($tagfield)->data();
2644 foreach my $field ( $record->field($tagfield) ) {
2645 my @subfields = $field->subfields();
2646 foreach my $subfield (@subfields) {
2647 if ( @
$subfield[0] eq $insubfield ) {
2648 push @result, @
$subfield[1];
2649 $indicator = $field->indicator(1) . $field->indicator(2);
2654 return ( $indicator, @result );
2658 =head2 PrepareItemrecordDisplay
2660 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2662 Returns a hash with all the fields for Display a given item data in a template
2664 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2668 sub PrepareItemrecordDisplay
{
2670 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2672 my $dbh = C4
::Context
->dbh;
2673 $frameworkcode = &GetFrameworkCode
($bibnum) if $bibnum;
2674 my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField
( "items.itemnumber", $frameworkcode );
2676 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2677 # a shared data structure. No plugin (including custom ones) should change
2678 # its contents. See also GetMarcStructure.
2679 my $tagslib = &GetMarcStructure
( 1, $frameworkcode, { unsafe
=> 1 } );
2681 # return nothing if we don't have found an existing framework.
2682 return q{} unless $tagslib;
2685 $itemrecord = C4
::Items
::GetMarcItem
( $bibnum, $itemnum );
2689 my $branch_limit = C4
::Context
->userenv ? C4
::Context
->userenv->{"branch"} : "";
2691 SELECT authorised_value
,lib FROM authorised_values
2694 LEFT JOIN authorised_values_branches ON
( id
= av_id
)
2699 $query .= qq{ AND
( branchcode
= ? OR branchcode IS NULL
)} if $branch_limit;
2700 $query .= qq{ ORDER BY lib
};
2701 my $authorised_values_sth = $dbh->prepare( $query );
2702 foreach my $tag ( sort keys %{$tagslib} ) {
2705 # loop through each subfield
2707 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2708 next if IsMarcStructureInternal
($tagslib->{$tag}{$subfield});
2709 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2710 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2712 $subfield_data{tag
} = $tag;
2713 $subfield_data{subfield
} = $subfield;
2714 $subfield_data{countsubfield
} = $cntsubf++;
2715 $subfield_data{kohafield
} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2716 $subfield_data{id
} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2718 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2719 $subfield_data{marc_lib
} = $tagslib->{$tag}->{$subfield}->{lib
};
2720 $subfield_data{mandatory
} = $tagslib->{$tag}->{$subfield}->{mandatory
};
2721 $subfield_data{repeatable
} = $tagslib->{$tag}->{$subfield}->{repeatable
};
2722 $subfield_data{hidden
} = "display:none"
2723 if ( ( $tagslib->{$tag}->{$subfield}->{hidden
} > 4 )
2724 || ( $tagslib->{$tag}->{$subfield}->{hidden
} < -4 ) );
2725 my ( $x, $defaultvalue );
2727 ( $x, $defaultvalue ) = _find_value
( $tag, $subfield, $itemrecord );
2729 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue
} unless $defaultvalue;
2730 if ( !defined $defaultvalue ) {
2731 $defaultvalue = q
||;
2733 $defaultvalue =~ s/"/"/g;
2736 # search for itemcallnumber if applicable
2737 if ( $tagslib->{$tag}->{$subfield}->{kohafield
} eq 'items.itemcallnumber'
2738 && C4
::Context
->preference('itemcallnumber') ) {
2739 my $CNtag = substr( C4
::Context
->preference('itemcallnumber'), 0, 3 );
2740 my $CNsubfield = substr( C4
::Context
->preference('itemcallnumber'), 3, 1 );
2741 if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2742 $defaultvalue = $field->subfield($CNsubfield);
2745 if ( $tagslib->{$tag}->{$subfield}->{kohafield
} eq 'items.itemcallnumber'
2747 && $defaultvalues->{'callnumber'} ) {
2748 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2749 # if the item record exists, only use default value if the item has no callnumber
2750 $defaultvalue = $defaultvalues->{callnumber
};
2751 } elsif ( !$itemrecord and $defaultvalues ) {
2752 # if the item record *doesn't* exists, always use the default value
2753 $defaultvalue = $defaultvalues->{callnumber
};
2756 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield
} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield
} eq 'items.homebranch' )
2758 && $defaultvalues->{'branchcode'} ) {
2759 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2760 $defaultvalue = $defaultvalues->{branchcode
};
2763 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield
} eq 'items.location' )
2765 && $defaultvalues->{'location'} ) {
2767 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2768 # if the item record exists, only use default value if the item has no locationr
2769 $defaultvalue = $defaultvalues->{location
};
2770 } elsif ( !$itemrecord and $defaultvalues ) {
2771 # if the item record *doesn't* exists, always use the default value
2772 $defaultvalue = $defaultvalues->{location
};
2775 if ( $tagslib->{$tag}->{$subfield}->{authorised_value
} ) {
2776 my @authorised_values;
2779 # builds list, depending on authorised value...
2781 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2782 if ( ( C4
::Context
->preference("IndependentBranches") )
2783 && !C4
::Context
->IsSuperLibrarian() ) {
2784 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2785 $sth->execute( C4
::Context
->userenv->{branch
} );
2786 push @authorised_values, ""
2787 unless ( $tagslib->{$tag}->{$subfield}->{mandatory
} );
2788 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2789 push @authorised_values, $branchcode;
2790 $authorised_lib{$branchcode} = $branchname;
2793 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2795 push @authorised_values, ""
2796 unless ( $tagslib->{$tag}->{$subfield}->{mandatory
} );
2797 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2798 push @authorised_values, $branchcode;
2799 $authorised_lib{$branchcode} = $branchname;
2803 $defaultvalue = C4
::Context
->userenv ? C4
::Context
->userenv->{branch
} : undef;
2804 if ( $defaultvalues and $defaultvalues->{branchcode
} ) {
2805 $defaultvalue = $defaultvalues->{branchcode
};
2809 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value
} eq "itemtypes" ) {
2810 my $itemtypes = Koha
::ItemTypes
->search_with_localization;
2811 push @authorised_values, ""
2812 unless ( $tagslib->{$tag}->{$subfield}->{mandatory
} );
2813 while ( my $itemtype = $itemtypes->next ) {
2814 push @authorised_values, $itemtype->itemtype;
2815 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2817 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2818 $defaultvalue = $defaultvalues->{'itemtype'};
2822 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value
} eq "cn_source" ) {
2823 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory
} );
2825 my $class_sources = GetClassSources
();
2826 my $default_source = C4
::Context
->preference("DefaultClassificationSource");
2828 foreach my $class_source (sort keys %$class_sources) {
2829 next unless $class_sources->{$class_source}->{'used'} or
2830 ($class_source eq $default_source);
2831 push @authorised_values, $class_source;
2832 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2835 $defaultvalue = $default_source;
2837 #---- "true" authorised value
2839 $authorised_values_sth->execute(
2840 $tagslib->{$tag}->{$subfield}->{authorised_value
},
2841 $branch_limit ?
$branch_limit : ()
2843 push @authorised_values, ""
2844 unless ( $tagslib->{$tag}->{$subfield}->{mandatory
} );
2845 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2846 push @authorised_values, $value;
2847 $authorised_lib{$value} = $lib;
2850 $subfield_data{marc_value
} = {
2852 values => \
@authorised_values,
2853 default => "$defaultvalue",
2854 labels
=> \
%authorised_lib,
2856 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder
} ) {
2858 require Koha
::FrameworkPlugin
;
2859 my $plugin = Koha
::FrameworkPlugin
->new({
2860 name
=> $tagslib->{$tag}->{$subfield}->{value_builder
},
2863 my $pars = { dbh
=> $dbh, record
=> undef, tagslib
=>$tagslib, id
=> $subfield_data{id
}, tabloop
=> undef };
2864 $plugin->build( $pars );
2865 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2866 $defaultvalue = $field->subfield($subfield);
2868 if( !$plugin->errstr ) {
2869 #TODO Move html to template; see report 12176/13397
2870 my $tab= $plugin->noclick?
'-1': '';
2871 my $class= $plugin->noclick?
' disabled': '';
2872 my $title= $plugin->noclick?
'No popup': 'Tag editor';
2873 $subfield_data{marc_value
} = qq[<input type
="text" id
="$subfield_data{id}" name
="field_value" class="input_marceditor" size
="50" maxlength
="255" value
="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2875 warn $plugin->errstr;
2876 $subfield_data{marc_value
} = qq(<input type
="text" id
="$subfield_data{id}" name
="field_value" class="input_marceditor" size
="50" maxlength
="255" value
="$defaultvalue" />); # supply default input form
2879 elsif ( $tag eq '' ) { # it's an hidden field
2880 $subfield_data{marc_value
} = qq(<input type
="hidden" tabindex
="1" id
="$subfield_data{id}" name
="field_value" class="input_marceditor" size
="50" maxlength
="255" value
="$defaultvalue" />);
2882 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2883 $subfield_data{marc_value
} = qq(<input type
="text" tabindex
="1" id
="$subfield_data{id}" name
="field_value" class="input_marceditor" size
="50" maxlength
="255" value
="$defaultvalue" />);
2885 elsif ( length($defaultvalue) > 100
2886 or (C4
::Context
->preference("marcflavour") eq "UNIMARC" and
2887 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2888 or (C4
::Context
->preference("marcflavour") eq "MARC21" and
2889 500 <= $tag && $tag < 600 )
2891 # oversize field (textarea)
2892 $subfield_data{marc_value
} = qq(<textarea tabindex
="1" id
="$subfield_data{id}" name
="field_value" class="input_marceditor" size
="50" maxlength
="255">$defaultvalue</textarea
>\n");
2894 $subfield_data{marc_value} = "<input type
=\"text
\" name
=\"field_value
\" value
=\"$defaultvalue\" size
=\"50\" maxlength
=\"255\" />";
2896 push( @loop_data, \%subfield_data );
2901 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2902 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2905 'itemtagfield' => $itemtagfield,
2906 'itemtagsubfield' => $itemtagsubfield,
2907 'itemnumber' => $itemnumber,
2908 'iteminformation' => \@loop_data
2912 sub ToggleNewStatus {
2913 my ( $params ) = @_;
2914 my @rules = @{ $params->{rules} };
2915 my $report_only = $params->{report_only};
2917 my $dbh = C4::Context->dbh;
2919 my @item_columns = map { "items
.$_" } Koha::Items->columns;
2920 my @biblioitem_columns = map { "biblioitems
.$_" } Koha::Biblioitems->columns;
2922 for my $rule ( @rules ) {
2923 my $age = $rule->{age};
2924 my $conditions = $rule->{conditions};
2925 my $substitutions = $rule->{substitutions};
2929 SELECT items.biblionumber, items.itemnumber
2931 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2934 for my $condition ( @$conditions ) {
2936 grep {/^$condition->{field}$/} @item_columns
2937 or grep {/^$condition->{field}$/} @biblioitem_columns
2939 if ( $condition->{value} =~ /\|/ ) {
2940 my @values = split /\|/, $condition->{value};
2941 $query .= qq| AND $condition->{field} IN (|
2942 . join( ',', ('?') x scalar @values )
2944 push @params, @values;
2946 $query .= qq| AND $condition->{field} = ?|;
2947 push @params, $condition->{value};
2951 if ( defined $age ) {
2952 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2955 my $sth = $dbh->prepare($query);
2956 $sth->execute( @params );
2957 while ( my $values = $sth->fetchrow_hashref ) {
2958 my $biblionumber = $values->{biblionumber};
2959 my $itemnumber = $values->{itemnumber};
2960 my $item = C4::Items::GetItem( $itemnumber );
2961 for my $substitution ( @$substitutions ) {
2962 next unless $substitution->{field};
2963 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
2964 unless $report_only;
2965 push @{ $report->{$itemnumber} }, $substitution;