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_object->set($item);
285 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
286 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
289 return $item_object->unblessed;
292 =head2 ModItemTransfer
294 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
296 Marks an item as being transferred from one branch to another and records the trigger.
300 sub ModItemTransfer {
301 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
303 my $dbh = C4::Context->dbh;
304 my $item = Koha::Items->find( $itemnumber );
306 # Remove the 'shelving cart' location status if it is being used.
307 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
309 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
311 #new entry in branchtransfers....
312 my $sth = $dbh->prepare(
313 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
314 VALUES (?, ?, NOW(), ?, ?)");
315 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
317 # FIXME we are fetching the item twice in the 2 next statements!
318 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
319 ModDateLastSeen($itemnumber);
323 =head2 ModDateLastSeen
325 ModDateLastSeen( $itemnumber, $leave_item_lost );
327 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
328 C<$itemnumber> is the item number
329 C<$leave_item_lost> determines if a lost item will be found or remain lost
333 sub ModDateLastSeen {
334 my ( $itemnumber, $leave_item_lost ) = @_;
336 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
338 my $item = Koha::Items->find($itemnumber);
339 $item->datelastseen($today);
340 $item->itemlost(0) unless $leave_item_lost;
341 $item->store({ log_action => 0 });
344 =head2 CheckItemPreSave
346 my $item_ref = TransformMarcToKoha($marc, 'items');
348 my %errors = CheckItemPreSave($item_ref);
349 if (exists $errors{'duplicate_barcode'}) {
350 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
351 } elsif (exists $errors{'invalid_homebranch'}) {
352 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
353 } elsif (exists $errors{'invalid_holdingbranch'}) {
354 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
359 Given a hashref containing item fields, determine if it can be
360 inserted or updated in the database. Specifically, checks for
361 database integrity issues, and returns a hash containing any
362 of the following keys, if applicable.
366 =item duplicate_barcode
368 Barcode, if it duplicates one already found in the database.
370 =item invalid_homebranch
372 Home branch, if not defined in branches table.
374 =item invalid_holdingbranch
376 Holding branch, if not defined in branches table.
380 This function does NOT implement any policy-related checks,
381 e.g., whether current operator is allowed to save an
382 item that has a given branch code.
386 sub CheckItemPreSave {
387 my $item_ref = shift;
391 # check for duplicate barcode
392 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
393 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
394 if ($existing_item) {
395 if (!exists $item_ref->{'itemnumber'} # new item
396 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
397 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
402 # check for valid home branch
403 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
404 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
405 unless (defined $home_library) {
406 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
410 # check for valid holding branch
411 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
412 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
413 unless (defined $holding_library) {
414 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
422 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
424 The following functions provide various ways of
425 getting an item record, a set of item records, or
426 lists of authorized values for certain item fields.
430 =head2 GetItemsForInventory
432 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
433 minlocation => $minlocation,
434 maxlocation => $maxlocation,
435 location => $location,
436 itemtype => $itemtype,
437 ignoreissued => $ignoreissued,
438 datelastseen => $datelastseen,
439 branchcode => $branchcode,
443 statushash => $statushash,
446 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
448 The sub returns a reference to a list of hashes, each containing
449 itemnumber, author, title, barcode, item callnumber, and date last
450 seen. It is ordered by callnumber then title.
452 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
453 the datelastseen can be used to specify that you want to see items not seen since a past date only.
454 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
455 $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.
457 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
461 sub GetItemsForInventory {
462 my ( $parameters ) = @_;
463 my $minlocation = $parameters->{'minlocation'} // '';
464 my $maxlocation = $parameters->{'maxlocation'} // '';
465 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
466 my $location = $parameters->{'location'} // '';
467 my $itemtype = $parameters->{'itemtype'} // '';
468 my $ignoreissued = $parameters->{'ignoreissued'} // '';
469 my $datelastseen = $parameters->{'datelastseen'} // '';
470 my $branchcode = $parameters->{'branchcode'} // '';
471 my $branch = $parameters->{'branch'} // '';
472 my $offset = $parameters->{'offset'} // '';
473 my $size = $parameters->{'size'} // '';
474 my $statushash = $parameters->{'statushash'} // '';
475 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
477 my $dbh = C4::Context->dbh;
478 my ( @bind_params, @where_strings );
480 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
481 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
483 my $select_columns = q{
484 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
486 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
489 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
490 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
493 for my $authvfield (keys %$statushash){
494 if ( scalar @{$statushash->{$authvfield}} > 0 ){
495 my $joinedvals = join ',', @{$statushash->{$authvfield}};
496 push @where_strings, "$authvfield in (" . $joinedvals . ")";
502 push @where_strings, 'items.cn_sort >= ?';
503 push @bind_params, $min_cnsort;
507 push @where_strings, 'items.cn_sort <= ?';
508 push @bind_params, $max_cnsort;
512 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
513 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
514 push @bind_params, $datelastseen;
518 push @where_strings, 'items.location = ?';
519 push @bind_params, $location;
523 if($branch eq "homebranch"){
524 push @where_strings, 'items.homebranch = ?';
526 push @where_strings, 'items.holdingbranch = ?';
528 push @bind_params, $branchcode;
532 push @where_strings, 'biblioitems.itemtype = ?';
533 push @bind_params, $itemtype;
536 if ( $ignoreissued) {
537 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
538 push @where_strings, 'issues.date_due IS NULL';
541 if ( $ignore_waiting_holds ) {
542 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
543 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
546 if ( @where_strings ) {
548 $query .= join ' AND ', @where_strings;
550 my $count_query = $select_count . $query;
551 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
552 $query .= " LIMIT $offset, $size" if ($offset and $size);
553 $query = $select_columns . $query;
554 my $sth = $dbh->prepare($query);
555 $sth->execute( @bind_params );
558 my $tmpresults = $sth->fetchall_arrayref({});
559 $sth = $dbh->prepare( $count_query );
560 $sth->execute( @bind_params );
561 my ($iTotalRecords) = $sth->fetchrow_array();
563 my @avs = Koha::AuthorisedValues->search(
564 { 'marc_subfield_structures.kohafield' => { '>' => '' },
565 'me.authorised_value' => { '>' => '' },
567 { join => { category => 'marc_subfield_structures' },
568 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
569 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
570 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
574 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
576 foreach my $row (@$tmpresults) {
579 foreach (keys %$row) {
582 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
585 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
591 return (\@results, $iTotalRecords);
596 @results = GetItemsInfo($biblionumber);
598 Returns information about items with the given biblionumber.
600 C<GetItemsInfo> returns a list of references-to-hash. Each element
601 contains a number of keys. Most of them are attributes from the
602 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
603 Koha database. Other keys include:
607 =item C<$data-E<gt>{branchname}>
609 The name (not the code) of the branch to which the book belongs.
611 =item C<$data-E<gt>{datelastseen}>
613 This is simply C<items.datelastseen>, except that while the date is
614 stored in YYYY-MM-DD format in the database, here it is converted to
615 DD/MM/YYYY format. A NULL date is returned as C<//>.
617 =item C<$data-E<gt>{datedue}>
619 =item C<$data-E<gt>{class}>
621 This is the concatenation of C<biblioitems.classification>, the book's
622 Dewey code, and C<biblioitems.subclass>.
624 =item C<$data-E<gt>{ocount}>
626 I think this is the number of copies of the book available.
628 =item C<$data-E<gt>{order}>
630 If this is set, it is set to C<One Order>.
637 my ( $biblionumber ) = @_;
638 my $dbh = C4::Context->dbh;
639 require C4::Languages;
640 my $language = C4::Languages::getlanguage();
646 biblioitems.itemtype,
649 biblioitems.publicationyear,
650 biblioitems.publishercode,
651 biblioitems.volumedate,
652 biblioitems.volumedesc,
655 items.notforloan as itemnotforloan,
656 issues.borrowernumber,
657 issues.date_due as datedue,
658 issues.onsite_checkout,
659 borrowers.cardnumber,
662 borrowers.branchcode as bcode,
664 serial.publisheddate,
665 itemtypes.description,
666 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
667 itemtypes.notforloan as notforloan_per_itemtype,
671 holding.opac_info as holding_branch_opac_info,
672 home.opac_info as home_branch_opac_info,
673 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
675 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
676 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
677 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
678 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
679 LEFT JOIN issues USING (itemnumber)
680 LEFT JOIN borrowers USING (borrowernumber)
681 LEFT JOIN serialitems USING (itemnumber)
682 LEFT JOIN serial USING (serialid)
683 LEFT JOIN itemtypes ON itemtypes.itemtype = "
684 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
686 LEFT JOIN tmp_holdsqueue USING (itemnumber)
687 LEFT JOIN localization ON itemtypes.itemtype = localization.code
688 AND localization.entity = 'itemtypes'
689 AND localization.lang = ?
692 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
693 my $sth = $dbh->prepare($query);
694 $sth->execute($language, $biblionumber);
699 my $userenv = C4::Context->userenv;
700 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
701 while ( my $data = $sth->fetchrow_hashref ) {
702 if ( $data->{borrowernumber} && $want_not_same_branch) {
703 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
706 $serial ||= $data->{'serial'};
709 # get notforloan complete status if applicable
710 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
711 $data->{notforloanvalue} = $descriptions->{lib} // '';
712 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
714 # get restricted status and description if applicable
715 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
716 $data->{restrictedvalue} = $descriptions->{lib} // '';
717 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
719 # my stack procedures
720 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
721 $data->{stack} = $descriptions->{lib} // '';
723 # Find the last 3 people who borrowed this item.
724 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
726 AND old_issues.borrowernumber = borrowers.borrowernumber
727 ORDER BY returndate DESC
729 $sth2->execute($data->{'itemnumber'});
731 while (my $data2 = $sth2->fetchrow_hashref()) {
732 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
733 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
734 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
738 $results[$i] = $data;
743 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
747 =head2 GetItemsLocationInfo
749 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
751 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
753 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
757 =item C<$data-E<gt>{homebranch}>
759 Branch Name of the item's homebranch
761 =item C<$data-E<gt>{holdingbranch}>
763 Branch Name of the item's holdingbranch
765 =item C<$data-E<gt>{location}>
767 Item's shelving location code
769 =item C<$data-E<gt>{location_intranet}>
771 The intranet description for the Shelving Location as set in authorised_values 'LOC'
773 =item C<$data-E<gt>{location_opac}>
775 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
778 =item C<$data-E<gt>{itemcallnumber}>
780 Item's itemcallnumber
782 =item C<$data-E<gt>{cn_sort}>
784 Item's call number normalized for sorting
790 sub GetItemsLocationInfo {
791 my $biblionumber = shift;
794 my $dbh = C4::Context->dbh;
795 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
796 location, itemcallnumber, cn_sort
797 FROM items, branches as a, branches as b
798 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
800 ORDER BY cn_sort ASC";
801 my $sth = $dbh->prepare($query);
802 $sth->execute($biblionumber);
804 while ( my $data = $sth->fetchrow_hashref ) {
805 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
806 $av = $av->count ? $av->next : undef;
807 $data->{location_intranet} = $av ? $av->lib : '';
808 $data->{location_opac} = $av ? $av->opac_description : '';
809 push @results, $data;
814 =head2 GetHostItemsInfo
816 $hostiteminfo = GetHostItemsInfo($hostfield);
817 Returns the iteminfo for items linked to records via a host field
821 sub GetHostItemsInfo {
825 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
826 return @returnitemsInfo;
830 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
831 C4::Context->preference('marcflavour') eq 'NORMARC') {
832 @fields = $record->field('773');
833 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
834 @fields = $record->field('461');
837 foreach my $hostfield ( @fields ) {
838 my $hostbiblionumber = $hostfield->subfield("0");
839 my $linkeditemnumber = $hostfield->subfield("9");
840 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
841 foreach my $hostitemInfo (@hostitemInfos) {
842 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
843 push @returnitemsInfo, $hostitemInfo;
848 return @returnitemsInfo;
851 =head2 get_hostitemnumbers_of
853 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
855 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
857 Return a reference on a hash where key is a biblionumber and values are
858 references on array of itemnumbers.
863 sub get_hostitemnumbers_of {
864 my ($biblionumber) = @_;
866 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
870 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
871 return unless $marcrecord;
873 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
875 my $marcflavor = C4::Context->preference('marcflavour');
876 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
881 elsif ( $marcflavor eq 'UNIMARC' ) {
887 foreach my $hostfield ( $marcrecord->field($tag) ) {
888 my $hostbiblionumber = $hostfield->subfield($biblio_s);
889 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
890 my $linkeditemnumber = $hostfield->subfield($item_s);
891 if ( ! $linkeditemnumber ) {
892 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
895 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
896 push @returnhostitemnumbers, $linkeditemnumber
900 return @returnhostitemnumbers;
903 =head2 GetHiddenItemnumbers
905 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
907 Given a list of items it checks which should be hidden from the OPAC given
908 the current configuration. Returns a list of itemnumbers corresponding to
909 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
914 sub GetHiddenItemnumbers {
916 my $items = $params->{items};
917 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
918 foreach my $except (split(/\|/, $exceptions)){
919 if ($params->{'borcat'} eq $except){
920 return; # we don't hide anything for this borrower category
926 my $yaml = C4::Context->preference('OpacHiddenItems');
927 return () if (! $yaml =~ /\S/ );
928 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
931 $hidingrules = YAML::Load($yaml);
934 warn "Unable to parse OpacHiddenItems syspref : $@";
937 my $dbh = C4::Context->dbh;
940 foreach my $item (@$items) {
943 foreach my $field (keys %$hidingrules) {
945 if (exists $item->{$field}) {
946 $val = $item->{$field};
949 my $query = "SELECT $field from items where itemnumber = ?";
950 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
952 $val = '' unless defined $val;
954 # If the results matches the values in the yaml file
955 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
957 # We add the itemnumber to the list
958 push @resultitems, $item->{'itemnumber'};
960 # If at least one rule matched for an item, no need to test the others
968 =head1 LIMITED USE FUNCTIONS
970 The following functions, while part of the public API,
971 are not exported. This is generally because they are
972 meant to be used by only one script for a specific
973 purpose, and should not be used in any other context
974 without careful thought.
980 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
982 Returns MARC::Record of the item passed in parameter.
983 This function is meant for use only in C<cataloguing/additem.pl>,
984 where it is needed to support that script's MARC-like
990 my ( $biblionumber, $itemnumber ) = @_;
992 # GetMarcItem has been revised so that it does the following:
993 # 1. Gets the item information from the items table.
994 # 2. Converts it to a MARC field for storage in the bib record.
996 # The previous behavior was:
997 # 1. Get the bib record.
998 # 2. Return the MARC tag corresponding to the item record.
1000 # The difference is that one treats the items row as authoritative,
1001 # while the other treats the MARC representation as authoritative
1002 # under certain circumstances.
1004 my $item = Koha::Items->find($itemnumber) or return;
1006 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1007 # Also, don't emit a subfield if the underlying field is blank.
1009 return Item2Marc($item->unblessed, $biblionumber);
1013 my ($itemrecord,$biblionumber)=@_;
1016 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1017 } keys %{ $itemrecord }
1019 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1020 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1021 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1022 "items.itemnumber", $framework,
1025 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1026 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1027 foreach my $field ($itemmarc->field($itemtag)){
1028 $field->add_subfields(@$unlinked_item_subfields);
1034 =head1 PRIVATE FUNCTIONS AND VARIABLES
1036 The following functions are not meant to be called
1037 directly, but are documented in order to explain
1038 the inner workings of C<C4::Items>.
1042 =head2 MoveItemFromBiblio
1044 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1046 Moves an item from a biblio to another
1048 Returns undef if the move failed or the biblionumber of the destination record otherwise
1052 sub MoveItemFromBiblio {
1053 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1054 my $dbh = C4::Context->dbh;
1055 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1056 SELECT biblioitemnumber
1058 WHERE biblionumber = ?
1059 |, undef, $tobiblio );
1060 my $return = $dbh->do(q|
1062 SET biblioitemnumber = ?,
1064 WHERE itemnumber = ?
1065 AND biblionumber = ?
1066 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1068 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1069 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1070 # Checking if the item we want to move is in an order
1071 require C4::Acquisition;
1072 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1074 # Replacing the biblionumber within the order if necessary
1075 $order->{'biblionumber'} = $tobiblio;
1076 C4::Acquisition::ModOrder($order);
1079 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1080 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1083 SET biblionumber = ?
1084 WHERE itemnumber = ?
1085 |, undef, $tobiblio, $itemnumber );
1092 =head2 _marc_from_item_hash
1094 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1096 Given an item hash representing a complete item record,
1097 create a C<MARC::Record> object containing an embedded
1098 tag representing that item.
1100 The third, optional parameter C<$unlinked_item_subfields> is
1101 an arrayref of subfields (not mapped to C<items> fields per the
1102 framework) to be added to the MARC representation
1107 sub _marc_from_item_hash {
1109 my $frameworkcode = shift;
1110 my $unlinked_item_subfields;
1112 $unlinked_item_subfields = shift;
1115 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1116 # Also, don't emit a subfield if the underlying field is blank.
1117 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1118 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1119 : () } keys %{ $item } };
1121 my $item_marc = MARC::Record->new();
1122 foreach my $item_field ( keys %{$mungeditem} ) {
1123 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1124 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1125 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1126 foreach my $value (@values){
1127 if ( my $field = $item_marc->field($tag) ) {
1128 $field->add_subfields( $subfield => $value );
1130 my $add_subfields = [];
1131 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1132 $add_subfields = $unlinked_item_subfields;
1134 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1142 =head2 _repack_item_errors
1144 Add an error message hash generated by C<CheckItemPreSave>
1145 to a list of errors.
1149 sub _repack_item_errors {
1150 my $item_sequence_num = shift;
1151 my $item_ref = shift;
1152 my $error_ref = shift;
1154 my @repacked_errors = ();
1156 foreach my $error_code (sort keys %{ $error_ref }) {
1157 my $repacked_error = {};
1158 $repacked_error->{'item_sequence'} = $item_sequence_num;
1159 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1160 $repacked_error->{'error_code'} = $error_code;
1161 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1162 push @repacked_errors, $repacked_error;
1165 return @repacked_errors;
1168 =head2 _get_unlinked_item_subfields
1170 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1174 sub _get_unlinked_item_subfields {
1175 my $original_item_marc = shift;
1176 my $frameworkcode = shift;
1178 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1180 # assume that this record has only one field, and that that
1181 # field contains only the item information
1183 my @fields = $original_item_marc->fields();
1184 if ($#fields > -1) {
1185 my $field = $fields[0];
1186 my $tag = $field->tag();
1187 foreach my $subfield ($field->subfields()) {
1188 if (defined $subfield->[1] and
1189 $subfield->[1] ne '' and
1190 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1191 push @$subfields, $subfield->[0] => $subfield->[1];
1198 =head2 _get_unlinked_subfields_xml
1200 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1204 sub _get_unlinked_subfields_xml {
1205 my $unlinked_item_subfields = shift;
1208 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1209 my $marc = MARC::Record->new();
1210 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1211 # used in the framework
1212 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1213 $marc->encoding("UTF-8");
1214 $xml = $marc->as_xml("USMARC");
1220 =head2 _parse_unlinked_item_subfields_from_xml
1222 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1226 sub _parse_unlinked_item_subfields_from_xml {
1228 require C4::Charset;
1229 return unless defined $xml and $xml ne "";
1230 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1231 my $unlinked_subfields = [];
1232 my @fields = $marc->fields();
1233 if ($#fields > -1) {
1234 foreach my $subfield ($fields[0]->subfields()) {
1235 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1238 return $unlinked_subfields;
1241 =head2 GetAnalyticsCount
1243 $count= &GetAnalyticsCount($itemnumber)
1245 counts Usage of itemnumber in Analytical bibliorecords.
1249 sub GetAnalyticsCount {
1250 my ($itemnumber) = @_;
1252 ### ZOOM search here
1254 $query= "hi=".$itemnumber;
1255 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1256 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1260 sub _SearchItems_build_where_fragment {
1263 my $dbh = C4::Context->dbh;
1266 if (exists($filter->{conjunction})) {
1267 my (@where_strs, @where_args);
1268 foreach my $f (@{ $filter->{filters} }) {
1269 my $fragment = _SearchItems_build_where_fragment($f);
1271 push @where_strs, $fragment->{str};
1272 push @where_args, @{ $fragment->{args} };
1277 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1280 args => \@where_args,
1284 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1285 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1286 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1287 my @operators = qw(= != > < >= <= like);
1288 my $field = $filter->{field} // q{};
1289 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1290 my $op = $filter->{operator};
1291 my $query = $filter->{query};
1293 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1294 $op = '='; # default operator
1298 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1300 my $marcsubfield = $2;
1301 my ($kohafield) = $dbh->selectrow_array(q|
1302 SELECT kohafield FROM marc_subfield_structure
1303 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1304 |, undef, $marcfield, $marcsubfield);
1307 $column = $kohafield;
1309 # MARC field is not linked to a DB field so we need to use
1310 # ExtractValue on marcxml from biblio_metadata or
1311 # items.more_subfields_xml, depending on the MARC field.
1314 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1315 if ($marcfield eq $itemfield) {
1316 $sqlfield = 'more_subfields_xml';
1317 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1319 $sqlfield = 'metadata'; # From biblio_metadata
1320 if ($marcfield < 10) {
1321 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1323 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1326 $column = "ExtractValue($sqlfield, '$xpath')";
1328 } elsif ($field eq 'issues') {
1329 # Consider NULL as 0 for issues count
1330 $column = 'COALESCE(issues,0)';
1335 if (ref $query eq 'ARRAY') {
1338 } elsif ($op eq '!=') {
1342 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1347 str => "$column $op ?",
1354 return $where_fragment;
1359 my ($items, $total) = SearchItems($filter, $params);
1361 Perform a search among items
1363 $filter is a reference to a hash which can be a filter, or a combination of filters.
1365 A filter has the following keys:
1369 =item * field: the name of a SQL column in table items
1371 =item * query: the value to search in this column
1373 =item * operator: comparison operator. Can be one of = != > < >= <= like
1377 A combination of filters hash the following keys:
1381 =item * conjunction: 'AND' or 'OR'
1383 =item * filters: array ref of filters
1387 $params is a reference to a hash that can contain the following parameters:
1391 =item * rows: Number of items to return. 0 returns everything (default: 0)
1393 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1396 =item * sortby: A SQL column name in items table to sort on
1398 =item * sortorder: 'ASC' or 'DESC'
1405 my ($filter, $params) = @_;
1409 return unless ref $filter eq 'HASH';
1410 return unless ref $params eq 'HASH';
1412 # Default parameters
1413 $params->{rows} ||= 0;
1414 $params->{page} ||= 1;
1415 $params->{sortby} ||= 'itemnumber';
1416 $params->{sortorder} ||= 'ASC';
1418 my ($where_str, @where_args);
1419 my $where_fragment = _SearchItems_build_where_fragment($filter);
1420 if ($where_fragment) {
1421 $where_str = $where_fragment->{str};
1422 @where_args = @{ $where_fragment->{args} };
1425 my $dbh = C4::Context->dbh;
1427 SELECT SQL_CALC_FOUND_ROWS items.*
1429 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1430 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1431 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1434 if (defined $where_str and $where_str ne '') {
1435 $query .= qq{ AND $where_str };
1438 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1439 push @where_args, C4::Context->preference('marcflavour');
1441 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1442 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1443 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1444 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1445 ? $params->{sortby} : 'itemnumber';
1446 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1447 $query .= qq{ ORDER BY $sortby $sortorder };
1449 my $rows = $params->{rows};
1452 my $offset = $rows * ($params->{page}-1);
1453 $query .= qq { LIMIT ?, ? };
1454 push @limit_args, $offset, $rows;
1457 my $sth = $dbh->prepare($query);
1458 my $rv = $sth->execute(@where_args, @limit_args);
1460 return unless ($rv);
1461 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1463 return ($sth->fetchall_arrayref({}), $total_rows);
1467 =head1 OTHER FUNCTIONS
1471 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1473 Find the given $subfield in the given $tag in the given
1474 MARC::Record $record. If the subfield is found, returns
1475 the (indicators, value) pair; otherwise, (undef, undef) is
1479 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1480 I suggest we export it from this module.
1485 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1488 if ( $tagfield < 10 ) {
1489 if ( $record->field($tagfield) ) {
1490 push @result, $record->field($tagfield)->data();
1495 foreach my $field ( $record->field($tagfield) ) {
1496 my @subfields = $field->subfields();
1497 foreach my $subfield (@subfields) {
1498 if ( @$subfield[0] eq $insubfield ) {
1499 push @result, @$subfield[1];
1500 $indicator = $field->indicator(1) . $field->indicator(2);
1505 return ( $indicator, @result );
1509 =head2 PrepareItemrecordDisplay
1511 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1513 Returns a hash with all the fields for Display a given item data in a template
1515 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1519 sub PrepareItemrecordDisplay {
1521 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1523 my $dbh = C4::Context->dbh;
1524 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1525 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1527 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1528 # a shared data structure. No plugin (including custom ones) should change
1529 # its contents. See also GetMarcStructure.
1530 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1532 # return nothing if we don't have found an existing framework.
1533 return q{} unless $tagslib;
1536 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1540 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1542 SELECT authorised_value,lib FROM authorised_values
1545 LEFT JOIN authorised_values_branches ON ( id = av_id )
1550 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1551 $query .= qq{ ORDER BY lib};
1552 my $authorised_values_sth = $dbh->prepare( $query );
1553 foreach my $tag ( sort keys %{$tagslib} ) {
1556 # loop through each subfield
1558 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1559 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1560 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1561 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1563 $subfield_data{tag} = $tag;
1564 $subfield_data{subfield} = $subfield;
1565 $subfield_data{countsubfield} = $cntsubf++;
1566 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1567 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1569 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1570 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1571 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1572 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1573 $subfield_data{hidden} = "display:none"
1574 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1575 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1576 my ( $x, $defaultvalue );
1578 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1580 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1581 if ( !defined $defaultvalue ) {
1582 $defaultvalue = q||;
1584 $defaultvalue =~ s/"/"/g;
1587 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1589 # search for itemcallnumber if applicable
1590 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1591 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1592 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1593 my $CNtag = substr( $itemcn_pref, 0, 3 );
1594 next unless my $field = $itemrecord->field($CNtag);
1595 my $CNsubfields = substr( $itemcn_pref, 3 );
1596 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1597 last if $defaultvalue;
1600 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1602 && $defaultvalues->{'callnumber'} ) {
1603 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1604 # if the item record exists, only use default value if the item has no callnumber
1605 $defaultvalue = $defaultvalues->{callnumber};
1606 } elsif ( !$itemrecord and $defaultvalues ) {
1607 # if the item record *doesn't* exists, always use the default value
1608 $defaultvalue = $defaultvalues->{callnumber};
1611 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1613 && $defaultvalues->{'branchcode'} ) {
1614 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1615 $defaultvalue = $defaultvalues->{branchcode};
1618 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1620 && $defaultvalues->{'location'} ) {
1622 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1623 # if the item record exists, only use default value if the item has no locationr
1624 $defaultvalue = $defaultvalues->{location};
1625 } elsif ( !$itemrecord and $defaultvalues ) {
1626 # if the item record *doesn't* exists, always use the default value
1627 $defaultvalue = $defaultvalues->{location};
1630 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1631 my @authorised_values;
1634 # builds list, depending on authorised value...
1636 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1637 if ( ( C4::Context->preference("IndependentBranches") )
1638 && !C4::Context->IsSuperLibrarian() ) {
1639 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1640 $sth->execute( C4::Context->userenv->{branch} );
1641 push @authorised_values, ""
1642 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1643 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1644 push @authorised_values, $branchcode;
1645 $authorised_lib{$branchcode} = $branchname;
1648 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1650 push @authorised_values, ""
1651 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1652 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1653 push @authorised_values, $branchcode;
1654 $authorised_lib{$branchcode} = $branchname;
1658 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1659 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1660 $defaultvalue = $defaultvalues->{branchcode};
1664 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1665 my $itemtypes = Koha::ItemTypes->search_with_localization;
1666 push @authorised_values, ""
1667 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1668 while ( my $itemtype = $itemtypes->next ) {
1669 push @authorised_values, $itemtype->itemtype;
1670 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1672 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1673 $defaultvalue = $defaultvalues->{'itemtype'};
1677 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1678 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1680 my $class_sources = GetClassSources();
1681 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1683 foreach my $class_source (sort keys %$class_sources) {
1684 next unless $class_sources->{$class_source}->{'used'} or
1685 ($class_source eq $default_source);
1686 push @authorised_values, $class_source;
1687 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1690 $defaultvalue = $default_source;
1692 #---- "true" authorised value
1694 $authorised_values_sth->execute(
1695 $tagslib->{$tag}->{$subfield}->{authorised_value},
1696 $branch_limit ? $branch_limit : ()
1698 push @authorised_values, ""
1699 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1700 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1701 push @authorised_values, $value;
1702 $authorised_lib{$value} = $lib;
1705 $subfield_data{marc_value} = {
1707 values => \@authorised_values,
1708 default => $defaultvalue // q{},
1709 labels => \%authorised_lib,
1711 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1713 require Koha::FrameworkPlugin;
1714 my $plugin = Koha::FrameworkPlugin->new({
1715 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1718 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1719 $plugin->build( $pars );
1720 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1721 $defaultvalue = $field->subfield($subfield) || q{};
1723 if( !$plugin->errstr ) {
1724 #TODO Move html to template; see report 12176/13397
1725 my $tab= $plugin->noclick? '-1': '';
1726 my $class= $plugin->noclick? ' disabled': '';
1727 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1728 $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;
1730 warn $plugin->errstr;
1731 $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
1734 elsif ( $tag eq '' ) { # it's an hidden field
1735 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1737 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1738 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1740 elsif ( length($defaultvalue) > 100
1741 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1742 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1743 or (C4::Context->preference("marcflavour") eq "MARC21" and
1744 500 <= $tag && $tag < 600 )
1746 # oversize field (textarea)
1747 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1749 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1751 push( @loop_data, \%subfield_data );
1756 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1757 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1760 'itemtagfield' => $itemtagfield,
1761 'itemtagsubfield' => $itemtagsubfield,
1762 'itemnumber' => $itemnumber,
1763 'iteminformation' => \@loop_data
1767 sub ToggleNewStatus {
1768 my ( $params ) = @_;
1769 my @rules = @{ $params->{rules} };
1770 my $report_only = $params->{report_only};
1772 my $dbh = C4::Context->dbh;
1774 my @item_columns = map { "items.$_" } Koha::Items->columns;
1775 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1777 for my $rule ( @rules ) {
1778 my $age = $rule->{age};
1779 my $conditions = $rule->{conditions};
1780 my $substitutions = $rule->{substitutions};
1781 foreach ( @$substitutions ) {
1782 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1789 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1792 for my $condition ( @$conditions ) {
1794 grep { $_ eq $condition->{field} } @item_columns
1795 or grep { $_ eq $condition->{field} } @biblioitem_columns
1797 if ( $condition->{value} =~ /\|/ ) {
1798 my @values = split /\|/, $condition->{value};
1799 $query .= qq| AND $condition->{field} IN (|
1800 . join( ',', ('?') x scalar @values )
1802 push @params, @values;
1804 $query .= qq| AND $condition->{field} = ?|;
1805 push @params, $condition->{value};
1809 if ( defined $age ) {
1810 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1813 my $sth = $dbh->prepare($query);
1814 $sth->execute( @params );
1815 while ( my $values = $sth->fetchrow_hashref ) {
1816 my $biblionumber = $values->{biblionumber};
1817 my $itemnumber = $values->{itemnumber};
1818 my $item = Koha::Items->find($itemnumber);
1819 for my $substitution ( @$substitutions ) {
1820 my $field = $substitution->{item_field};
1821 my $value = $substitution->{value};
1822 next unless $substitution->{field};
1823 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1824 $item->$field($value);
1825 push @{ $report->{$itemnumber} }, $substitution;
1827 $item->store unless $report_only;