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>.
23 use vars qw(@ISA @EXPORT);
40 get_hostitemnumbers_of
48 PrepareItemrecordDisplay
61 use List::MoreUtils qw(any);
63 use DateTime::Format::MySQL;
64 use Data::Dumper; # used as part of logging item record changes, not just for
65 # debugging; so please don't remove this
67 use Koha::AuthorisedValues;
68 use Koha::DateUtils qw(dt_from_string);
71 use Koha::Biblioitems;
74 use Koha::SearchEngine;
75 use Koha::SearchEngine::Search;
80 C4::Items - item management functions
84 This module contains an API for manipulating item
85 records in Koha, and is used by cataloguing, circulation,
86 acquisitions, and serials management.
88 # FIXME This POD is not up-to-date
89 A Koha item record is stored in two places: the
90 items table and embedded in a MARC tag in the XML
91 version of the associated bib record in C<biblioitems.marcxml>.
92 This is done to allow the item information to be readily
93 indexed (e.g., by Zebra), but means that each item
94 modification transaction must keep the items table
95 and the MARC XML in sync at all times.
97 The items table will be considered authoritative. In other
98 words, if there is ever a discrepancy between the items
99 table and the MARC XML, the items table should be considered
102 =head1 HISTORICAL NOTE
104 Most of the functions in C<C4::Items> were originally in
105 the C<C4::Biblio> module.
107 =head1 CORE EXPORTED FUNCTIONS
109 The following functions are meant for use by users
116 CartToShelf($itemnumber);
118 Set the current shelving location of the item record
119 to its stored permanent shelving location. This is
120 primarily used to indicate when an item whose current
121 location is a special processing ('PROC') or shelving cart
122 ('CART') location is back in the stacks.
127 my ( $itemnumber ) = @_;
129 unless ( $itemnumber ) {
130 croak "FAILED CartToShelf() - no itemnumber supplied";
133 my $item = Koha::Items->find($itemnumber);
134 if ( $item->location eq 'CART' ) {
135 $item->location($item->permanent_location)->store;
139 =head2 AddItemFromMarc
141 my ($biblionumber, $biblioitemnumber, $itemnumber)
142 = AddItemFromMarc($source_item_marc, $biblionumber);
144 Given a MARC::Record object containing an embedded item
145 record and a biblionumber, create a new item record.
149 sub AddItemFromMarc {
150 my ( $source_item_marc, $biblionumber ) = @_;
151 my $dbh = C4::Context->dbh;
153 # parse item hash from MARC
154 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
155 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
157 my $localitemmarc = MARC::Record->new;
158 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
160 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
161 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
162 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
163 $item_values->{biblionumber} = $biblionumber;
164 my $item = Koha::Item->new( $item_values )->store;
165 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
168 =head2 AddItemBatchFromMarc
170 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
171 $biblionumber, $biblioitemnumber, $frameworkcode);
173 Efficiently create item records from a MARC biblio record with
174 embedded item fields. This routine is suitable for batch jobs.
176 This API assumes that the bib record has already been
177 saved to the C<biblio> and C<biblioitems> tables. It does
178 not expect that C<biblio_metadata.metadata> is populated, but it
179 will do so via a call to ModBibiloMarc.
181 The goal of this API is to have a similar effect to using AddBiblio
182 and AddItems in succession, but without inefficient repeated
183 parsing of the MARC XML bib record.
185 This function returns an arrayref of new itemsnumbers and an arrayref of item
186 errors encountered during the processing. Each entry in the errors
187 list is a hashref containing the following keys:
193 Sequence number of original item tag in the MARC record.
197 Item barcode, provide to assist in the construction of
198 useful error messages.
202 Code representing the error condition. Can be 'duplicate_barcode',
203 'invalid_homebranch', or 'invalid_holdingbranch'.
205 =item error_information
207 Additional information appropriate to the error condition.
213 sub AddItemBatchFromMarc {
214 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
216 my @itemnumbers = ();
218 my $dbh = C4::Context->dbh;
220 # We modify the record, so lets work on a clone so we don't change the
222 $record = $record->clone();
223 # loop through the item tags and start creating items
224 my @bad_item_fields = ();
225 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
226 my $item_sequence_num = 0;
227 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
228 $item_sequence_num++;
229 # we take the item field and stick it into a new
230 # MARC record -- this is required so far because (FIXME)
231 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
232 # and there is no TransformMarcFieldToKoha
233 my $temp_item_marc = MARC::Record->new();
234 $temp_item_marc->append_fields($item_field);
236 # add biblionumber and biblioitemnumber
237 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
238 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
239 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
240 $item->{'biblionumber'} = $biblionumber;
241 $item->{'biblioitemnumber'} = $biblioitemnumber;
243 # check for duplicate barcode
244 my %item_errors = CheckItemPreSave($item);
246 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
247 push @bad_item_fields, $item_field;
251 _set_derived_columns_for_add($item);
252 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
253 warn $error if $error;
254 push @itemnumbers, $itemnumber; # FIXME not checking error
255 $item->{'itemnumber'} = $itemnumber;
257 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
259 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
260 $item_field->replace_with($new_item_marc->field($itemtag));
263 # remove any MARC item fields for rejected items
264 foreach my $item_field (@bad_item_fields) {
265 $record->delete_field($item_field);
268 # update the MARC biblio
269 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
271 return (\@itemnumbers, \@errors);
274 =head2 ModItemFromMarc
276 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
278 This function updates an item record based on a supplied
279 C<MARC::Record> object containing an embedded item field.
280 This API is meant for the use of C<additem.pl>; for
281 other purposes, C<ModItem> should be used.
283 This function uses the hash %default_values_for_mod_from_marc,
284 which contains default values for item fields to
285 apply when modifying an item. This is needed because
286 if an item field's value is cleared, TransformMarcToKoha
287 does not include the column in the
288 hash that's passed to ModItem, which without
289 use of this hash makes it impossible to clear
290 an item field's value. See bug 2466.
292 Note that only columns that can be directly
293 changed from the cataloging and serials
294 item editors are included in this hash.
300 sub _build_default_values_for_mod_marc {
301 # Has no framework parameter anymore, since Default is authoritative
302 # for Koha to MARC mappings.
304 my $cache = Koha::Caches->get_instance();
305 my $cache_key = "default_value_for_mod_marc-";
306 my $cached = $cache->get_from_cache($cache_key);
307 return $cached if $cached;
309 my $default_values = {
311 booksellerid => undef,
313 'items.cn_source' => undef,
314 coded_location_qualifier => undef,
318 holdingbranch => undef,
320 itemcallnumber => undef,
323 itemnotes_nonpublic => undef,
326 permanent_location => undef,
331 replacementprice => undef,
332 replacementpricedate => undef,
335 stocknumber => undef,
339 my %default_values_for_mod_from_marc;
340 while ( my ( $field, $default_value ) = each %$default_values ) {
341 my $kohafield = $field;
342 $kohafield =~ s|^([^\.]+)$|items.$1|;
343 $default_values_for_mod_from_marc{$field} = $default_value
344 if C4::Biblio::GetMarcFromKohaField( $kohafield );
347 $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
348 return \%default_values_for_mod_from_marc;
351 sub ModItemFromMarc {
352 my $item_marc = shift;
353 my $biblionumber = shift;
354 my $itemnumber = shift;
356 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
357 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
359 my $localitemmarc = MARC::Record->new;
360 $localitemmarc->append_fields( $item_marc->field($itemtag) );
361 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
362 my $default_values = _build_default_values_for_mod_marc();
363 foreach my $item_field ( keys %$default_values ) {
364 $item->{$item_field} = $default_values->{$item_field}
365 unless exists $item->{$item_field};
367 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
369 my $item_object = Koha::Items->find($itemnumber);
370 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
372 return $item_object->get_from_storage->unblessed;
375 =head2 ModItemTransfer
377 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
379 Marks an item as being transferred from one branch to another and records the trigger.
383 sub ModItemTransfer {
384 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
386 my $dbh = C4::Context->dbh;
387 my $item = Koha::Items->find( $itemnumber );
389 # Remove the 'shelving cart' location status if it is being used.
390 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
392 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
394 #new entry in branchtransfers....
395 my $sth = $dbh->prepare(
396 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
397 VALUES (?, ?, NOW(), ?, ?)");
398 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
400 # FIXME we are fetching the item twice in the 2 next statements!
401 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
402 ModDateLastSeen($itemnumber);
406 =head2 ModDateLastSeen
408 ModDateLastSeen( $itemnumber, $leave_item_lost );
410 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
411 C<$itemnumber> is the item number
412 C<$leave_item_lost> determines if a lost item will be found or remain lost
416 sub ModDateLastSeen {
417 my ( $itemnumber, $leave_item_lost ) = @_;
419 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
421 my $item = Koha::Items->find($itemnumber);
422 $item->datelastseen($today);
423 $item->itemlost(0) unless $leave_item_lost;
424 $item->store({ log_action => 0 });
427 =head2 CheckItemPreSave
429 my $item_ref = TransformMarcToKoha($marc, 'items');
431 my %errors = CheckItemPreSave($item_ref);
432 if (exists $errors{'duplicate_barcode'}) {
433 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
434 } elsif (exists $errors{'invalid_homebranch'}) {
435 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
436 } elsif (exists $errors{'invalid_holdingbranch'}) {
437 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
442 Given a hashref containing item fields, determine if it can be
443 inserted or updated in the database. Specifically, checks for
444 database integrity issues, and returns a hash containing any
445 of the following keys, if applicable.
449 =item duplicate_barcode
451 Barcode, if it duplicates one already found in the database.
453 =item invalid_homebranch
455 Home branch, if not defined in branches table.
457 =item invalid_holdingbranch
459 Holding branch, if not defined in branches table.
463 This function does NOT implement any policy-related checks,
464 e.g., whether current operator is allowed to save an
465 item that has a given branch code.
469 sub CheckItemPreSave {
470 my $item_ref = shift;
474 # check for duplicate barcode
475 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
476 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
477 if ($existing_item) {
478 if (!exists $item_ref->{'itemnumber'} # new item
479 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
480 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
485 # check for valid home branch
486 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
487 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
488 unless (defined $home_library) {
489 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
493 # check for valid holding branch
494 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
495 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
496 unless (defined $holding_library) {
497 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
505 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
507 The following functions provide various ways of
508 getting an item record, a set of item records, or
509 lists of authorized values for certain item fields.
513 =head2 GetItemsForInventory
515 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
516 minlocation => $minlocation,
517 maxlocation => $maxlocation,
518 location => $location,
519 itemtype => $itemtype,
520 ignoreissued => $ignoreissued,
521 datelastseen => $datelastseen,
522 branchcode => $branchcode,
526 statushash => $statushash,
529 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
531 The sub returns a reference to a list of hashes, each containing
532 itemnumber, author, title, barcode, item callnumber, and date last
533 seen. It is ordered by callnumber then title.
535 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
536 the datelastseen can be used to specify that you want to see items not seen since a past date only.
537 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
538 $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.
540 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
544 sub GetItemsForInventory {
545 my ( $parameters ) = @_;
546 my $minlocation = $parameters->{'minlocation'} // '';
547 my $maxlocation = $parameters->{'maxlocation'} // '';
548 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
549 my $location = $parameters->{'location'} // '';
550 my $itemtype = $parameters->{'itemtype'} // '';
551 my $ignoreissued = $parameters->{'ignoreissued'} // '';
552 my $datelastseen = $parameters->{'datelastseen'} // '';
553 my $branchcode = $parameters->{'branchcode'} // '';
554 my $branch = $parameters->{'branch'} // '';
555 my $offset = $parameters->{'offset'} // '';
556 my $size = $parameters->{'size'} // '';
557 my $statushash = $parameters->{'statushash'} // '';
558 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
560 my $dbh = C4::Context->dbh;
561 my ( @bind_params, @where_strings );
563 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
564 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
566 my $select_columns = q{
567 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
569 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
572 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
573 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
576 for my $authvfield (keys %$statushash){
577 if ( scalar @{$statushash->{$authvfield}} > 0 ){
578 my $joinedvals = join ',', @{$statushash->{$authvfield}};
579 push @where_strings, "$authvfield in (" . $joinedvals . ")";
585 push @where_strings, 'items.cn_sort >= ?';
586 push @bind_params, $min_cnsort;
590 push @where_strings, 'items.cn_sort <= ?';
591 push @bind_params, $max_cnsort;
595 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
596 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
597 push @bind_params, $datelastseen;
601 push @where_strings, 'items.location = ?';
602 push @bind_params, $location;
606 if($branch eq "homebranch"){
607 push @where_strings, 'items.homebranch = ?';
609 push @where_strings, 'items.holdingbranch = ?';
611 push @bind_params, $branchcode;
615 push @where_strings, 'biblioitems.itemtype = ?';
616 push @bind_params, $itemtype;
619 if ( $ignoreissued) {
620 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
621 push @where_strings, 'issues.date_due IS NULL';
624 if ( $ignore_waiting_holds ) {
625 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
626 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
629 if ( @where_strings ) {
631 $query .= join ' AND ', @where_strings;
633 my $count_query = $select_count . $query;
634 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
635 $query .= " LIMIT $offset, $size" if ($offset and $size);
636 $query = $select_columns . $query;
637 my $sth = $dbh->prepare($query);
638 $sth->execute( @bind_params );
641 my $tmpresults = $sth->fetchall_arrayref({});
642 $sth = $dbh->prepare( $count_query );
643 $sth->execute( @bind_params );
644 my ($iTotalRecords) = $sth->fetchrow_array();
646 my @avs = Koha::AuthorisedValues->search(
647 { 'marc_subfield_structures.kohafield' => { '>' => '' },
648 'me.authorised_value' => { '>' => '' },
650 { join => { category => 'marc_subfield_structures' },
651 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
652 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
653 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
657 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
659 foreach my $row (@$tmpresults) {
662 foreach (keys %$row) {
665 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
668 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
674 return (\@results, $iTotalRecords);
679 @results = GetItemsInfo($biblionumber);
681 Returns information about items with the given biblionumber.
683 C<GetItemsInfo> returns a list of references-to-hash. Each element
684 contains a number of keys. Most of them are attributes from the
685 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
686 Koha database. Other keys include:
690 =item C<$data-E<gt>{branchname}>
692 The name (not the code) of the branch to which the book belongs.
694 =item C<$data-E<gt>{datelastseen}>
696 This is simply C<items.datelastseen>, except that while the date is
697 stored in YYYY-MM-DD format in the database, here it is converted to
698 DD/MM/YYYY format. A NULL date is returned as C<//>.
700 =item C<$data-E<gt>{datedue}>
702 =item C<$data-E<gt>{class}>
704 This is the concatenation of C<biblioitems.classification>, the book's
705 Dewey code, and C<biblioitems.subclass>.
707 =item C<$data-E<gt>{ocount}>
709 I think this is the number of copies of the book available.
711 =item C<$data-E<gt>{order}>
713 If this is set, it is set to C<One Order>.
720 my ( $biblionumber ) = @_;
721 my $dbh = C4::Context->dbh;
722 require C4::Languages;
723 my $language = C4::Languages::getlanguage();
729 biblioitems.itemtype,
732 biblioitems.publicationyear,
733 biblioitems.publishercode,
734 biblioitems.volumedate,
735 biblioitems.volumedesc,
738 items.notforloan as itemnotforloan,
739 issues.borrowernumber,
740 issues.date_due as datedue,
741 issues.onsite_checkout,
742 borrowers.cardnumber,
745 borrowers.branchcode as bcode,
747 serial.publisheddate,
748 itemtypes.description,
749 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
750 itemtypes.notforloan as notforloan_per_itemtype,
754 holding.opac_info as holding_branch_opac_info,
755 home.opac_info as home_branch_opac_info,
756 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
758 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
759 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
760 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
761 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
762 LEFT JOIN issues USING (itemnumber)
763 LEFT JOIN borrowers USING (borrowernumber)
764 LEFT JOIN serialitems USING (itemnumber)
765 LEFT JOIN serial USING (serialid)
766 LEFT JOIN itemtypes ON itemtypes.itemtype = "
767 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
769 LEFT JOIN tmp_holdsqueue USING (itemnumber)
770 LEFT JOIN localization ON itemtypes.itemtype = localization.code
771 AND localization.entity = 'itemtypes'
772 AND localization.lang = ?
775 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
776 my $sth = $dbh->prepare($query);
777 $sth->execute($language, $biblionumber);
782 my $userenv = C4::Context->userenv;
783 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
784 while ( my $data = $sth->fetchrow_hashref ) {
785 if ( $data->{borrowernumber} && $want_not_same_branch) {
786 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
789 $serial ||= $data->{'serial'};
792 # get notforloan complete status if applicable
793 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
794 $data->{notforloanvalue} = $descriptions->{lib} // '';
795 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
797 # get restricted status and description if applicable
798 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
799 $data->{restrictedvalue} = $descriptions->{lib} // '';
800 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
802 # my stack procedures
803 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
804 $data->{stack} = $descriptions->{lib} // '';
806 # Find the last 3 people who borrowed this item.
807 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
809 AND old_issues.borrowernumber = borrowers.borrowernumber
810 ORDER BY returndate DESC
812 $sth2->execute($data->{'itemnumber'});
814 while (my $data2 = $sth2->fetchrow_hashref()) {
815 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
816 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
817 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
821 $results[$i] = $data;
826 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
830 =head2 GetItemsLocationInfo
832 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
834 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
836 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
840 =item C<$data-E<gt>{homebranch}>
842 Branch Name of the item's homebranch
844 =item C<$data-E<gt>{holdingbranch}>
846 Branch Name of the item's holdingbranch
848 =item C<$data-E<gt>{location}>
850 Item's shelving location code
852 =item C<$data-E<gt>{location_intranet}>
854 The intranet description for the Shelving Location as set in authorised_values 'LOC'
856 =item C<$data-E<gt>{location_opac}>
858 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
861 =item C<$data-E<gt>{itemcallnumber}>
863 Item's itemcallnumber
865 =item C<$data-E<gt>{cn_sort}>
867 Item's call number normalized for sorting
873 sub GetItemsLocationInfo {
874 my $biblionumber = shift;
877 my $dbh = C4::Context->dbh;
878 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
879 location, itemcallnumber, cn_sort
880 FROM items, branches as a, branches as b
881 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
883 ORDER BY cn_sort ASC";
884 my $sth = $dbh->prepare($query);
885 $sth->execute($biblionumber);
887 while ( my $data = $sth->fetchrow_hashref ) {
888 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
889 $av = $av->count ? $av->next : undef;
890 $data->{location_intranet} = $av ? $av->lib : '';
891 $data->{location_opac} = $av ? $av->opac_description : '';
892 push @results, $data;
897 =head2 GetHostItemsInfo
899 $hostiteminfo = GetHostItemsInfo($hostfield);
900 Returns the iteminfo for items linked to records via a host field
904 sub GetHostItemsInfo {
908 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
909 return @returnitemsInfo;
913 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
914 C4::Context->preference('marcflavour') eq 'NORMARC') {
915 @fields = $record->field('773');
916 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
917 @fields = $record->field('461');
920 foreach my $hostfield ( @fields ) {
921 my $hostbiblionumber = $hostfield->subfield("0");
922 my $linkeditemnumber = $hostfield->subfield("9");
923 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
924 foreach my $hostitemInfo (@hostitemInfos) {
925 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
926 push @returnitemsInfo, $hostitemInfo;
931 return @returnitemsInfo;
934 =head2 get_hostitemnumbers_of
936 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
938 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
940 Return a reference on a hash where key is a biblionumber and values are
941 references on array of itemnumbers.
946 sub get_hostitemnumbers_of {
947 my ($biblionumber) = @_;
949 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
953 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
954 return unless $marcrecord;
956 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
958 my $marcflavor = C4::Context->preference('marcflavour');
959 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
964 elsif ( $marcflavor eq 'UNIMARC' ) {
970 foreach my $hostfield ( $marcrecord->field($tag) ) {
971 my $hostbiblionumber = $hostfield->subfield($biblio_s);
972 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
973 my $linkeditemnumber = $hostfield->subfield($item_s);
974 if ( ! $linkeditemnumber ) {
975 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
978 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
979 push @returnhostitemnumbers, $linkeditemnumber
983 return @returnhostitemnumbers;
986 =head2 GetHiddenItemnumbers
988 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
990 Given a list of items it checks which should be hidden from the OPAC given
991 the current configuration. Returns a list of itemnumbers corresponding to
992 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
997 sub GetHiddenItemnumbers {
999 my $items = $params->{items};
1000 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1001 foreach my $except (split(/\|/, $exceptions)){
1002 if ($params->{'borcat'} eq $except){
1003 return; # we don't hide anything for this borrower category
1009 my $yaml = C4::Context->preference('OpacHiddenItems');
1010 return () if (! $yaml =~ /\S/ );
1011 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1014 $hidingrules = YAML::Load($yaml);
1017 warn "Unable to parse OpacHiddenItems syspref : $@";
1020 my $dbh = C4::Context->dbh;
1023 foreach my $item (@$items) {
1025 # We check each rule
1026 foreach my $field (keys %$hidingrules) {
1028 if (exists $item->{$field}) {
1029 $val = $item->{$field};
1032 my $query = "SELECT $field from items where itemnumber = ?";
1033 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1035 $val = '' unless defined $val;
1037 # If the results matches the values in the yaml file
1038 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1040 # We add the itemnumber to the list
1041 push @resultitems, $item->{'itemnumber'};
1043 # If at least one rule matched for an item, no need to test the others
1048 return @resultitems;
1051 =head1 LIMITED USE FUNCTIONS
1053 The following functions, while part of the public API,
1054 are not exported. This is generally because they are
1055 meant to be used by only one script for a specific
1056 purpose, and should not be used in any other context
1057 without careful thought.
1063 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1065 Returns MARC::Record of the item passed in parameter.
1066 This function is meant for use only in C<cataloguing/additem.pl>,
1067 where it is needed to support that script's MARC-like
1073 my ( $biblionumber, $itemnumber ) = @_;
1075 # GetMarcItem has been revised so that it does the following:
1076 # 1. Gets the item information from the items table.
1077 # 2. Converts it to a MARC field for storage in the bib record.
1079 # The previous behavior was:
1080 # 1. Get the bib record.
1081 # 2. Return the MARC tag corresponding to the item record.
1083 # The difference is that one treats the items row as authoritative,
1084 # while the other treats the MARC representation as authoritative
1085 # under certain circumstances.
1087 my $item = Koha::Items->find($itemnumber) or return;
1089 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1090 # Also, don't emit a subfield if the underlying field is blank.
1092 return Item2Marc($item->unblessed, $biblionumber);
1096 my ($itemrecord,$biblionumber)=@_;
1099 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1100 } keys %{ $itemrecord }
1102 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1103 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1104 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1105 "items.itemnumber", $framework,
1108 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1109 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1110 foreach my $field ($itemmarc->field($itemtag)){
1111 $field->add_subfields(@$unlinked_item_subfields);
1117 =head1 PRIVATE FUNCTIONS AND VARIABLES
1119 The following functions are not meant to be called
1120 directly, but are documented in order to explain
1121 the inner workings of C<C4::Items>.
1125 =head2 %derived_columns
1127 This hash keeps track of item columns that
1128 are strictly derived from other columns in
1129 the item record and are not meant to be set
1132 Each key in the hash should be the name of a
1133 column (as named by TransformMarcToKoha). Each
1134 value should be hashref whose keys are the
1135 columns on which the derived column depends. The
1136 hashref should also contain a 'BUILDER' key
1137 that is a reference to a sub that calculates
1142 my %derived_columns = (
1143 'items.cn_sort' => {
1144 'itemcallnumber' => 1,
1145 'items.cn_source' => 1,
1146 'BUILDER' => \&_calc_items_cn_sort,
1150 =head2 _set_derived_columns_for_add
1152 _set_derived_column_for_add($item);
1154 Given an item hash representing a new item to be added,
1155 calculate any derived columns. Currently the only
1156 such column is C<items.cn_sort>.
1160 sub _set_derived_columns_for_add {
1163 foreach my $column (keys %derived_columns) {
1164 my $builder = $derived_columns{$column}->{'BUILDER'};
1165 my $source_values = {};
1166 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1167 next if $source_column eq 'BUILDER';
1168 $source_values->{$source_column} = $item->{$source_column};
1170 $builder->($item, $source_values);
1174 =head2 _get_single_item_column
1176 _get_single_item_column($column, $itemnumber);
1178 Retrieves the value of a single column from an C<items>
1179 row specified by C<$itemnumber>.
1183 sub _get_single_item_column {
1185 my $itemnumber = shift;
1187 my $dbh = C4::Context->dbh;
1188 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1189 $sth->execute($itemnumber);
1190 my ($value) = $sth->fetchrow();
1194 =head2 _calc_items_cn_sort
1196 _calc_items_cn_sort($item, $source_values);
1198 Helper routine to calculate C<items.cn_sort>.
1202 sub _calc_items_cn_sort {
1204 my $source_values = shift;
1206 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1209 =head2 _koha_new_item
1211 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1213 Perform the actual insert into the C<items> table.
1217 sub _koha_new_item {
1218 my ( $item, $barcode ) = @_;
1219 my $dbh=C4::Context->dbh;
1221 $item->{permanent_location} //= $item->{location};
1222 _mod_item_dates( $item );
1224 "INSERT INTO items SET
1226 biblioitemnumber = ?,
1228 dateaccessioned = ?,
1232 replacementprice = ?,
1233 replacementpricedate = ?,
1234 datelastborrowed = ?,
1242 coded_location_qualifier = ?,
1245 itemnotes_nonpublic = ?,
1248 permanent_location = ?,
1260 more_subfields_xml = ?,
1265 my $sth = $dbh->prepare($query);
1266 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1268 $item->{'biblionumber'},
1269 $item->{'biblioitemnumber'},
1271 $item->{'dateaccessioned'},
1272 $item->{'booksellerid'},
1273 $item->{'homebranch'},
1275 $item->{'replacementprice'},
1276 $item->{'replacementpricedate'} || $today,
1277 $item->{datelastborrowed},
1278 $item->{datelastseen} || $today,
1280 $item->{'notforloan'},
1282 $item->{'itemlost'},
1283 $item->{'withdrawn'},
1284 $item->{'itemcallnumber'},
1285 $item->{'coded_location_qualifier'},
1286 $item->{'restricted'},
1287 $item->{'itemnotes'},
1288 $item->{'itemnotes_nonpublic'},
1289 $item->{'holdingbranch'},
1290 $item->{'location'},
1291 $item->{'permanent_location'},
1294 $item->{'renewals'},
1295 $item->{'reserves'},
1296 $item->{'items.cn_source'},
1297 $item->{'items.cn_sort'},
1300 $item->{'materials'},
1302 $item->{'enumchron'},
1303 $item->{'more_subfields_xml'},
1304 $item->{'copynumber'},
1305 $item->{'stocknumber'},
1306 $item->{'new_status'},
1310 if ( defined $sth->errstr ) {
1311 $error.="ERROR in _koha_new_item $query".$sth->errstr;
1314 $itemnumber = $dbh->{'mysql_insertid'};
1317 return ( $itemnumber, $error );
1320 =head2 MoveItemFromBiblio
1322 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1324 Moves an item from a biblio to another
1326 Returns undef if the move failed or the biblionumber of the destination record otherwise
1330 sub MoveItemFromBiblio {
1331 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1332 my $dbh = C4::Context->dbh;
1333 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1334 SELECT biblioitemnumber
1336 WHERE biblionumber = ?
1337 |, undef, $tobiblio );
1338 my $return = $dbh->do(q|
1340 SET biblioitemnumber = ?,
1342 WHERE itemnumber = ?
1343 AND biblionumber = ?
1344 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1346 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1347 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1348 # Checking if the item we want to move is in an order
1349 require C4::Acquisition;
1350 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1352 # Replacing the biblionumber within the order if necessary
1353 $order->{'biblionumber'} = $tobiblio;
1354 C4::Acquisition::ModOrder($order);
1357 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1358 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1361 SET biblionumber = ?
1362 WHERE itemnumber = ?
1363 |, undef, $tobiblio, $itemnumber );
1370 =head2 ItemSafeToDelete
1372 ItemSafeToDelete( $biblionumber, $itemnumber);
1374 Exported function (core API) for checking whether an item record is safe to delete.
1376 returns 1 if the item is safe to delete,
1378 "book_on_loan" if the item is checked out,
1380 "not_same_branch" if the item is blocked by independent branches,
1382 "book_reserved" if the there are holds aganst the item, or
1384 "linked_analytics" if the item has linked analytic records.
1388 sub ItemSafeToDelete {
1389 my ( $biblionumber, $itemnumber ) = @_;
1391 my $dbh = C4::Context->dbh;
1395 my $countanalytics = GetAnalyticsCount($itemnumber);
1397 my $item = Koha::Items->find($itemnumber) or return;
1399 if ($item->checkout) {
1400 $status = "book_on_loan";
1402 elsif ( defined C4::Context->userenv
1403 and !C4::Context->IsSuperLibrarian()
1404 and C4::Context->preference("IndependentBranches")
1405 and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1407 $status = "not_same_branch";
1410 # check it doesn't have a waiting reserve
1411 my $sth = $dbh->prepare(
1413 SELECT COUNT(*) FROM reserves
1414 WHERE (found = 'W' OR found = 'T')
1418 $sth->execute($itemnumber);
1419 my ($reserve) = $sth->fetchrow;
1421 $status = "book_reserved";
1423 elsif ( $countanalytics > 0 ) {
1424 $status = "linked_analytics";
1435 DelItemCheck( $biblionumber, $itemnumber);
1437 Exported function (core API) for deleting an item record in Koha if there no current issue.
1439 DelItemCheck wraps ItemSafeToDelete around DelItem.
1444 my ( $biblionumber, $itemnumber ) = @_;
1445 my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1447 if ( $status == 1 ) {
1448 my $item = Koha::Items->find($itemnumber);
1449 $item->move_to_deleted;
1455 sub _mod_item_dates { # date formatting for date fields in item hash
1457 return if !$item || ref($item) ne 'HASH';
1460 { $_ =~ /^onloan$|^date|date$|datetime$/ }
1462 # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1463 # NOTE: We do not (yet) have items fields ending with datetime
1464 # Fields with _on$ have been handled already
1466 foreach my $key ( @keys ) {
1467 next if !defined $item->{$key}; # skip undefs
1468 my $dt = eval { dt_from_string( $item->{$key} ) };
1469 # eval: dt_from_string will die on us if we pass illegal dates
1472 if( defined $dt && ref($dt) eq 'DateTime' ) {
1473 if( $key =~ /datetime/ ) {
1474 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1476 $newstr = DateTime::Format::MySQL->format_date($dt);
1479 $item->{$key} = $newstr; # might be undef to clear garbage
1483 =head2 _marc_from_item_hash
1485 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1487 Given an item hash representing a complete item record,
1488 create a C<MARC::Record> object containing an embedded
1489 tag representing that item.
1491 The third, optional parameter C<$unlinked_item_subfields> is
1492 an arrayref of subfields (not mapped to C<items> fields per the
1493 framework) to be added to the MARC representation
1498 sub _marc_from_item_hash {
1500 my $frameworkcode = shift;
1501 my $unlinked_item_subfields;
1503 $unlinked_item_subfields = shift;
1506 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1507 # Also, don't emit a subfield if the underlying field is blank.
1508 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1509 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1510 : () } keys %{ $item } };
1512 my $item_marc = MARC::Record->new();
1513 foreach my $item_field ( keys %{$mungeditem} ) {
1514 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1515 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1516 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1517 foreach my $value (@values){
1518 if ( my $field = $item_marc->field($tag) ) {
1519 $field->add_subfields( $subfield => $value );
1521 my $add_subfields = [];
1522 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1523 $add_subfields = $unlinked_item_subfields;
1525 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1533 =head2 _repack_item_errors
1535 Add an error message hash generated by C<CheckItemPreSave>
1536 to a list of errors.
1540 sub _repack_item_errors {
1541 my $item_sequence_num = shift;
1542 my $item_ref = shift;
1543 my $error_ref = shift;
1545 my @repacked_errors = ();
1547 foreach my $error_code (sort keys %{ $error_ref }) {
1548 my $repacked_error = {};
1549 $repacked_error->{'item_sequence'} = $item_sequence_num;
1550 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1551 $repacked_error->{'error_code'} = $error_code;
1552 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1553 push @repacked_errors, $repacked_error;
1556 return @repacked_errors;
1559 =head2 _get_unlinked_item_subfields
1561 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1565 sub _get_unlinked_item_subfields {
1566 my $original_item_marc = shift;
1567 my $frameworkcode = shift;
1569 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1571 # assume that this record has only one field, and that that
1572 # field contains only the item information
1574 my @fields = $original_item_marc->fields();
1575 if ($#fields > -1) {
1576 my $field = $fields[0];
1577 my $tag = $field->tag();
1578 foreach my $subfield ($field->subfields()) {
1579 if (defined $subfield->[1] and
1580 $subfield->[1] ne '' and
1581 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1582 push @$subfields, $subfield->[0] => $subfield->[1];
1589 =head2 _get_unlinked_subfields_xml
1591 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1595 sub _get_unlinked_subfields_xml {
1596 my $unlinked_item_subfields = shift;
1599 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1600 my $marc = MARC::Record->new();
1601 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1602 # used in the framework
1603 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1604 $marc->encoding("UTF-8");
1605 $xml = $marc->as_xml("USMARC");
1611 =head2 _parse_unlinked_item_subfields_from_xml
1613 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1617 sub _parse_unlinked_item_subfields_from_xml {
1619 require C4::Charset;
1620 return unless defined $xml and $xml ne "";
1621 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1622 my $unlinked_subfields = [];
1623 my @fields = $marc->fields();
1624 if ($#fields > -1) {
1625 foreach my $subfield ($fields[0]->subfields()) {
1626 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1629 return $unlinked_subfields;
1632 =head2 GetAnalyticsCount
1634 $count= &GetAnalyticsCount($itemnumber)
1636 counts Usage of itemnumber in Analytical bibliorecords.
1640 sub GetAnalyticsCount {
1641 my ($itemnumber) = @_;
1643 ### ZOOM search here
1645 $query= "hi=".$itemnumber;
1646 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1647 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1651 sub _SearchItems_build_where_fragment {
1654 my $dbh = C4::Context->dbh;
1657 if (exists($filter->{conjunction})) {
1658 my (@where_strs, @where_args);
1659 foreach my $f (@{ $filter->{filters} }) {
1660 my $fragment = _SearchItems_build_where_fragment($f);
1662 push @where_strs, $fragment->{str};
1663 push @where_args, @{ $fragment->{args} };
1668 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1671 args => \@where_args,
1675 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1676 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1677 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1678 my @operators = qw(= != > < >= <= like);
1679 my $field = $filter->{field} // q{};
1680 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1681 my $op = $filter->{operator};
1682 my $query = $filter->{query};
1684 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1685 $op = '='; # default operator
1689 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1691 my $marcsubfield = $2;
1692 my ($kohafield) = $dbh->selectrow_array(q|
1693 SELECT kohafield FROM marc_subfield_structure
1694 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1695 |, undef, $marcfield, $marcsubfield);
1698 $column = $kohafield;
1700 # MARC field is not linked to a DB field so we need to use
1701 # ExtractValue on marcxml from biblio_metadata or
1702 # items.more_subfields_xml, depending on the MARC field.
1705 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1706 if ($marcfield eq $itemfield) {
1707 $sqlfield = 'more_subfields_xml';
1708 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1710 $sqlfield = 'metadata'; # From biblio_metadata
1711 if ($marcfield < 10) {
1712 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1714 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1717 $column = "ExtractValue($sqlfield, '$xpath')";
1719 } elsif ($field eq 'issues') {
1720 # Consider NULL as 0 for issues count
1721 $column = 'COALESCE(issues,0)';
1726 if (ref $query eq 'ARRAY') {
1729 } elsif ($op eq '!=') {
1733 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1738 str => "$column $op ?",
1745 return $where_fragment;
1750 my ($items, $total) = SearchItems($filter, $params);
1752 Perform a search among items
1754 $filter is a reference to a hash which can be a filter, or a combination of filters.
1756 A filter has the following keys:
1760 =item * field: the name of a SQL column in table items
1762 =item * query: the value to search in this column
1764 =item * operator: comparison operator. Can be one of = != > < >= <= like
1768 A combination of filters hash the following keys:
1772 =item * conjunction: 'AND' or 'OR'
1774 =item * filters: array ref of filters
1778 $params is a reference to a hash that can contain the following parameters:
1782 =item * rows: Number of items to return. 0 returns everything (default: 0)
1784 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1787 =item * sortby: A SQL column name in items table to sort on
1789 =item * sortorder: 'ASC' or 'DESC'
1796 my ($filter, $params) = @_;
1800 return unless ref $filter eq 'HASH';
1801 return unless ref $params eq 'HASH';
1803 # Default parameters
1804 $params->{rows} ||= 0;
1805 $params->{page} ||= 1;
1806 $params->{sortby} ||= 'itemnumber';
1807 $params->{sortorder} ||= 'ASC';
1809 my ($where_str, @where_args);
1810 my $where_fragment = _SearchItems_build_where_fragment($filter);
1811 if ($where_fragment) {
1812 $where_str = $where_fragment->{str};
1813 @where_args = @{ $where_fragment->{args} };
1816 my $dbh = C4::Context->dbh;
1818 SELECT SQL_CALC_FOUND_ROWS items.*
1820 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1821 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1822 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1825 if (defined $where_str and $where_str ne '') {
1826 $query .= qq{ AND $where_str };
1829 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1830 push @where_args, C4::Context->preference('marcflavour');
1832 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1833 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1834 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1835 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1836 ? $params->{sortby} : 'itemnumber';
1837 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1838 $query .= qq{ ORDER BY $sortby $sortorder };
1840 my $rows = $params->{rows};
1843 my $offset = $rows * ($params->{page}-1);
1844 $query .= qq { LIMIT ?, ? };
1845 push @limit_args, $offset, $rows;
1848 my $sth = $dbh->prepare($query);
1849 my $rv = $sth->execute(@where_args, @limit_args);
1851 return unless ($rv);
1852 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1854 return ($sth->fetchall_arrayref({}), $total_rows);
1858 =head1 OTHER FUNCTIONS
1862 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1864 Find the given $subfield in the given $tag in the given
1865 MARC::Record $record. If the subfield is found, returns
1866 the (indicators, value) pair; otherwise, (undef, undef) is
1870 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1871 I suggest we export it from this module.
1876 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1879 if ( $tagfield < 10 ) {
1880 if ( $record->field($tagfield) ) {
1881 push @result, $record->field($tagfield)->data();
1886 foreach my $field ( $record->field($tagfield) ) {
1887 my @subfields = $field->subfields();
1888 foreach my $subfield (@subfields) {
1889 if ( @$subfield[0] eq $insubfield ) {
1890 push @result, @$subfield[1];
1891 $indicator = $field->indicator(1) . $field->indicator(2);
1896 return ( $indicator, @result );
1900 =head2 PrepareItemrecordDisplay
1902 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1904 Returns a hash with all the fields for Display a given item data in a template
1906 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1910 sub PrepareItemrecordDisplay {
1912 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1914 my $dbh = C4::Context->dbh;
1915 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1916 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1918 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1919 # a shared data structure. No plugin (including custom ones) should change
1920 # its contents. See also GetMarcStructure.
1921 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1923 # return nothing if we don't have found an existing framework.
1924 return q{} unless $tagslib;
1927 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1931 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1933 SELECT authorised_value,lib FROM authorised_values
1936 LEFT JOIN authorised_values_branches ON ( id = av_id )
1941 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1942 $query .= qq{ ORDER BY lib};
1943 my $authorised_values_sth = $dbh->prepare( $query );
1944 foreach my $tag ( sort keys %{$tagslib} ) {
1947 # loop through each subfield
1949 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1950 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1951 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1952 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1954 $subfield_data{tag} = $tag;
1955 $subfield_data{subfield} = $subfield;
1956 $subfield_data{countsubfield} = $cntsubf++;
1957 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1958 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1960 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1961 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1962 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1963 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1964 $subfield_data{hidden} = "display:none"
1965 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1966 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1967 my ( $x, $defaultvalue );
1969 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1971 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1972 if ( !defined $defaultvalue ) {
1973 $defaultvalue = q||;
1975 $defaultvalue =~ s/"/"/g;
1978 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1980 # search for itemcallnumber if applicable
1981 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1982 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1983 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1984 my $CNtag = substr( $itemcn_pref, 0, 3 );
1985 next unless my $field = $itemrecord->field($CNtag);
1986 my $CNsubfields = substr( $itemcn_pref, 3 );
1987 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1988 last if $defaultvalue;
1991 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1993 && $defaultvalues->{'callnumber'} ) {
1994 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1995 # if the item record exists, only use default value if the item has no callnumber
1996 $defaultvalue = $defaultvalues->{callnumber};
1997 } elsif ( !$itemrecord and $defaultvalues ) {
1998 # if the item record *doesn't* exists, always use the default value
1999 $defaultvalue = $defaultvalues->{callnumber};
2002 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2004 && $defaultvalues->{'branchcode'} ) {
2005 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2006 $defaultvalue = $defaultvalues->{branchcode};
2009 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2011 && $defaultvalues->{'location'} ) {
2013 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2014 # if the item record exists, only use default value if the item has no locationr
2015 $defaultvalue = $defaultvalues->{location};
2016 } elsif ( !$itemrecord and $defaultvalues ) {
2017 # if the item record *doesn't* exists, always use the default value
2018 $defaultvalue = $defaultvalues->{location};
2021 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2022 my @authorised_values;
2025 # builds list, depending on authorised value...
2027 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2028 if ( ( C4::Context->preference("IndependentBranches") )
2029 && !C4::Context->IsSuperLibrarian() ) {
2030 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2031 $sth->execute( C4::Context->userenv->{branch} );
2032 push @authorised_values, ""
2033 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2034 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2035 push @authorised_values, $branchcode;
2036 $authorised_lib{$branchcode} = $branchname;
2039 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2041 push @authorised_values, ""
2042 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2043 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2044 push @authorised_values, $branchcode;
2045 $authorised_lib{$branchcode} = $branchname;
2049 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2050 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2051 $defaultvalue = $defaultvalues->{branchcode};
2055 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2056 my $itemtypes = Koha::ItemTypes->search_with_localization;
2057 push @authorised_values, ""
2058 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2059 while ( my $itemtype = $itemtypes->next ) {
2060 push @authorised_values, $itemtype->itemtype;
2061 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2063 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2064 $defaultvalue = $defaultvalues->{'itemtype'};
2068 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2069 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2071 my $class_sources = GetClassSources();
2072 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2074 foreach my $class_source (sort keys %$class_sources) {
2075 next unless $class_sources->{$class_source}->{'used'} or
2076 ($class_source eq $default_source);
2077 push @authorised_values, $class_source;
2078 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2081 $defaultvalue = $default_source;
2083 #---- "true" authorised value
2085 $authorised_values_sth->execute(
2086 $tagslib->{$tag}->{$subfield}->{authorised_value},
2087 $branch_limit ? $branch_limit : ()
2089 push @authorised_values, ""
2090 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2091 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2092 push @authorised_values, $value;
2093 $authorised_lib{$value} = $lib;
2096 $subfield_data{marc_value} = {
2098 values => \@authorised_values,
2099 default => $defaultvalue // q{},
2100 labels => \%authorised_lib,
2102 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2104 require Koha::FrameworkPlugin;
2105 my $plugin = Koha::FrameworkPlugin->new({
2106 name => $tagslib->{$tag}->{$subfield}->{value_builder},
2109 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2110 $plugin->build( $pars );
2111 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2112 $defaultvalue = $field->subfield($subfield) || q{};
2114 if( !$plugin->errstr ) {
2115 #TODO Move html to template; see report 12176/13397
2116 my $tab= $plugin->noclick? '-1': '';
2117 my $class= $plugin->noclick? ' disabled': '';
2118 my $title= $plugin->noclick? 'No popup': 'Tag editor';
2119 $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
2121 warn $plugin->errstr;
2122 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
2125 elsif ( $tag eq '' ) { # it's an hidden field
2126 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2128 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2129 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2131 elsif ( length($defaultvalue) > 100
2132 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2133 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2134 or (C4::Context->preference("marcflavour") eq "MARC21" and
2135 500 <= $tag && $tag < 600 )
2137 # oversize field (textarea)
2138 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2140 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2142 push( @loop_data, \%subfield_data );
2147 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2148 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2151 'itemtagfield' => $itemtagfield,
2152 'itemtagsubfield' => $itemtagsubfield,
2153 'itemnumber' => $itemnumber,
2154 'iteminformation' => \@loop_data
2158 sub ToggleNewStatus {
2159 my ( $params ) = @_;
2160 my @rules = @{ $params->{rules} };
2161 my $report_only = $params->{report_only};
2163 my $dbh = C4::Context->dbh;
2165 my @item_columns = map { "items.$_" } Koha::Items->columns;
2166 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2168 for my $rule ( @rules ) {
2169 my $age = $rule->{age};
2170 my $conditions = $rule->{conditions};
2171 my $substitutions = $rule->{substitutions};
2172 foreach ( @$substitutions ) {
2173 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2180 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2183 for my $condition ( @$conditions ) {
2185 grep { $_ eq $condition->{field} } @item_columns
2186 or grep { $_ eq $condition->{field} } @biblioitem_columns
2188 if ( $condition->{value} =~ /\|/ ) {
2189 my @values = split /\|/, $condition->{value};
2190 $query .= qq| AND $condition->{field} IN (|
2191 . join( ',', ('?') x scalar @values )
2193 push @params, @values;
2195 $query .= qq| AND $condition->{field} = ?|;
2196 push @params, $condition->{value};
2200 if ( defined $age ) {
2201 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2204 my $sth = $dbh->prepare($query);
2205 $sth->execute( @params );
2206 while ( my $values = $sth->fetchrow_hashref ) {
2207 my $biblionumber = $values->{biblionumber};
2208 my $itemnumber = $values->{itemnumber};
2209 my $item = Koha::Items->find($itemnumber);
2210 for my $substitution ( @$substitutions ) {
2211 my $field = $substitution->{item_field};
2212 my $value = $substitution->{value};
2213 next unless $substitution->{field};
2214 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2215 $item->$field($value);
2216 push @{ $report->{$itemnumber} }, $substitution;
2218 $item->store unless $report_only;