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
46 PrepareItemrecordDisplay
59 use List::MoreUtils qw(any);
61 use DateTime::Format::MySQL;
62 use Data::Dumper; # used as part of logging item record changes, not just for
63 # debugging; so please don't remove this
65 use Koha::AuthorisedValues;
66 use Koha::DateUtils qw(dt_from_string);
69 use Koha::Biblioitems;
72 use Koha::SearchEngine;
73 use Koha::SearchEngine::Search;
78 C4::Items - item management functions
82 This module contains an API for manipulating item
83 records in Koha, and is used by cataloguing, circulation,
84 acquisitions, and serials management.
86 # FIXME This POD is not up-to-date
87 A Koha item record is stored in two places: the
88 items table and embedded in a MARC tag in the XML
89 version of the associated bib record in C<biblioitems.marcxml>.
90 This is done to allow the item information to be readily
91 indexed (e.g., by Zebra), but means that each item
92 modification transaction must keep the items table
93 and the MARC XML in sync at all times.
95 The items table will be considered authoritative. In other
96 words, if there is ever a discrepancy between the items
97 table and the MARC XML, the items table should be considered
100 =head1 HISTORICAL NOTE
102 Most of the functions in C<C4::Items> were originally in
103 the C<C4::Biblio> module.
105 =head1 CORE EXPORTED FUNCTIONS
107 The following functions are meant for use by users
114 CartToShelf($itemnumber);
116 Set the current shelving location of the item record
117 to its stored permanent shelving location. This is
118 primarily used to indicate when an item whose current
119 location is a special processing ('PROC') or shelving cart
120 ('CART') location is back in the stacks.
125 my ( $itemnumber ) = @_;
127 unless ( $itemnumber ) {
128 croak "FAILED CartToShelf() - no itemnumber supplied";
131 my $item = Koha::Items->find($itemnumber);
132 if ( $item->location eq 'CART' ) {
133 $item->location($item->permanent_location)->store;
137 =head2 AddItemFromMarc
139 my ($biblionumber, $biblioitemnumber, $itemnumber)
140 = AddItemFromMarc($source_item_marc, $biblionumber);
142 Given a MARC::Record object containing an embedded item
143 record and a biblionumber, create a new item record.
147 sub AddItemFromMarc {
148 my ( $source_item_marc, $biblionumber ) = @_;
149 my $dbh = C4::Context->dbh;
151 # parse item hash from MARC
152 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
153 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
155 my $localitemmarc = MARC::Record->new;
156 $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
158 my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
159 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
160 $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
161 $item_values->{biblionumber} = $biblionumber;
162 $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
163 my $item = Koha::Item->new( $item_values )->store;
164 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
167 =head2 AddItemBatchFromMarc
169 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
170 $biblionumber, $biblioitemnumber, $frameworkcode);
172 Efficiently create item records from a MARC biblio record with
173 embedded item fields. This routine is suitable for batch jobs.
175 This API assumes that the bib record has already been
176 saved to the C<biblio> and C<biblioitems> tables. It does
177 not expect that C<biblio_metadata.metadata> is populated, but it
178 will do so via a call to ModBibiloMarc.
180 The goal of this API is to have a similar effect to using AddBiblio
181 and AddItems in succession, but without inefficient repeated
182 parsing of the MARC XML bib record.
184 This function returns an arrayref of new itemsnumbers and an arrayref of item
185 errors encountered during the processing. Each entry in the errors
186 list is a hashref containing the following keys:
192 Sequence number of original item tag in the MARC record.
196 Item barcode, provide to assist in the construction of
197 useful error messages.
201 Code representing the error condition. Can be 'duplicate_barcode',
202 'invalid_homebranch', or 'invalid_holdingbranch'.
204 =item error_information
206 Additional information appropriate to the error condition.
212 sub AddItemBatchFromMarc {
213 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
215 my @itemnumbers = ();
217 my $dbh = C4::Context->dbh;
219 # We modify the record, so lets work on a clone so we don't change the
221 $record = $record->clone();
222 # loop through the item tags and start creating items
223 my @bad_item_fields = ();
224 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
225 my $item_sequence_num = 0;
226 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
227 $item_sequence_num++;
228 # we take the item field and stick it into a new
229 # MARC record -- this is required so far because (FIXME)
230 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
231 # and there is no TransformMarcFieldToKoha
232 my $temp_item_marc = MARC::Record->new();
233 $temp_item_marc->append_fields($item_field);
235 # add biblionumber and biblioitemnumber
236 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
237 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
238 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
239 $item->{'biblionumber'} = $biblionumber;
240 $item->{'biblioitemnumber'} = $biblioitemnumber;
241 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
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 my $item_object = Koha::Item->new($item)->store;
252 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
254 logaction("CATALOGUING", "ADD", $item->itemnumber, "item") if C4::Context->preference("CataloguingLog");
256 my $new_item_marc = _marc_from_item_hash($item->unblessed, $frameworkcode, $unlinked_item_subfields);
257 $item_field->replace_with($new_item_marc->field($itemtag));
260 # remove any MARC item fields for rejected items
261 foreach my $item_field (@bad_item_fields) {
262 $record->delete_field($item_field);
265 # update the MARC biblio
266 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
268 return (\@itemnumbers, \@errors);
271 sub ModItemFromMarc {
272 my $item_marc = shift;
273 my $biblionumber = shift;
274 my $itemnumber = shift;
276 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
277 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
279 my $localitemmarc = MARC::Record->new;
280 $localitemmarc->append_fields( $item_marc->field($itemtag) );
281 my $item_object = Koha::Items->find($itemnumber);
282 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
283 $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
284 $item->{itemnumber} = $itemnumber;
285 $item->{biblionumber} = $biblionumber;
286 $item_object = $item_object->set_or_blank($item);
287 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
288 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
291 return $item_object->unblessed;
294 =head2 ModItemTransfer
296 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
298 Marks an item as being transferred from one branch to another and records the trigger.
302 sub ModItemTransfer {
303 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
305 my $dbh = C4::Context->dbh;
306 my $item = Koha::Items->find( $itemnumber );
308 # Remove the 'shelving cart' location status if it is being used.
309 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
311 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
313 #new entry in branchtransfers....
314 my $sth = $dbh->prepare(
315 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
316 VALUES (?, ?, NOW(), ?, ?)");
317 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
319 # FIXME we are fetching the item twice in the 2 next statements!
320 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
321 ModDateLastSeen($itemnumber);
325 =head2 ModDateLastSeen
327 ModDateLastSeen( $itemnumber, $leave_item_lost );
329 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
330 C<$itemnumber> is the item number
331 C<$leave_item_lost> determines if a lost item will be found or remain lost
335 sub ModDateLastSeen {
336 my ( $itemnumber, $leave_item_lost ) = @_;
338 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
340 my $item = Koha::Items->find($itemnumber);
341 $item->datelastseen($today);
342 $item->itemlost(0) unless $leave_item_lost;
343 $item->store({ log_action => 0 });
346 =head2 CheckItemPreSave
348 my $item_ref = TransformMarcToKoha($marc, 'items');
350 my %errors = CheckItemPreSave($item_ref);
351 if (exists $errors{'duplicate_barcode'}) {
352 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
353 } elsif (exists $errors{'invalid_homebranch'}) {
354 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
355 } elsif (exists $errors{'invalid_holdingbranch'}) {
356 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
361 Given a hashref containing item fields, determine if it can be
362 inserted or updated in the database. Specifically, checks for
363 database integrity issues, and returns a hash containing any
364 of the following keys, if applicable.
368 =item duplicate_barcode
370 Barcode, if it duplicates one already found in the database.
372 =item invalid_homebranch
374 Home branch, if not defined in branches table.
376 =item invalid_holdingbranch
378 Holding branch, if not defined in branches table.
382 This function does NOT implement any policy-related checks,
383 e.g., whether current operator is allowed to save an
384 item that has a given branch code.
388 sub CheckItemPreSave {
389 my $item_ref = shift;
393 # check for duplicate barcode
394 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
395 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
396 if ($existing_item) {
397 if (!exists $item_ref->{'itemnumber'} # new item
398 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
399 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
404 # check for valid home branch
405 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
406 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
407 unless (defined $home_library) {
408 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
412 # check for valid holding branch
413 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
414 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
415 unless (defined $holding_library) {
416 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
424 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
426 The following functions provide various ways of
427 getting an item record, a set of item records, or
428 lists of authorized values for certain item fields.
432 =head2 GetItemsForInventory
434 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
435 minlocation => $minlocation,
436 maxlocation => $maxlocation,
437 location => $location,
438 itemtype => $itemtype,
439 ignoreissued => $ignoreissued,
440 datelastseen => $datelastseen,
441 branchcode => $branchcode,
445 statushash => $statushash,
448 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
450 The sub returns a reference to a list of hashes, each containing
451 itemnumber, author, title, barcode, item callnumber, and date last
452 seen. It is ordered by callnumber then title.
454 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
455 the datelastseen can be used to specify that you want to see items not seen since a past date only.
456 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
457 $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.
459 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
463 sub GetItemsForInventory {
464 my ( $parameters ) = @_;
465 my $minlocation = $parameters->{'minlocation'} // '';
466 my $maxlocation = $parameters->{'maxlocation'} // '';
467 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
468 my $location = $parameters->{'location'} // '';
469 my $itemtype = $parameters->{'itemtype'} // '';
470 my $ignoreissued = $parameters->{'ignoreissued'} // '';
471 my $datelastseen = $parameters->{'datelastseen'} // '';
472 my $branchcode = $parameters->{'branchcode'} // '';
473 my $branch = $parameters->{'branch'} // '';
474 my $offset = $parameters->{'offset'} // '';
475 my $size = $parameters->{'size'} // '';
476 my $statushash = $parameters->{'statushash'} // '';
477 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
479 my $dbh = C4::Context->dbh;
480 my ( @bind_params, @where_strings );
482 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
483 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
485 my $select_columns = q{
486 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
488 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
491 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
492 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
495 for my $authvfield (keys %$statushash){
496 if ( scalar @{$statushash->{$authvfield}} > 0 ){
497 my $joinedvals = join ',', @{$statushash->{$authvfield}};
498 push @where_strings, "$authvfield in (" . $joinedvals . ")";
504 push @where_strings, 'items.cn_sort >= ?';
505 push @bind_params, $min_cnsort;
509 push @where_strings, 'items.cn_sort <= ?';
510 push @bind_params, $max_cnsort;
514 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
515 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
516 push @bind_params, $datelastseen;
520 push @where_strings, 'items.location = ?';
521 push @bind_params, $location;
525 if($branch eq "homebranch"){
526 push @where_strings, 'items.homebranch = ?';
528 push @where_strings, 'items.holdingbranch = ?';
530 push @bind_params, $branchcode;
534 push @where_strings, 'biblioitems.itemtype = ?';
535 push @bind_params, $itemtype;
538 if ( $ignoreissued) {
539 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
540 push @where_strings, 'issues.date_due IS NULL';
543 if ( $ignore_waiting_holds ) {
544 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
545 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
548 if ( @where_strings ) {
550 $query .= join ' AND ', @where_strings;
552 my $count_query = $select_count . $query;
553 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
554 $query .= " LIMIT $offset, $size" if ($offset and $size);
555 $query = $select_columns . $query;
556 my $sth = $dbh->prepare($query);
557 $sth->execute( @bind_params );
560 my $tmpresults = $sth->fetchall_arrayref({});
561 $sth = $dbh->prepare( $count_query );
562 $sth->execute( @bind_params );
563 my ($iTotalRecords) = $sth->fetchrow_array();
565 my @avs = Koha::AuthorisedValues->search(
566 { 'marc_subfield_structures.kohafield' => { '>' => '' },
567 'me.authorised_value' => { '>' => '' },
569 { join => { category => 'marc_subfield_structures' },
570 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
571 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
572 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
576 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
578 foreach my $row (@$tmpresults) {
581 foreach (keys %$row) {
584 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
587 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
593 return (\@results, $iTotalRecords);
598 @results = GetItemsInfo($biblionumber);
600 Returns information about items with the given biblionumber.
602 C<GetItemsInfo> returns a list of references-to-hash. Each element
603 contains a number of keys. Most of them are attributes from the
604 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
605 Koha database. Other keys include:
609 =item C<$data-E<gt>{branchname}>
611 The name (not the code) of the branch to which the book belongs.
613 =item C<$data-E<gt>{datelastseen}>
615 This is simply C<items.datelastseen>, except that while the date is
616 stored in YYYY-MM-DD format in the database, here it is converted to
617 DD/MM/YYYY format. A NULL date is returned as C<//>.
619 =item C<$data-E<gt>{datedue}>
621 =item C<$data-E<gt>{class}>
623 This is the concatenation of C<biblioitems.classification>, the book's
624 Dewey code, and C<biblioitems.subclass>.
626 =item C<$data-E<gt>{ocount}>
628 I think this is the number of copies of the book available.
630 =item C<$data-E<gt>{order}>
632 If this is set, it is set to C<One Order>.
639 my ( $biblionumber ) = @_;
640 my $dbh = C4::Context->dbh;
641 require C4::Languages;
642 my $language = C4::Languages::getlanguage();
648 biblioitems.itemtype,
651 biblioitems.publicationyear,
652 biblioitems.publishercode,
653 biblioitems.volumedate,
654 biblioitems.volumedesc,
657 items.notforloan as itemnotforloan,
658 issues.borrowernumber,
659 issues.date_due as datedue,
660 issues.onsite_checkout,
661 borrowers.cardnumber,
664 borrowers.branchcode as bcode,
666 serial.publisheddate,
667 itemtypes.description,
668 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
669 itemtypes.notforloan as notforloan_per_itemtype,
673 holding.opac_info as holding_branch_opac_info,
674 home.opac_info as home_branch_opac_info,
675 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
677 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
678 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
679 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
680 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
681 LEFT JOIN issues USING (itemnumber)
682 LEFT JOIN borrowers USING (borrowernumber)
683 LEFT JOIN serialitems USING (itemnumber)
684 LEFT JOIN serial USING (serialid)
685 LEFT JOIN itemtypes ON itemtypes.itemtype = "
686 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
688 LEFT JOIN tmp_holdsqueue USING (itemnumber)
689 LEFT JOIN localization ON itemtypes.itemtype = localization.code
690 AND localization.entity = 'itemtypes'
691 AND localization.lang = ?
694 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
695 my $sth = $dbh->prepare($query);
696 $sth->execute($language, $biblionumber);
701 my $userenv = C4::Context->userenv;
702 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
703 while ( my $data = $sth->fetchrow_hashref ) {
704 if ( $data->{borrowernumber} && $want_not_same_branch) {
705 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
708 $serial ||= $data->{'serial'};
711 # get notforloan complete status if applicable
712 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
713 $data->{notforloanvalue} = $descriptions->{lib} // '';
714 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
716 # get restricted status and description if applicable
717 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
718 $data->{restrictedvalue} = $descriptions->{lib} // '';
719 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
721 # my stack procedures
722 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
723 $data->{stack} = $descriptions->{lib} // '';
725 # Find the last 3 people who borrowed this item.
726 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
728 AND old_issues.borrowernumber = borrowers.borrowernumber
729 ORDER BY returndate DESC
731 $sth2->execute($data->{'itemnumber'});
733 while (my $data2 = $sth2->fetchrow_hashref()) {
734 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
735 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
736 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
740 $results[$i] = $data;
745 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
749 =head2 GetItemsLocationInfo
751 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
753 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
755 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
759 =item C<$data-E<gt>{homebranch}>
761 Branch Name of the item's homebranch
763 =item C<$data-E<gt>{holdingbranch}>
765 Branch Name of the item's holdingbranch
767 =item C<$data-E<gt>{location}>
769 Item's shelving location code
771 =item C<$data-E<gt>{location_intranet}>
773 The intranet description for the Shelving Location as set in authorised_values 'LOC'
775 =item C<$data-E<gt>{location_opac}>
777 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
780 =item C<$data-E<gt>{itemcallnumber}>
782 Item's itemcallnumber
784 =item C<$data-E<gt>{cn_sort}>
786 Item's call number normalized for sorting
792 sub GetItemsLocationInfo {
793 my $biblionumber = shift;
796 my $dbh = C4::Context->dbh;
797 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
798 location, itemcallnumber, cn_sort
799 FROM items, branches as a, branches as b
800 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
802 ORDER BY cn_sort ASC";
803 my $sth = $dbh->prepare($query);
804 $sth->execute($biblionumber);
806 while ( my $data = $sth->fetchrow_hashref ) {
807 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
808 $av = $av->count ? $av->next : undef;
809 $data->{location_intranet} = $av ? $av->lib : '';
810 $data->{location_opac} = $av ? $av->opac_description : '';
811 push @results, $data;
816 =head2 GetHostItemsInfo
818 $hostiteminfo = GetHostItemsInfo($hostfield);
819 Returns the iteminfo for items linked to records via a host field
823 sub GetHostItemsInfo {
827 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
828 return @returnitemsInfo;
832 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
833 C4::Context->preference('marcflavour') eq 'NORMARC') {
834 @fields = $record->field('773');
835 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
836 @fields = $record->field('461');
839 foreach my $hostfield ( @fields ) {
840 my $hostbiblionumber = $hostfield->subfield("0");
841 my $linkeditemnumber = $hostfield->subfield("9");
842 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
843 foreach my $hostitemInfo (@hostitemInfos) {
844 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
845 push @returnitemsInfo, $hostitemInfo;
850 return @returnitemsInfo;
853 =head2 get_hostitemnumbers_of
855 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
857 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
859 Return a reference on a hash where key is a biblionumber and values are
860 references on array of itemnumbers.
865 sub get_hostitemnumbers_of {
866 my ($biblionumber) = @_;
868 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
872 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
873 return unless $marcrecord;
875 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
877 my $marcflavor = C4::Context->preference('marcflavour');
878 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
883 elsif ( $marcflavor eq 'UNIMARC' ) {
889 foreach my $hostfield ( $marcrecord->field($tag) ) {
890 my $hostbiblionumber = $hostfield->subfield($biblio_s);
891 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
892 my $linkeditemnumber = $hostfield->subfield($item_s);
893 if ( ! $linkeditemnumber ) {
894 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
897 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
898 push @returnhostitemnumbers, $linkeditemnumber
902 return @returnhostitemnumbers;
905 =head2 GetHiddenItemnumbers
907 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
909 Given a list of items it checks which should be hidden from the OPAC given
910 the current configuration. Returns a list of itemnumbers corresponding to
911 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
916 sub GetHiddenItemnumbers {
918 my $items = $params->{items};
919 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
920 foreach my $except (split(/\|/, $exceptions)){
921 if ($params->{'borcat'} eq $except){
922 return; # we don't hide anything for this borrower category
928 my $yaml = C4::Context->preference('OpacHiddenItems');
929 return () if (! $yaml =~ /\S/ );
930 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
933 $hidingrules = YAML::Load($yaml);
936 warn "Unable to parse OpacHiddenItems syspref : $@";
939 my $dbh = C4::Context->dbh;
942 foreach my $item (@$items) {
945 foreach my $field (keys %$hidingrules) {
947 if (exists $item->{$field}) {
948 $val = $item->{$field};
951 my $query = "SELECT $field from items where itemnumber = ?";
952 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
954 $val = '' unless defined $val;
956 # If the results matches the values in the yaml file
957 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
959 # We add the itemnumber to the list
960 push @resultitems, $item->{'itemnumber'};
962 # If at least one rule matched for an item, no need to test the others
970 =head1 LIMITED USE FUNCTIONS
972 The following functions, while part of the public API,
973 are not exported. This is generally because they are
974 meant to be used by only one script for a specific
975 purpose, and should not be used in any other context
976 without careful thought.
982 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
984 Returns MARC::Record of the item passed in parameter.
985 This function is meant for use only in C<cataloguing/additem.pl>,
986 where it is needed to support that script's MARC-like
992 my ( $biblionumber, $itemnumber ) = @_;
994 # GetMarcItem has been revised so that it does the following:
995 # 1. Gets the item information from the items table.
996 # 2. Converts it to a MARC field for storage in the bib record.
998 # The previous behavior was:
999 # 1. Get the bib record.
1000 # 2. Return the MARC tag corresponding to the item record.
1002 # The difference is that one treats the items row as authoritative,
1003 # while the other treats the MARC representation as authoritative
1004 # under certain circumstances.
1006 my $item = Koha::Items->find($itemnumber) or return;
1008 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1009 # Also, don't emit a subfield if the underlying field is blank.
1011 return Item2Marc($item->unblessed, $biblionumber);
1015 my ($itemrecord,$biblionumber)=@_;
1018 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1019 } keys %{ $itemrecord }
1021 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1022 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1023 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1024 "items.itemnumber", $framework,
1027 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1028 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1029 foreach my $field ($itemmarc->field($itemtag)){
1030 $field->add_subfields(@$unlinked_item_subfields);
1036 =head1 PRIVATE FUNCTIONS AND VARIABLES
1038 The following functions are not meant to be called
1039 directly, but are documented in order to explain
1040 the inner workings of C<C4::Items>.
1044 =head2 MoveItemFromBiblio
1046 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1048 Moves an item from a biblio to another
1050 Returns undef if the move failed or the biblionumber of the destination record otherwise
1054 sub MoveItemFromBiblio {
1055 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1056 my $dbh = C4::Context->dbh;
1057 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1058 SELECT biblioitemnumber
1060 WHERE biblionumber = ?
1061 |, undef, $tobiblio );
1062 my $return = $dbh->do(q|
1064 SET biblioitemnumber = ?,
1066 WHERE itemnumber = ?
1067 AND biblionumber = ?
1068 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1070 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1071 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1072 # Checking if the item we want to move is in an order
1073 require C4::Acquisition;
1074 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1076 # Replacing the biblionumber within the order if necessary
1077 $order->{'biblionumber'} = $tobiblio;
1078 C4::Acquisition::ModOrder($order);
1081 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1082 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1085 SET biblionumber = ?
1086 WHERE itemnumber = ?
1087 |, undef, $tobiblio, $itemnumber );
1094 =head2 _marc_from_item_hash
1096 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1098 Given an item hash representing a complete item record,
1099 create a C<MARC::Record> object containing an embedded
1100 tag representing that item.
1102 The third, optional parameter C<$unlinked_item_subfields> is
1103 an arrayref of subfields (not mapped to C<items> fields per the
1104 framework) to be added to the MARC representation
1109 sub _marc_from_item_hash {
1111 my $frameworkcode = shift;
1112 my $unlinked_item_subfields;
1114 $unlinked_item_subfields = shift;
1117 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1118 # Also, don't emit a subfield if the underlying field is blank.
1119 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1120 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1121 : () } keys %{ $item } };
1123 my $item_marc = MARC::Record->new();
1124 foreach my $item_field ( keys %{$mungeditem} ) {
1125 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1126 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1127 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1128 foreach my $value (@values){
1129 if ( my $field = $item_marc->field($tag) ) {
1130 $field->add_subfields( $subfield => $value );
1132 my $add_subfields = [];
1133 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1134 $add_subfields = $unlinked_item_subfields;
1136 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1144 =head2 _repack_item_errors
1146 Add an error message hash generated by C<CheckItemPreSave>
1147 to a list of errors.
1151 sub _repack_item_errors {
1152 my $item_sequence_num = shift;
1153 my $item_ref = shift;
1154 my $error_ref = shift;
1156 my @repacked_errors = ();
1158 foreach my $error_code (sort keys %{ $error_ref }) {
1159 my $repacked_error = {};
1160 $repacked_error->{'item_sequence'} = $item_sequence_num;
1161 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1162 $repacked_error->{'error_code'} = $error_code;
1163 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1164 push @repacked_errors, $repacked_error;
1167 return @repacked_errors;
1170 =head2 _get_unlinked_item_subfields
1172 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1176 sub _get_unlinked_item_subfields {
1177 my $original_item_marc = shift;
1178 my $frameworkcode = shift;
1180 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1182 # assume that this record has only one field, and that that
1183 # field contains only the item information
1185 my @fields = $original_item_marc->fields();
1186 if ($#fields > -1) {
1187 my $field = $fields[0];
1188 my $tag = $field->tag();
1189 foreach my $subfield ($field->subfields()) {
1190 if (defined $subfield->[1] and
1191 $subfield->[1] ne '' and
1192 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1193 push @$subfields, $subfield->[0] => $subfield->[1];
1200 =head2 _get_unlinked_subfields_xml
1202 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1206 sub _get_unlinked_subfields_xml {
1207 my $unlinked_item_subfields = shift;
1210 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1211 my $marc = MARC::Record->new();
1212 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1213 # used in the framework
1214 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1215 $marc->encoding("UTF-8");
1216 $xml = $marc->as_xml("USMARC");
1222 =head2 _parse_unlinked_item_subfields_from_xml
1224 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1228 sub _parse_unlinked_item_subfields_from_xml {
1230 require C4::Charset;
1231 return unless defined $xml and $xml ne "";
1232 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1233 my $unlinked_subfields = [];
1234 my @fields = $marc->fields();
1235 if ($#fields > -1) {
1236 foreach my $subfield ($fields[0]->subfields()) {
1237 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1240 return $unlinked_subfields;
1243 =head2 GetAnalyticsCount
1245 $count= &GetAnalyticsCount($itemnumber)
1247 counts Usage of itemnumber in Analytical bibliorecords.
1251 sub GetAnalyticsCount {
1252 my ($itemnumber) = @_;
1254 ### ZOOM search here
1256 $query= "hi=".$itemnumber;
1257 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1258 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1262 sub _SearchItems_build_where_fragment {
1265 my $dbh = C4::Context->dbh;
1268 if (exists($filter->{conjunction})) {
1269 my (@where_strs, @where_args);
1270 foreach my $f (@{ $filter->{filters} }) {
1271 my $fragment = _SearchItems_build_where_fragment($f);
1273 push @where_strs, $fragment->{str};
1274 push @where_args, @{ $fragment->{args} };
1279 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1282 args => \@where_args,
1286 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1287 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1288 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1289 my @operators = qw(= != > < >= <= like);
1290 my $field = $filter->{field} // q{};
1291 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1292 my $op = $filter->{operator};
1293 my $query = $filter->{query};
1295 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1296 $op = '='; # default operator
1300 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1302 my $marcsubfield = $2;
1303 my ($kohafield) = $dbh->selectrow_array(q|
1304 SELECT kohafield FROM marc_subfield_structure
1305 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1306 |, undef, $marcfield, $marcsubfield);
1309 $column = $kohafield;
1311 # MARC field is not linked to a DB field so we need to use
1312 # ExtractValue on marcxml from biblio_metadata or
1313 # items.more_subfields_xml, depending on the MARC field.
1316 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1317 if ($marcfield eq $itemfield) {
1318 $sqlfield = 'more_subfields_xml';
1319 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1321 $sqlfield = 'metadata'; # From biblio_metadata
1322 if ($marcfield < 10) {
1323 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1325 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1328 $column = "ExtractValue($sqlfield, '$xpath')";
1330 } elsif ($field eq 'issues') {
1331 # Consider NULL as 0 for issues count
1332 $column = 'COALESCE(issues,0)';
1337 if (ref $query eq 'ARRAY') {
1340 } elsif ($op eq '!=') {
1344 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1349 str => "$column $op ?",
1356 return $where_fragment;
1361 my ($items, $total) = SearchItems($filter, $params);
1363 Perform a search among items
1365 $filter is a reference to a hash which can be a filter, or a combination of filters.
1367 A filter has the following keys:
1371 =item * field: the name of a SQL column in table items
1373 =item * query: the value to search in this column
1375 =item * operator: comparison operator. Can be one of = != > < >= <= like
1379 A combination of filters hash the following keys:
1383 =item * conjunction: 'AND' or 'OR'
1385 =item * filters: array ref of filters
1389 $params is a reference to a hash that can contain the following parameters:
1393 =item * rows: Number of items to return. 0 returns everything (default: 0)
1395 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1398 =item * sortby: A SQL column name in items table to sort on
1400 =item * sortorder: 'ASC' or 'DESC'
1407 my ($filter, $params) = @_;
1411 return unless ref $filter eq 'HASH';
1412 return unless ref $params eq 'HASH';
1414 # Default parameters
1415 $params->{rows} ||= 0;
1416 $params->{page} ||= 1;
1417 $params->{sortby} ||= 'itemnumber';
1418 $params->{sortorder} ||= 'ASC';
1420 my ($where_str, @where_args);
1421 my $where_fragment = _SearchItems_build_where_fragment($filter);
1422 if ($where_fragment) {
1423 $where_str = $where_fragment->{str};
1424 @where_args = @{ $where_fragment->{args} };
1427 my $dbh = C4::Context->dbh;
1429 SELECT SQL_CALC_FOUND_ROWS items.*
1431 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1432 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1433 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1436 if (defined $where_str and $where_str ne '') {
1437 $query .= qq{ AND $where_str };
1440 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1441 push @where_args, C4::Context->preference('marcflavour');
1443 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1444 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1445 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1446 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1447 ? $params->{sortby} : 'itemnumber';
1448 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1449 $query .= qq{ ORDER BY $sortby $sortorder };
1451 my $rows = $params->{rows};
1454 my $offset = $rows * ($params->{page}-1);
1455 $query .= qq { LIMIT ?, ? };
1456 push @limit_args, $offset, $rows;
1459 my $sth = $dbh->prepare($query);
1460 my $rv = $sth->execute(@where_args, @limit_args);
1462 return unless ($rv);
1463 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1465 return ($sth->fetchall_arrayref({}), $total_rows);
1469 =head1 OTHER FUNCTIONS
1473 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1475 Find the given $subfield in the given $tag in the given
1476 MARC::Record $record. If the subfield is found, returns
1477 the (indicators, value) pair; otherwise, (undef, undef) is
1481 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1482 I suggest we export it from this module.
1487 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1490 if ( $tagfield < 10 ) {
1491 if ( $record->field($tagfield) ) {
1492 push @result, $record->field($tagfield)->data();
1497 foreach my $field ( $record->field($tagfield) ) {
1498 my @subfields = $field->subfields();
1499 foreach my $subfield (@subfields) {
1500 if ( @$subfield[0] eq $insubfield ) {
1501 push @result, @$subfield[1];
1502 $indicator = $field->indicator(1) . $field->indicator(2);
1507 return ( $indicator, @result );
1511 =head2 PrepareItemrecordDisplay
1513 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1515 Returns a hash with all the fields for Display a given item data in a template
1517 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1521 sub PrepareItemrecordDisplay {
1523 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1525 my $dbh = C4::Context->dbh;
1526 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1527 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1529 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1530 # a shared data structure. No plugin (including custom ones) should change
1531 # its contents. See also GetMarcStructure.
1532 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1534 # return nothing if we don't have found an existing framework.
1535 return q{} unless $tagslib;
1538 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1542 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1544 SELECT authorised_value,lib FROM authorised_values
1547 LEFT JOIN authorised_values_branches ON ( id = av_id )
1552 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1553 $query .= qq{ ORDER BY lib};
1554 my $authorised_values_sth = $dbh->prepare( $query );
1555 foreach my $tag ( sort keys %{$tagslib} ) {
1558 # loop through each subfield
1560 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1561 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1562 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1563 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1565 $subfield_data{tag} = $tag;
1566 $subfield_data{subfield} = $subfield;
1567 $subfield_data{countsubfield} = $cntsubf++;
1568 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1569 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1571 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1572 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1573 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1574 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1575 $subfield_data{hidden} = "display:none"
1576 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1577 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1578 my ( $x, $defaultvalue );
1580 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1582 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1583 if ( !defined $defaultvalue ) {
1584 $defaultvalue = q||;
1586 $defaultvalue =~ s/"/"/g;
1589 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1591 # search for itemcallnumber if applicable
1592 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1593 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1594 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1595 my $CNtag = substr( $itemcn_pref, 0, 3 );
1596 next unless my $field = $itemrecord->field($CNtag);
1597 my $CNsubfields = substr( $itemcn_pref, 3 );
1598 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1599 last if $defaultvalue;
1602 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1604 && $defaultvalues->{'callnumber'} ) {
1605 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1606 # if the item record exists, only use default value if the item has no callnumber
1607 $defaultvalue = $defaultvalues->{callnumber};
1608 } elsif ( !$itemrecord and $defaultvalues ) {
1609 # if the item record *doesn't* exists, always use the default value
1610 $defaultvalue = $defaultvalues->{callnumber};
1613 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1615 && $defaultvalues->{'branchcode'} ) {
1616 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1617 $defaultvalue = $defaultvalues->{branchcode};
1620 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1622 && $defaultvalues->{'location'} ) {
1624 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1625 # if the item record exists, only use default value if the item has no locationr
1626 $defaultvalue = $defaultvalues->{location};
1627 } elsif ( !$itemrecord and $defaultvalues ) {
1628 # if the item record *doesn't* exists, always use the default value
1629 $defaultvalue = $defaultvalues->{location};
1632 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1633 my @authorised_values;
1636 # builds list, depending on authorised value...
1638 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1639 if ( ( C4::Context->preference("IndependentBranches") )
1640 && !C4::Context->IsSuperLibrarian() ) {
1641 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1642 $sth->execute( C4::Context->userenv->{branch} );
1643 push @authorised_values, ""
1644 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1645 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1646 push @authorised_values, $branchcode;
1647 $authorised_lib{$branchcode} = $branchname;
1650 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1652 push @authorised_values, ""
1653 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1654 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1655 push @authorised_values, $branchcode;
1656 $authorised_lib{$branchcode} = $branchname;
1660 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1661 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1662 $defaultvalue = $defaultvalues->{branchcode};
1666 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1667 my $itemtypes = Koha::ItemTypes->search_with_localization;
1668 push @authorised_values, ""
1669 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1670 while ( my $itemtype = $itemtypes->next ) {
1671 push @authorised_values, $itemtype->itemtype;
1672 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1674 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1675 $defaultvalue = $defaultvalues->{'itemtype'};
1679 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1680 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1682 my $class_sources = GetClassSources();
1683 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1685 foreach my $class_source (sort keys %$class_sources) {
1686 next unless $class_sources->{$class_source}->{'used'} or
1687 ($class_source eq $default_source);
1688 push @authorised_values, $class_source;
1689 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1692 $defaultvalue = $default_source;
1694 #---- "true" authorised value
1696 $authorised_values_sth->execute(
1697 $tagslib->{$tag}->{$subfield}->{authorised_value},
1698 $branch_limit ? $branch_limit : ()
1700 push @authorised_values, ""
1701 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1702 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1703 push @authorised_values, $value;
1704 $authorised_lib{$value} = $lib;
1707 $subfield_data{marc_value} = {
1709 values => \@authorised_values,
1710 default => $defaultvalue // q{},
1711 labels => \%authorised_lib,
1713 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1715 require Koha::FrameworkPlugin;
1716 my $plugin = Koha::FrameworkPlugin->new({
1717 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1720 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1721 $plugin->build( $pars );
1722 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1723 $defaultvalue = $field->subfield($subfield) || q{};
1725 if( !$plugin->errstr ) {
1726 #TODO Move html to template; see report 12176/13397
1727 my $tab= $plugin->noclick? '-1': '';
1728 my $class= $plugin->noclick? ' disabled': '';
1729 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1730 $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;
1732 warn $plugin->errstr;
1733 $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
1736 elsif ( $tag eq '' ) { # it's an hidden field
1737 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1739 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1740 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1742 elsif ( length($defaultvalue) > 100
1743 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1744 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1745 or (C4::Context->preference("marcflavour") eq "MARC21" and
1746 500 <= $tag && $tag < 600 )
1748 # oversize field (textarea)
1749 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1751 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1753 push( @loop_data, \%subfield_data );
1758 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1759 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1762 'itemtagfield' => $itemtagfield,
1763 'itemtagsubfield' => $itemtagsubfield,
1764 'itemnumber' => $itemnumber,
1765 'iteminformation' => \@loop_data
1769 sub ToggleNewStatus {
1770 my ( $params ) = @_;
1771 my @rules = @{ $params->{rules} };
1772 my $report_only = $params->{report_only};
1774 my $dbh = C4::Context->dbh;
1776 my @item_columns = map { "items.$_" } Koha::Items->columns;
1777 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1779 for my $rule ( @rules ) {
1780 my $age = $rule->{age};
1781 my $conditions = $rule->{conditions};
1782 my $substitutions = $rule->{substitutions};
1783 foreach ( @$substitutions ) {
1784 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1791 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1794 for my $condition ( @$conditions ) {
1796 grep { $_ eq $condition->{field} } @item_columns
1797 or grep { $_ eq $condition->{field} } @biblioitem_columns
1799 if ( $condition->{value} =~ /\|/ ) {
1800 my @values = split /\|/, $condition->{value};
1801 $query .= qq| AND $condition->{field} IN (|
1802 . join( ',', ('?') x scalar @values )
1804 push @params, @values;
1806 $query .= qq| AND $condition->{field} = ?|;
1807 push @params, $condition->{value};
1811 if ( defined $age ) {
1812 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1815 my $sth = $dbh->prepare($query);
1816 $sth->execute( @params );
1817 while ( my $values = $sth->fetchrow_hashref ) {
1818 my $biblionumber = $values->{biblionumber};
1819 my $itemnumber = $values->{itemnumber};
1820 my $item = Koha::Items->find($itemnumber);
1821 for my $substitution ( @$substitutions ) {
1822 my $field = $substitution->{item_field};
1823 my $value = $substitution->{value};
1824 next unless $substitution->{field};
1825 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1826 $item->$field($value);
1827 push @{ $report->{$itemnumber} }, $substitution;
1829 $item->store unless $report_only;