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 my $item = Koha::Item->new( $item_values )->store;
163 return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
166 =head2 AddItemBatchFromMarc
168 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
169 $biblionumber, $biblioitemnumber, $frameworkcode);
171 Efficiently create item records from a MARC biblio record with
172 embedded item fields. This routine is suitable for batch jobs.
174 This API assumes that the bib record has already been
175 saved to the C<biblio> and C<biblioitems> tables. It does
176 not expect that C<biblio_metadata.metadata> is populated, but it
177 will do so via a call to ModBibiloMarc.
179 The goal of this API is to have a similar effect to using AddBiblio
180 and AddItems in succession, but without inefficient repeated
181 parsing of the MARC XML bib record.
183 This function returns an arrayref of new itemsnumbers and an arrayref of item
184 errors encountered during the processing. Each entry in the errors
185 list is a hashref containing the following keys:
191 Sequence number of original item tag in the MARC record.
195 Item barcode, provide to assist in the construction of
196 useful error messages.
200 Code representing the error condition. Can be 'duplicate_barcode',
201 'invalid_homebranch', or 'invalid_holdingbranch'.
203 =item error_information
205 Additional information appropriate to the error condition.
211 sub AddItemBatchFromMarc {
212 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
214 my @itemnumbers = ();
216 my $dbh = C4::Context->dbh;
218 # We modify the record, so lets work on a clone so we don't change the
220 $record = $record->clone();
221 # loop through the item tags and start creating items
222 my @bad_item_fields = ();
223 my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
224 my $item_sequence_num = 0;
225 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
226 $item_sequence_num++;
227 # we take the item field and stick it into a new
228 # MARC record -- this is required so far because (FIXME)
229 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
230 # and there is no TransformMarcFieldToKoha
231 my $temp_item_marc = MARC::Record->new();
232 $temp_item_marc->append_fields($item_field);
234 # add biblionumber and biblioitemnumber
235 my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
236 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
237 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
238 $item->{'biblionumber'} = $biblionumber;
239 $item->{'biblioitemnumber'} = $biblioitemnumber;
241 # check for duplicate barcode
242 my %item_errors = CheckItemPreSave($item);
244 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
245 push @bad_item_fields, $item_field;
249 my $item_object = Koha::Item->new($item)->store;
250 push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
252 logaction("CATALOGUING", "ADD", $item->itemnumber, "item") if C4::Context->preference("CataloguingLog");
254 my $new_item_marc = _marc_from_item_hash($item->unblessed, $frameworkcode, $unlinked_item_subfields);
255 $item_field->replace_with($new_item_marc->field($itemtag));
258 # remove any MARC item fields for rejected items
259 foreach my $item_field (@bad_item_fields) {
260 $record->delete_field($item_field);
263 # update the MARC biblio
264 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
266 return (\@itemnumbers, \@errors);
269 sub ModItemFromMarc {
270 my $item_marc = shift;
271 my $biblionumber = shift;
272 my $itemnumber = shift;
274 my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
275 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
277 my $localitemmarc = MARC::Record->new;
278 $localitemmarc->append_fields( $item_marc->field($itemtag) );
279 my $item_object = Koha::Items->find($itemnumber);
280 my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
281 $item_object->set($item);
282 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
283 $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
286 return $item_object->unblessed;
289 =head2 ModItemTransfer
291 ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
293 Marks an item as being transferred from one branch to another and records the trigger.
297 sub ModItemTransfer {
298 my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
300 my $dbh = C4::Context->dbh;
301 my $item = Koha::Items->find( $itemnumber );
303 # Remove the 'shelving cart' location status if it is being used.
304 CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
306 $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
308 #new entry in branchtransfers....
309 my $sth = $dbh->prepare(
310 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
311 VALUES (?, ?, NOW(), ?, ?)");
312 $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
314 # FIXME we are fetching the item twice in the 2 next statements!
315 Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
316 ModDateLastSeen($itemnumber);
320 =head2 ModDateLastSeen
322 ModDateLastSeen( $itemnumber, $leave_item_lost );
324 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
325 C<$itemnumber> is the item number
326 C<$leave_item_lost> determines if a lost item will be found or remain lost
330 sub ModDateLastSeen {
331 my ( $itemnumber, $leave_item_lost ) = @_;
333 my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
335 my $item = Koha::Items->find($itemnumber);
336 $item->datelastseen($today);
337 $item->itemlost(0) unless $leave_item_lost;
338 $item->store({ log_action => 0 });
341 =head2 CheckItemPreSave
343 my $item_ref = TransformMarcToKoha($marc, 'items');
345 my %errors = CheckItemPreSave($item_ref);
346 if (exists $errors{'duplicate_barcode'}) {
347 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
348 } elsif (exists $errors{'invalid_homebranch'}) {
349 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
350 } elsif (exists $errors{'invalid_holdingbranch'}) {
351 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
356 Given a hashref containing item fields, determine if it can be
357 inserted or updated in the database. Specifically, checks for
358 database integrity issues, and returns a hash containing any
359 of the following keys, if applicable.
363 =item duplicate_barcode
365 Barcode, if it duplicates one already found in the database.
367 =item invalid_homebranch
369 Home branch, if not defined in branches table.
371 =item invalid_holdingbranch
373 Holding branch, if not defined in branches table.
377 This function does NOT implement any policy-related checks,
378 e.g., whether current operator is allowed to save an
379 item that has a given branch code.
383 sub CheckItemPreSave {
384 my $item_ref = shift;
388 # check for duplicate barcode
389 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
390 my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
391 if ($existing_item) {
392 if (!exists $item_ref->{'itemnumber'} # new item
393 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
394 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
399 # check for valid home branch
400 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
401 my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
402 unless (defined $home_library) {
403 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
407 # check for valid holding branch
408 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
409 my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
410 unless (defined $holding_library) {
411 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
419 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
421 The following functions provide various ways of
422 getting an item record, a set of item records, or
423 lists of authorized values for certain item fields.
427 =head2 GetItemsForInventory
429 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
430 minlocation => $minlocation,
431 maxlocation => $maxlocation,
432 location => $location,
433 itemtype => $itemtype,
434 ignoreissued => $ignoreissued,
435 datelastseen => $datelastseen,
436 branchcode => $branchcode,
440 statushash => $statushash,
443 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
445 The sub returns a reference to a list of hashes, each containing
446 itemnumber, author, title, barcode, item callnumber, and date last
447 seen. It is ordered by callnumber then title.
449 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
450 the datelastseen can be used to specify that you want to see items not seen since a past date only.
451 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
452 $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.
454 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
458 sub GetItemsForInventory {
459 my ( $parameters ) = @_;
460 my $minlocation = $parameters->{'minlocation'} // '';
461 my $maxlocation = $parameters->{'maxlocation'} // '';
462 my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
463 my $location = $parameters->{'location'} // '';
464 my $itemtype = $parameters->{'itemtype'} // '';
465 my $ignoreissued = $parameters->{'ignoreissued'} // '';
466 my $datelastseen = $parameters->{'datelastseen'} // '';
467 my $branchcode = $parameters->{'branchcode'} // '';
468 my $branch = $parameters->{'branch'} // '';
469 my $offset = $parameters->{'offset'} // '';
470 my $size = $parameters->{'size'} // '';
471 my $statushash = $parameters->{'statushash'} // '';
472 my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
474 my $dbh = C4::Context->dbh;
475 my ( @bind_params, @where_strings );
477 my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
478 my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
480 my $select_columns = q{
481 SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
483 my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
486 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
487 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
490 for my $authvfield (keys %$statushash){
491 if ( scalar @{$statushash->{$authvfield}} > 0 ){
492 my $joinedvals = join ',', @{$statushash->{$authvfield}};
493 push @where_strings, "$authvfield in (" . $joinedvals . ")";
499 push @where_strings, 'items.cn_sort >= ?';
500 push @bind_params, $min_cnsort;
504 push @where_strings, 'items.cn_sort <= ?';
505 push @bind_params, $max_cnsort;
509 $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
510 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
511 push @bind_params, $datelastseen;
515 push @where_strings, 'items.location = ?';
516 push @bind_params, $location;
520 if($branch eq "homebranch"){
521 push @where_strings, 'items.homebranch = ?';
523 push @where_strings, 'items.holdingbranch = ?';
525 push @bind_params, $branchcode;
529 push @where_strings, 'biblioitems.itemtype = ?';
530 push @bind_params, $itemtype;
533 if ( $ignoreissued) {
534 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
535 push @where_strings, 'issues.date_due IS NULL';
538 if ( $ignore_waiting_holds ) {
539 $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
540 push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
543 if ( @where_strings ) {
545 $query .= join ' AND ', @where_strings;
547 my $count_query = $select_count . $query;
548 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
549 $query .= " LIMIT $offset, $size" if ($offset and $size);
550 $query = $select_columns . $query;
551 my $sth = $dbh->prepare($query);
552 $sth->execute( @bind_params );
555 my $tmpresults = $sth->fetchall_arrayref({});
556 $sth = $dbh->prepare( $count_query );
557 $sth->execute( @bind_params );
558 my ($iTotalRecords) = $sth->fetchrow_array();
560 my @avs = Koha::AuthorisedValues->search(
561 { 'marc_subfield_structures.kohafield' => { '>' => '' },
562 'me.authorised_value' => { '>' => '' },
564 { join => { category => 'marc_subfield_structures' },
565 distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
566 '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
567 '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ],
571 my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
573 foreach my $row (@$tmpresults) {
576 foreach (keys %$row) {
579 $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
582 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
588 return (\@results, $iTotalRecords);
593 @results = GetItemsInfo($biblionumber);
595 Returns information about items with the given biblionumber.
597 C<GetItemsInfo> returns a list of references-to-hash. Each element
598 contains a number of keys. Most of them are attributes from the
599 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
600 Koha database. Other keys include:
604 =item C<$data-E<gt>{branchname}>
606 The name (not the code) of the branch to which the book belongs.
608 =item C<$data-E<gt>{datelastseen}>
610 This is simply C<items.datelastseen>, except that while the date is
611 stored in YYYY-MM-DD format in the database, here it is converted to
612 DD/MM/YYYY format. A NULL date is returned as C<//>.
614 =item C<$data-E<gt>{datedue}>
616 =item C<$data-E<gt>{class}>
618 This is the concatenation of C<biblioitems.classification>, the book's
619 Dewey code, and C<biblioitems.subclass>.
621 =item C<$data-E<gt>{ocount}>
623 I think this is the number of copies of the book available.
625 =item C<$data-E<gt>{order}>
627 If this is set, it is set to C<One Order>.
634 my ( $biblionumber ) = @_;
635 my $dbh = C4::Context->dbh;
636 require C4::Languages;
637 my $language = C4::Languages::getlanguage();
643 biblioitems.itemtype,
646 biblioitems.publicationyear,
647 biblioitems.publishercode,
648 biblioitems.volumedate,
649 biblioitems.volumedesc,
652 items.notforloan as itemnotforloan,
653 issues.borrowernumber,
654 issues.date_due as datedue,
655 issues.onsite_checkout,
656 borrowers.cardnumber,
659 borrowers.branchcode as bcode,
661 serial.publisheddate,
662 itemtypes.description,
663 COALESCE( localization.translation, itemtypes.description ) AS translated_description,
664 itemtypes.notforloan as notforloan_per_itemtype,
668 holding.opac_info as holding_branch_opac_info,
669 home.opac_info as home_branch_opac_info,
670 IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
672 LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
673 LEFT JOIN branches AS home ON items.homebranch=home.branchcode
674 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
675 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
676 LEFT JOIN issues USING (itemnumber)
677 LEFT JOIN borrowers USING (borrowernumber)
678 LEFT JOIN serialitems USING (itemnumber)
679 LEFT JOIN serial USING (serialid)
680 LEFT JOIN itemtypes ON itemtypes.itemtype = "
681 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
683 LEFT JOIN tmp_holdsqueue USING (itemnumber)
684 LEFT JOIN localization ON itemtypes.itemtype = localization.code
685 AND localization.entity = 'itemtypes'
686 AND localization.lang = ?
689 $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
690 my $sth = $dbh->prepare($query);
691 $sth->execute($language, $biblionumber);
696 my $userenv = C4::Context->userenv;
697 my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
698 while ( my $data = $sth->fetchrow_hashref ) {
699 if ( $data->{borrowernumber} && $want_not_same_branch) {
700 $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
703 $serial ||= $data->{'serial'};
706 # get notforloan complete status if applicable
707 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
708 $data->{notforloanvalue} = $descriptions->{lib} // '';
709 $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
711 # get restricted status and description if applicable
712 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
713 $data->{restrictedvalue} = $descriptions->{lib} // '';
714 $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
716 # my stack procedures
717 $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
718 $data->{stack} = $descriptions->{lib} // '';
720 # Find the last 3 people who borrowed this item.
721 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
723 AND old_issues.borrowernumber = borrowers.borrowernumber
724 ORDER BY returndate DESC
726 $sth2->execute($data->{'itemnumber'});
728 while (my $data2 = $sth2->fetchrow_hashref()) {
729 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
730 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
731 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
735 $results[$i] = $data;
740 ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
744 =head2 GetItemsLocationInfo
746 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
748 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
750 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
754 =item C<$data-E<gt>{homebranch}>
756 Branch Name of the item's homebranch
758 =item C<$data-E<gt>{holdingbranch}>
760 Branch Name of the item's holdingbranch
762 =item C<$data-E<gt>{location}>
764 Item's shelving location code
766 =item C<$data-E<gt>{location_intranet}>
768 The intranet description for the Shelving Location as set in authorised_values 'LOC'
770 =item C<$data-E<gt>{location_opac}>
772 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
775 =item C<$data-E<gt>{itemcallnumber}>
777 Item's itemcallnumber
779 =item C<$data-E<gt>{cn_sort}>
781 Item's call number normalized for sorting
787 sub GetItemsLocationInfo {
788 my $biblionumber = shift;
791 my $dbh = C4::Context->dbh;
792 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
793 location, itemcallnumber, cn_sort
794 FROM items, branches as a, branches as b
795 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
797 ORDER BY cn_sort ASC";
798 my $sth = $dbh->prepare($query);
799 $sth->execute($biblionumber);
801 while ( my $data = $sth->fetchrow_hashref ) {
802 my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
803 $av = $av->count ? $av->next : undef;
804 $data->{location_intranet} = $av ? $av->lib : '';
805 $data->{location_opac} = $av ? $av->opac_description : '';
806 push @results, $data;
811 =head2 GetHostItemsInfo
813 $hostiteminfo = GetHostItemsInfo($hostfield);
814 Returns the iteminfo for items linked to records via a host field
818 sub GetHostItemsInfo {
822 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
823 return @returnitemsInfo;
827 if( C4::Context->preference('marcflavour') eq 'MARC21' ||
828 C4::Context->preference('marcflavour') eq 'NORMARC') {
829 @fields = $record->field('773');
830 } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
831 @fields = $record->field('461');
834 foreach my $hostfield ( @fields ) {
835 my $hostbiblionumber = $hostfield->subfield("0");
836 my $linkeditemnumber = $hostfield->subfield("9");
837 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
838 foreach my $hostitemInfo (@hostitemInfos) {
839 if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
840 push @returnitemsInfo, $hostitemInfo;
845 return @returnitemsInfo;
848 =head2 get_hostitemnumbers_of
850 my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
852 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
854 Return a reference on a hash where key is a biblionumber and values are
855 references on array of itemnumbers.
860 sub get_hostitemnumbers_of {
861 my ($biblionumber) = @_;
863 if( !C4::Context->preference('EasyAnalyticalRecords') ) {
867 my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
868 return unless $marcrecord;
870 my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
872 my $marcflavor = C4::Context->preference('marcflavour');
873 if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
878 elsif ( $marcflavor eq 'UNIMARC' ) {
884 foreach my $hostfield ( $marcrecord->field($tag) ) {
885 my $hostbiblionumber = $hostfield->subfield($biblio_s);
886 next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
887 my $linkeditemnumber = $hostfield->subfield($item_s);
888 if ( ! $linkeditemnumber ) {
889 warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
892 my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
893 push @returnhostitemnumbers, $linkeditemnumber
897 return @returnhostitemnumbers;
900 =head2 GetHiddenItemnumbers
902 my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
904 Given a list of items it checks which should be hidden from the OPAC given
905 the current configuration. Returns a list of itemnumbers corresponding to
906 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
911 sub GetHiddenItemnumbers {
913 my $items = $params->{items};
914 if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
915 foreach my $except (split(/\|/, $exceptions)){
916 if ($params->{'borcat'} eq $except){
917 return; # we don't hide anything for this borrower category
923 my $yaml = C4::Context->preference('OpacHiddenItems');
924 return () if (! $yaml =~ /\S/ );
925 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
928 $hidingrules = YAML::Load($yaml);
931 warn "Unable to parse OpacHiddenItems syspref : $@";
934 my $dbh = C4::Context->dbh;
937 foreach my $item (@$items) {
940 foreach my $field (keys %$hidingrules) {
942 if (exists $item->{$field}) {
943 $val = $item->{$field};
946 my $query = "SELECT $field from items where itemnumber = ?";
947 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
949 $val = '' unless defined $val;
951 # If the results matches the values in the yaml file
952 if (any { $val eq $_ } @{$hidingrules->{$field}}) {
954 # We add the itemnumber to the list
955 push @resultitems, $item->{'itemnumber'};
957 # If at least one rule matched for an item, no need to test the others
965 =head1 LIMITED USE FUNCTIONS
967 The following functions, while part of the public API,
968 are not exported. This is generally because they are
969 meant to be used by only one script for a specific
970 purpose, and should not be used in any other context
971 without careful thought.
977 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
979 Returns MARC::Record of the item passed in parameter.
980 This function is meant for use only in C<cataloguing/additem.pl>,
981 where it is needed to support that script's MARC-like
987 my ( $biblionumber, $itemnumber ) = @_;
989 # GetMarcItem has been revised so that it does the following:
990 # 1. Gets the item information from the items table.
991 # 2. Converts it to a MARC field for storage in the bib record.
993 # The previous behavior was:
994 # 1. Get the bib record.
995 # 2. Return the MARC tag corresponding to the item record.
997 # The difference is that one treats the items row as authoritative,
998 # while the other treats the MARC representation as authoritative
999 # under certain circumstances.
1001 my $item = Koha::Items->find($itemnumber) or return;
1003 # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1004 # Also, don't emit a subfield if the underlying field is blank.
1006 return Item2Marc($item->unblessed, $biblionumber);
1010 my ($itemrecord,$biblionumber)=@_;
1013 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1014 } keys %{ $itemrecord }
1016 my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1017 my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1018 my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1019 "items.itemnumber", $framework,
1022 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1023 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1024 foreach my $field ($itemmarc->field($itemtag)){
1025 $field->add_subfields(@$unlinked_item_subfields);
1031 =head1 PRIVATE FUNCTIONS AND VARIABLES
1033 The following functions are not meant to be called
1034 directly, but are documented in order to explain
1035 the inner workings of C<C4::Items>.
1039 =head2 MoveItemFromBiblio
1041 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1043 Moves an item from a biblio to another
1045 Returns undef if the move failed or the biblionumber of the destination record otherwise
1049 sub MoveItemFromBiblio {
1050 my ($itemnumber, $frombiblio, $tobiblio) = @_;
1051 my $dbh = C4::Context->dbh;
1052 my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1053 SELECT biblioitemnumber
1055 WHERE biblionumber = ?
1056 |, undef, $tobiblio );
1057 my $return = $dbh->do(q|
1059 SET biblioitemnumber = ?,
1061 WHERE itemnumber = ?
1062 AND biblionumber = ?
1063 |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1065 ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1066 ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1067 # Checking if the item we want to move is in an order
1068 require C4::Acquisition;
1069 my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1071 # Replacing the biblionumber within the order if necessary
1072 $order->{'biblionumber'} = $tobiblio;
1073 C4::Acquisition::ModOrder($order);
1076 # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1077 for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1080 SET biblionumber = ?
1081 WHERE itemnumber = ?
1082 |, undef, $tobiblio, $itemnumber );
1089 =head2 _marc_from_item_hash
1091 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1093 Given an item hash representing a complete item record,
1094 create a C<MARC::Record> object containing an embedded
1095 tag representing that item.
1097 The third, optional parameter C<$unlinked_item_subfields> is
1098 an arrayref of subfields (not mapped to C<items> fields per the
1099 framework) to be added to the MARC representation
1104 sub _marc_from_item_hash {
1106 my $frameworkcode = shift;
1107 my $unlinked_item_subfields;
1109 $unlinked_item_subfields = shift;
1112 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1113 # Also, don't emit a subfield if the underlying field is blank.
1114 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
1115 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
1116 : () } keys %{ $item } };
1118 my $item_marc = MARC::Record->new();
1119 foreach my $item_field ( keys %{$mungeditem} ) {
1120 my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1121 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1122 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1123 foreach my $value (@values){
1124 if ( my $field = $item_marc->field($tag) ) {
1125 $field->add_subfields( $subfield => $value );
1127 my $add_subfields = [];
1128 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1129 $add_subfields = $unlinked_item_subfields;
1131 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1139 =head2 _repack_item_errors
1141 Add an error message hash generated by C<CheckItemPreSave>
1142 to a list of errors.
1146 sub _repack_item_errors {
1147 my $item_sequence_num = shift;
1148 my $item_ref = shift;
1149 my $error_ref = shift;
1151 my @repacked_errors = ();
1153 foreach my $error_code (sort keys %{ $error_ref }) {
1154 my $repacked_error = {};
1155 $repacked_error->{'item_sequence'} = $item_sequence_num;
1156 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1157 $repacked_error->{'error_code'} = $error_code;
1158 $repacked_error->{'error_information'} = $error_ref->{$error_code};
1159 push @repacked_errors, $repacked_error;
1162 return @repacked_errors;
1165 =head2 _get_unlinked_item_subfields
1167 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1171 sub _get_unlinked_item_subfields {
1172 my $original_item_marc = shift;
1173 my $frameworkcode = shift;
1175 my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1177 # assume that this record has only one field, and that that
1178 # field contains only the item information
1180 my @fields = $original_item_marc->fields();
1181 if ($#fields > -1) {
1182 my $field = $fields[0];
1183 my $tag = $field->tag();
1184 foreach my $subfield ($field->subfields()) {
1185 if (defined $subfield->[1] and
1186 $subfield->[1] ne '' and
1187 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1188 push @$subfields, $subfield->[0] => $subfield->[1];
1195 =head2 _get_unlinked_subfields_xml
1197 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1201 sub _get_unlinked_subfields_xml {
1202 my $unlinked_item_subfields = shift;
1205 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1206 my $marc = MARC::Record->new();
1207 # use of tag 999 is arbitrary, and doesn't need to match the item tag
1208 # used in the framework
1209 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1210 $marc->encoding("UTF-8");
1211 $xml = $marc->as_xml("USMARC");
1217 =head2 _parse_unlinked_item_subfields_from_xml
1219 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1223 sub _parse_unlinked_item_subfields_from_xml {
1225 require C4::Charset;
1226 return unless defined $xml and $xml ne "";
1227 my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1228 my $unlinked_subfields = [];
1229 my @fields = $marc->fields();
1230 if ($#fields > -1) {
1231 foreach my $subfield ($fields[0]->subfields()) {
1232 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1235 return $unlinked_subfields;
1238 =head2 GetAnalyticsCount
1240 $count= &GetAnalyticsCount($itemnumber)
1242 counts Usage of itemnumber in Analytical bibliorecords.
1246 sub GetAnalyticsCount {
1247 my ($itemnumber) = @_;
1249 ### ZOOM search here
1251 $query= "hi=".$itemnumber;
1252 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1253 my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1257 sub _SearchItems_build_where_fragment {
1260 my $dbh = C4::Context->dbh;
1263 if (exists($filter->{conjunction})) {
1264 my (@where_strs, @where_args);
1265 foreach my $f (@{ $filter->{filters} }) {
1266 my $fragment = _SearchItems_build_where_fragment($f);
1268 push @where_strs, $fragment->{str};
1269 push @where_args, @{ $fragment->{args} };
1274 $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1277 args => \@where_args,
1281 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1282 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1283 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1284 my @operators = qw(= != > < >= <= like);
1285 my $field = $filter->{field} // q{};
1286 if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1287 my $op = $filter->{operator};
1288 my $query = $filter->{query};
1290 if (!$op or (0 == grep { $_ eq $op } @operators)) {
1291 $op = '='; # default operator
1295 if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1297 my $marcsubfield = $2;
1298 my ($kohafield) = $dbh->selectrow_array(q|
1299 SELECT kohafield FROM marc_subfield_structure
1300 WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1301 |, undef, $marcfield, $marcsubfield);
1304 $column = $kohafield;
1306 # MARC field is not linked to a DB field so we need to use
1307 # ExtractValue on marcxml from biblio_metadata or
1308 # items.more_subfields_xml, depending on the MARC field.
1311 my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1312 if ($marcfield eq $itemfield) {
1313 $sqlfield = 'more_subfields_xml';
1314 $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1316 $sqlfield = 'metadata'; # From biblio_metadata
1317 if ($marcfield < 10) {
1318 $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1320 $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1323 $column = "ExtractValue($sqlfield, '$xpath')";
1325 } elsif ($field eq 'issues') {
1326 # Consider NULL as 0 for issues count
1327 $column = 'COALESCE(issues,0)';
1332 if (ref $query eq 'ARRAY') {
1335 } elsif ($op eq '!=') {
1339 str => "$column $op (" . join (',', ('?') x @$query) . ")",
1344 str => "$column $op ?",
1351 return $where_fragment;
1356 my ($items, $total) = SearchItems($filter, $params);
1358 Perform a search among items
1360 $filter is a reference to a hash which can be a filter, or a combination of filters.
1362 A filter has the following keys:
1366 =item * field: the name of a SQL column in table items
1368 =item * query: the value to search in this column
1370 =item * operator: comparison operator. Can be one of = != > < >= <= like
1374 A combination of filters hash the following keys:
1378 =item * conjunction: 'AND' or 'OR'
1380 =item * filters: array ref of filters
1384 $params is a reference to a hash that can contain the following parameters:
1388 =item * rows: Number of items to return. 0 returns everything (default: 0)
1390 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1393 =item * sortby: A SQL column name in items table to sort on
1395 =item * sortorder: 'ASC' or 'DESC'
1402 my ($filter, $params) = @_;
1406 return unless ref $filter eq 'HASH';
1407 return unless ref $params eq 'HASH';
1409 # Default parameters
1410 $params->{rows} ||= 0;
1411 $params->{page} ||= 1;
1412 $params->{sortby} ||= 'itemnumber';
1413 $params->{sortorder} ||= 'ASC';
1415 my ($where_str, @where_args);
1416 my $where_fragment = _SearchItems_build_where_fragment($filter);
1417 if ($where_fragment) {
1418 $where_str = $where_fragment->{str};
1419 @where_args = @{ $where_fragment->{args} };
1422 my $dbh = C4::Context->dbh;
1424 SELECT SQL_CALC_FOUND_ROWS items.*
1426 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1427 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1428 LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1431 if (defined $where_str and $where_str ne '') {
1432 $query .= qq{ AND $where_str };
1435 $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1436 push @where_args, C4::Context->preference('marcflavour');
1438 my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1439 push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1440 push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1441 my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1442 ? $params->{sortby} : 'itemnumber';
1443 my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1444 $query .= qq{ ORDER BY $sortby $sortorder };
1446 my $rows = $params->{rows};
1449 my $offset = $rows * ($params->{page}-1);
1450 $query .= qq { LIMIT ?, ? };
1451 push @limit_args, $offset, $rows;
1454 my $sth = $dbh->prepare($query);
1455 my $rv = $sth->execute(@where_args, @limit_args);
1457 return unless ($rv);
1458 my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1460 return ($sth->fetchall_arrayref({}), $total_rows);
1464 =head1 OTHER FUNCTIONS
1468 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1470 Find the given $subfield in the given $tag in the given
1471 MARC::Record $record. If the subfield is found, returns
1472 the (indicators, value) pair; otherwise, (undef, undef) is
1476 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1477 I suggest we export it from this module.
1482 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1485 if ( $tagfield < 10 ) {
1486 if ( $record->field($tagfield) ) {
1487 push @result, $record->field($tagfield)->data();
1492 foreach my $field ( $record->field($tagfield) ) {
1493 my @subfields = $field->subfields();
1494 foreach my $subfield (@subfields) {
1495 if ( @$subfield[0] eq $insubfield ) {
1496 push @result, @$subfield[1];
1497 $indicator = $field->indicator(1) . $field->indicator(2);
1502 return ( $indicator, @result );
1506 =head2 PrepareItemrecordDisplay
1508 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1510 Returns a hash with all the fields for Display a given item data in a template
1512 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1516 sub PrepareItemrecordDisplay {
1518 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1520 my $dbh = C4::Context->dbh;
1521 $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1522 my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1524 # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1525 # a shared data structure. No plugin (including custom ones) should change
1526 # its contents. See also GetMarcStructure.
1527 my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1529 # return nothing if we don't have found an existing framework.
1530 return q{} unless $tagslib;
1533 $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1537 my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1539 SELECT authorised_value,lib FROM authorised_values
1542 LEFT JOIN authorised_values_branches ON ( id = av_id )
1547 $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1548 $query .= qq{ ORDER BY lib};
1549 my $authorised_values_sth = $dbh->prepare( $query );
1550 foreach my $tag ( sort keys %{$tagslib} ) {
1553 # loop through each subfield
1555 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1556 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1557 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1558 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1560 $subfield_data{tag} = $tag;
1561 $subfield_data{subfield} = $subfield;
1562 $subfield_data{countsubfield} = $cntsubf++;
1563 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1564 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1566 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1567 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
1568 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
1569 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1570 $subfield_data{hidden} = "display:none"
1571 if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1572 || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1573 my ( $x, $defaultvalue );
1575 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1577 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1578 if ( !defined $defaultvalue ) {
1579 $defaultvalue = q||;
1581 $defaultvalue =~ s/"/"/g;
1584 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1586 # search for itemcallnumber if applicable
1587 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1588 && C4::Context->preference('itemcallnumber') && $itemrecord) {
1589 foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1590 my $CNtag = substr( $itemcn_pref, 0, 3 );
1591 next unless my $field = $itemrecord->field($CNtag);
1592 my $CNsubfields = substr( $itemcn_pref, 3 );
1593 $defaultvalue = $field->as_string( $CNsubfields, ' ');
1594 last if $defaultvalue;
1597 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1599 && $defaultvalues->{'callnumber'} ) {
1600 if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1601 # if the item record exists, only use default value if the item has no callnumber
1602 $defaultvalue = $defaultvalues->{callnumber};
1603 } elsif ( !$itemrecord and $defaultvalues ) {
1604 # if the item record *doesn't* exists, always use the default value
1605 $defaultvalue = $defaultvalues->{callnumber};
1608 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1610 && $defaultvalues->{'branchcode'} ) {
1611 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1612 $defaultvalue = $defaultvalues->{branchcode};
1615 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1617 && $defaultvalues->{'location'} ) {
1619 if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1620 # if the item record exists, only use default value if the item has no locationr
1621 $defaultvalue = $defaultvalues->{location};
1622 } elsif ( !$itemrecord and $defaultvalues ) {
1623 # if the item record *doesn't* exists, always use the default value
1624 $defaultvalue = $defaultvalues->{location};
1627 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1628 my @authorised_values;
1631 # builds list, depending on authorised value...
1633 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1634 if ( ( C4::Context->preference("IndependentBranches") )
1635 && !C4::Context->IsSuperLibrarian() ) {
1636 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1637 $sth->execute( C4::Context->userenv->{branch} );
1638 push @authorised_values, ""
1639 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1640 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1641 push @authorised_values, $branchcode;
1642 $authorised_lib{$branchcode} = $branchname;
1645 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1647 push @authorised_values, ""
1648 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1649 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1650 push @authorised_values, $branchcode;
1651 $authorised_lib{$branchcode} = $branchname;
1655 $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1656 if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1657 $defaultvalue = $defaultvalues->{branchcode};
1661 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1662 my $itemtypes = Koha::ItemTypes->search_with_localization;
1663 push @authorised_values, ""
1664 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1665 while ( my $itemtype = $itemtypes->next ) {
1666 push @authorised_values, $itemtype->itemtype;
1667 $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1669 if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1670 $defaultvalue = $defaultvalues->{'itemtype'};
1674 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1675 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1677 my $class_sources = GetClassSources();
1678 my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1680 foreach my $class_source (sort keys %$class_sources) {
1681 next unless $class_sources->{$class_source}->{'used'} or
1682 ($class_source eq $default_source);
1683 push @authorised_values, $class_source;
1684 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1687 $defaultvalue = $default_source;
1689 #---- "true" authorised value
1691 $authorised_values_sth->execute(
1692 $tagslib->{$tag}->{$subfield}->{authorised_value},
1693 $branch_limit ? $branch_limit : ()
1695 push @authorised_values, ""
1696 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1697 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1698 push @authorised_values, $value;
1699 $authorised_lib{$value} = $lib;
1702 $subfield_data{marc_value} = {
1704 values => \@authorised_values,
1705 default => $defaultvalue // q{},
1706 labels => \%authorised_lib,
1708 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1710 require Koha::FrameworkPlugin;
1711 my $plugin = Koha::FrameworkPlugin->new({
1712 name => $tagslib->{$tag}->{$subfield}->{value_builder},
1715 my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1716 $plugin->build( $pars );
1717 if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1718 $defaultvalue = $field->subfield($subfield) || q{};
1720 if( !$plugin->errstr ) {
1721 #TODO Move html to template; see report 12176/13397
1722 my $tab= $plugin->noclick? '-1': '';
1723 my $class= $plugin->noclick? ' disabled': '';
1724 my $title= $plugin->noclick? 'No popup': 'Tag editor';
1725 $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;
1727 warn $plugin->errstr;
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" />); # supply default input form
1731 elsif ( $tag eq '' ) { # it's an hidden field
1732 $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1734 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
1735 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1737 elsif ( length($defaultvalue) > 100
1738 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1739 300 <= $tag && $tag < 400 && $subfield eq 'a' )
1740 or (C4::Context->preference("marcflavour") eq "MARC21" and
1741 500 <= $tag && $tag < 600 )
1743 # oversize field (textarea)
1744 $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1746 $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1748 push( @loop_data, \%subfield_data );
1753 if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1754 $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1757 'itemtagfield' => $itemtagfield,
1758 'itemtagsubfield' => $itemtagsubfield,
1759 'itemnumber' => $itemnumber,
1760 'iteminformation' => \@loop_data
1764 sub ToggleNewStatus {
1765 my ( $params ) = @_;
1766 my @rules = @{ $params->{rules} };
1767 my $report_only = $params->{report_only};
1769 my $dbh = C4::Context->dbh;
1771 my @item_columns = map { "items.$_" } Koha::Items->columns;
1772 my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1774 for my $rule ( @rules ) {
1775 my $age = $rule->{age};
1776 my $conditions = $rule->{conditions};
1777 my $substitutions = $rule->{substitutions};
1778 foreach ( @$substitutions ) {
1779 ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1786 LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1789 for my $condition ( @$conditions ) {
1791 grep { $_ eq $condition->{field} } @item_columns
1792 or grep { $_ eq $condition->{field} } @biblioitem_columns
1794 if ( $condition->{value} =~ /\|/ ) {
1795 my @values = split /\|/, $condition->{value};
1796 $query .= qq| AND $condition->{field} IN (|
1797 . join( ',', ('?') x scalar @values )
1799 push @params, @values;
1801 $query .= qq| AND $condition->{field} = ?|;
1802 push @params, $condition->{value};
1806 if ( defined $age ) {
1807 $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1810 my $sth = $dbh->prepare($query);
1811 $sth->execute( @params );
1812 while ( my $values = $sth->fetchrow_hashref ) {
1813 my $biblionumber = $values->{biblionumber};
1814 my $itemnumber = $values->{itemnumber};
1815 my $item = Koha::Items->find($itemnumber);
1816 for my $substitution ( @$substitutions ) {
1817 my $field = $substitution->{item_field};
1818 my $value = $substitution->{value};
1819 next unless $substitution->{field};
1820 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1821 $item->$field($value);
1822 push @{ $report->{$itemnumber} }, $substitution;
1824 $item->store unless $report_only;