X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FItems.pm;h=817e86e9405c49a27d47c0d80c13f16e2e40a8e5;hb=2a1d85fc32a8b0df446bc32e92cbc38c3190ce7e;hp=a162ac5dec163bd3de6cec3cade76d033744eacb;hpb=abc00eb1cfd1ee018396d6df04e6f28d7b363dbb;p=koha-ffzg.git diff --git a/C4/Items.pm b/C4/Items.pm index a162ac5dec..817e86e940 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -20,12 +20,12 @@ package C4::Items; use Modern::Perl; -use vars qw(@ISA @EXPORT); +our (@ISA, @EXPORT_OK); BEGIN { require Exporter; @ISA = qw(Exporter); - @EXPORT = qw( + @EXPORT_OK = qw( AddItemFromMarc AddItemBatchFromMarc ModItemFromMarc @@ -34,42 +34,37 @@ BEGIN { ModItemTransfer CheckItemPreSave GetItemsForInventory - GetItemsInfo - GetItemsLocationInfo - GetHostItemsInfo get_hostitemnumbers_of - GetHiddenItemnumbers - MoveItemFromBiblio + GetMarcItem CartToShelf GetAnalyticsCount SearchItems PrepareItemrecordDisplay + ToggleNewStatus ); } -use Carp; -use Try::Tiny; +use Carp qw( croak ); use C4::Context; use C4::Koha; -use C4::Biblio; -use Koha::DateUtils; +use C4::Biblio qw( GetMarcStructure TransformMarcToKoha ); use MARC::Record; -use C4::ClassSource; -use C4::Log; -use List::MoreUtils qw(any); -use YAML qw(Load); +use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource ); +use C4::Log qw( logaction ); +use List::MoreUtils qw( any ); use DateTime::Format::MySQL; -use Data::Dumper; # used as part of logging item record changes, not just for # debugging; so please don't remove this use Koha::AuthorisedValues; -use Koha::DateUtils qw(dt_from_string); +use Koha::DateUtils qw( dt_from_string ); use Koha::Database; +use Koha::Biblios; use Koha::Biblioitems; use Koha::Items; use Koha::ItemTypes; use Koha::SearchEngine; +use Koha::SearchEngine::Indexer; use Koha::SearchEngine::Search; use Koha::Libraries; @@ -142,14 +137,18 @@ sub CartToShelf { Given a MARC::Record object containing an embedded item record and a biblionumber, create a new item record. -The final optional parameter, C<$params>, expected to contain -'skip_modzebra_update' key, which relayed down to Koha::Item/store, -there it prevents calling of ModZebra (and Elasticsearch update), -which takes most of the time in batch adds/deletes: ModZebra better +The final optional parameter, C<$params>, may contain +'skip_record_index' key, which relayed down to Koha::Item/store, +there it prevents calling of index_records, +which takes most of the time in batch adds/deletes: index_records to be called later in C after the whole loop. +You may also optionally pass biblioitemnumber in the params hash to +boost performance of inserts by preventing a lookup in Koha::Item. + $params: - skip_modzebra_update => 1|0 + skip_record_index => 1|0 + biblioitemnumber => $biblioitemnumber =cut @@ -163,17 +162,17 @@ sub AddItemFromMarc { # parse item hash from MARC my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber); my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" ); - my $localitemmarc = MARC::Record->new; $localitemmarc->append_fields( $source_item_marc->field($itemtag) ); - my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' ); + my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' }); my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode ); $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields); $item_values->{biblionumber} = $biblionumber; + $item_values->{biblioitemnumber} = $params->{biblioitemnumber}; $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate - my $item = Koha::Item->new( $item_values )->store({ skip_modzebra_update => $params->{skip_modzebra_update} }); + my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} }); return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber ); } @@ -224,7 +223,6 @@ Additional information appropriate to the error condition. sub AddItemBatchFromMarc { my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_; - my $error; my @itemnumbers = (); my @errors = (); my $dbh = C4::Context->dbh; @@ -246,7 +244,7 @@ sub AddItemBatchFromMarc { $temp_item_marc->append_fields($item_field); # add biblionumber and biblioitemnumber - my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' ); + my $item = TransformMarcToKoha({ record => $temp_item_marc, limit_table => 'items' }); my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode); $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields); $item->{'biblionumber'} = $biblionumber; @@ -276,16 +274,26 @@ sub AddItemBatchFromMarc { $record->delete_field($item_field); } - # update the MARC biblio - # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode ); - return (\@itemnumbers, \@errors); } +=head2 ModItemFromMarc + +my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]); + +The final optional parameter, C<$params>, expected to contain +'skip_record_index' key, which relayed down to Koha::Item/store, +there it prevents calling of index_records, +which takes most of the time in batch adds/deletes: index_records better +to be called later in C after the whole loop. + +$params: + skip_record_index => 1|0 + +=cut + sub ModItemFromMarc { - my $item_marc = shift; - my $biblionumber = shift; - my $itemnumber = shift; + my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_; my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber); my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" ); @@ -293,74 +301,113 @@ sub ModItemFromMarc { my $localitemmarc = MARC::Record->new; $localitemmarc->append_fields( $item_marc->field($itemtag) ); my $item_object = Koha::Items->find($itemnumber); - my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' ); + my $item = TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' }); + + # When importing items we blank this column, we need to set it to the existing value + # to prevent it being blanked by set_or_blank + $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} ); + + my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" ); + my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield ); + + # Retrieving the values for the fields that are not linked + my @mapped_fields = Koha::MarcSubfieldStructures->search( + { + frameworkcode => $frameworkcode, + kohafield => { -like => "items.%" } + } + )->get_column('kohafield'); + for my $c ( $item_object->_result->result_source->columns ) { + next if grep { "items.$c" eq $_ } @mapped_fields; + $item->{$c} = $item_object->$c; + } + $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate - $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate + delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate $item->{itemnumber} = $itemnumber; $item->{biblionumber} = $biblionumber; + + my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it + # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified $item_object = $item_object->set_or_blank($item); + $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value + + $item_object->make_column_dirty('permanent_location') if $has_permanent_location; + my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode ); $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields)); - $item_object->store; + $item_object->store({ skip_record_index => $params->{skip_record_index} }); return $item_object->unblessed; } =head2 ModItemTransfer - ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger); + ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]); Marks an item as being transferred from one branch to another and records the trigger. +The last optional parameter allows for passing skip_record_index through to the items store call. + =cut sub ModItemTransfer { - my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_; + my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_; my $dbh = C4::Context->dbh; my $item = Koha::Items->find( $itemnumber ); - # Remove the 'shelving cart' location status if it is being used. - CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' ); - - $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber); + # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits + # and always replacing any existing transfers. (In theory, calls to ModItemTransfer + # will have been preceded by a check of branch transfer limits) + my $to_library = Koha::Libraries->find($tobranch); + my $transfer = $item->request_transfer( + { + to => $to_library, + reason => $trigger, + ignore_limits => 1, + replace => 1 + } + ); - #new entry in branchtransfers.... - my $sth = $dbh->prepare( - "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason) - VALUES (?, ?, NOW(), ?, ?)"); - $sth->execute($itemnumber, $frombranch, $tobranch, $trigger); + # Immediately set the item to in transit if it is checked in + if ( !$item->checkout ) { + $item->holdingbranch($frombranch)->store( + { + log_action => 0, + skip_record_index => $params->{skip_record_index} + } + ); + $transfer->transit; + } - # FIXME we are fetching the item twice in the 2 next statements! - Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 }); - ModDateLastSeen($itemnumber); return; } =head2 ModDateLastSeen -ModDateLastSeen( $itemnumber, $leave_item_lost ); +ModDateLastSeen( $itemnumber, $leave_item_lost, $params ); Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking. C<$itemnumber> is the item number C<$leave_item_lost> determines if a lost item will be found or remain lost +The last optional parameter allows for passing skip_record_index through to the items store call. + =cut sub ModDateLastSeen { - my ( $itemnumber, $leave_item_lost ) = @_; - - my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); + my ( $itemnumber, $leave_item_lost, $params ) = @_; my $item = Koha::Items->find($itemnumber); - $item->datelastseen($today); + $item->datelastseen(dt_from_string); $item->itemlost(0) unless $leave_item_lost; - $item->store({ log_action => 0 }); + $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} }); } =head2 CheckItemPreSave - my $item_ref = TransformMarcToKoha($marc, 'items'); + my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' }); # do stuff my %errors = CheckItemPreSave($item_ref); if (exists $errors{'duplicate_barcode'}) { @@ -450,7 +497,6 @@ lists of authorized values for certain item fields. minlocation => $minlocation, maxlocation => $maxlocation, location => $location, - itemtype => $itemtype, ignoreissued => $ignoreissued, datelastseen => $datelastseen, branchcode => $branchcode, @@ -458,6 +504,7 @@ lists of authorized values for certain item fields. offset => $offset, size => $size, statushash => $statushash, + itemtypes => \@itemsarray, } ); Retrieve a list of title/authors/barcode/callnumber, for biblio inventory. @@ -490,6 +537,8 @@ sub GetItemsForInventory { my $size = $parameters->{'size'} // ''; my $statushash = $parameters->{'statushash'} // ''; my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // ''; + my $itemtypes = $parameters->{'itemtypes'} || []; + my $ccode = $parameters->{'ccode'} // ''; my $dbh = C4::Context->dbh; my ( @bind_params, @where_strings ); @@ -498,7 +547,8 @@ sub GetItemsForInventory { my $max_cnsort = GetClassSort($class_source,undef,$maxlocation); my $select_columns = q{ - SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort + SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode + }; my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))}; my $query = q{ @@ -515,6 +565,11 @@ sub GetItemsForInventory { } } + if ($ccode){ + push @where_strings, 'ccode = ?'; + push @bind_params, $ccode; + } + if ($minlocation) { push @where_strings, 'items.cn_sort >= ?'; push @bind_params, $min_cnsort; @@ -526,7 +581,6 @@ sub GetItemsForInventory { } if ($datelastseen) { - $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 }); push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)'; push @bind_params, $datelastseen; } @@ -549,7 +603,6 @@ sub GetItemsForInventory { push @where_strings, 'biblioitems.itemtype = ?'; push @bind_params, $itemtype; } - if ( $ignoreissued) { $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber "; push @where_strings, 'issues.date_due IS NULL'; @@ -560,6 +613,11 @@ sub GetItemsForInventory { push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} ); } + if ( @$itemtypes ) { + my $itemtypes_str = join ', ', @$itemtypes; + push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )"; + } + if ( @where_strings ) { $query .= 'WHERE '; $query .= join ' AND ', @where_strings; @@ -586,7 +644,7 @@ sub GetItemsForInventory { '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ], '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ], } - ); + )->as_list; my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs }; @@ -608,263 +666,6 @@ sub GetItemsForInventory { return (\@results, $iTotalRecords); } -=head2 GetItemsInfo - - @results = GetItemsInfo($biblionumber); - -Returns information about items with the given biblionumber. - -C returns a list of references-to-hash. Each element -contains a number of keys. Most of them are attributes from the -C, C, C, and C tables in the -Koha database. Other keys include: - -=over 2 - -=item C<$data-E{branchname}> - -The name (not the code) of the branch to which the book belongs. - -=item C<$data-E{datelastseen}> - -This is simply C, except that while the date is -stored in YYYY-MM-DD format in the database, here it is converted to -DD/MM/YYYY format. A NULL date is returned as C. - -=item C<$data-E{datedue}> - -=item C<$data-E{class}> - -This is the concatenation of C, the book's -Dewey code, and C. - -=item C<$data-E{ocount}> - -I think this is the number of copies of the book available. - -=item C<$data-E{order}> - -If this is set, it is set to C. - -=back - -=cut - -sub GetItemsInfo { - my ( $biblionumber ) = @_; - my $dbh = C4::Context->dbh; - require C4::Languages; - my $language = C4::Languages::getlanguage(); - my $query = " - SELECT items.*, - biblio.*, - biblioitems.volume, - biblioitems.number, - biblioitems.itemtype, - biblioitems.isbn, - biblioitems.issn, - biblioitems.publicationyear, - biblioitems.publishercode, - biblioitems.volumedate, - biblioitems.volumedesc, - biblioitems.lccn, - biblioitems.url, - items.notforloan as itemnotforloan, - issues.borrowernumber, - issues.date_due as datedue, - issues.onsite_checkout, - borrowers.cardnumber, - borrowers.surname, - borrowers.firstname, - borrowers.branchcode as bcode, - serial.serialseq, - serial.publisheddate, - itemtypes.description, - COALESCE( localization.translation, itemtypes.description ) AS translated_description, - itemtypes.notforloan as notforloan_per_itemtype, - holding.branchurl, - holding.branchcode, - holding.branchname, - holding.opac_info as holding_branch_opac_info, - home.opac_info as home_branch_opac_info, - IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold - FROM items - LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode - LEFT JOIN branches AS home ON items.homebranch=home.branchcode - LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber - LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber - LEFT JOIN issues USING (itemnumber) - LEFT JOIN borrowers USING (borrowernumber) - LEFT JOIN serialitems USING (itemnumber) - LEFT JOIN serial USING (serialid) - LEFT JOIN itemtypes ON itemtypes.itemtype = " - . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype'); - $query .= q| - LEFT JOIN tmp_holdsqueue USING (itemnumber) - LEFT JOIN localization ON itemtypes.itemtype = localization.code - AND localization.entity = 'itemtypes' - AND localization.lang = ? - |; - - $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ; - my $sth = $dbh->prepare($query); - $sth->execute($language, $biblionumber); - my $i = 0; - my @results; - my $serial; - - my $userenv = C4::Context->userenv; - my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian(); - while ( my $data = $sth->fetchrow_hashref ) { - if ( $data->{borrowernumber} && $want_not_same_branch) { - $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch}; - } - - $serial ||= $data->{'serial'}; - - my $descriptions; - # get notforloan complete status if applicable - $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} }); - $data->{notforloanvalue} = $descriptions->{lib} // ''; - $data->{notforloanvalueopac} = $descriptions->{opac_description} // ''; - - # get restricted status and description if applicable - $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} }); - $data->{restrictedvalue} = $descriptions->{lib} // ''; - $data->{restrictedvalueopac} = $descriptions->{opac_description} // ''; - - # my stack procedures - $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} }); - $data->{stack} = $descriptions->{lib} // ''; - - # Find the last 3 people who borrowed this item. - my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers - WHERE itemnumber = ? - AND old_issues.borrowernumber = borrowers.borrowernumber - ORDER BY returndate DESC - LIMIT 3"); - $sth2->execute($data->{'itemnumber'}); - my $ii = 0; - while (my $data2 = $sth2->fetchrow_hashref()) { - $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'}; - $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'}; - $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'}; - $ii++; - } - - $results[$i] = $data; - $i++; - } - - return $serial - ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results - : @results; -} - -=head2 GetItemsLocationInfo - - my @itemlocinfo = GetItemsLocationInfo($biblionumber); - -Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question - -C returns a list of references-to-hash. Data returned: - -=over 2 - -=item C<$data-E{homebranch}> - -Branch Name of the item's homebranch - -=item C<$data-E{holdingbranch}> - -Branch Name of the item's holdingbranch - -=item C<$data-E{location}> - -Item's shelving location code - -=item C<$data-E{location_intranet}> - -The intranet description for the Shelving Location as set in authorised_values 'LOC' - -=item C<$data-E{location_opac}> - -The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC -description is set. - -=item C<$data-E{itemcallnumber}> - -Item's itemcallnumber - -=item C<$data-E{cn_sort}> - -Item's call number normalized for sorting - -=back - -=cut - -sub GetItemsLocationInfo { - my $biblionumber = shift; - my @results; - - my $dbh = C4::Context->dbh; - my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, - location, itemcallnumber, cn_sort - FROM items, branches as a, branches as b - WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode - AND biblionumber = ? - ORDER BY cn_sort ASC"; - my $sth = $dbh->prepare($query); - $sth->execute($biblionumber); - - while ( my $data = $sth->fetchrow_hashref ) { - my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} }); - $av = $av->count ? $av->next : undef; - $data->{location_intranet} = $av ? $av->lib : ''; - $data->{location_opac} = $av ? $av->opac_description : ''; - push @results, $data; - } - return @results; -} - -=head2 GetHostItemsInfo - - $hostiteminfo = GetHostItemsInfo($hostfield); - Returns the iteminfo for items linked to records via a host field - -=cut - -sub GetHostItemsInfo { - my ($record) = @_; - my @returnitemsInfo; - - if( !C4::Context->preference('EasyAnalyticalRecords') ) { - return @returnitemsInfo; - } - - my @fields; - if( C4::Context->preference('marcflavour') eq 'MARC21' || - C4::Context->preference('marcflavour') eq 'NORMARC') { - @fields = $record->field('773'); - } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') { - @fields = $record->field('461'); - } - - foreach my $hostfield ( @fields ) { - my $hostbiblionumber = $hostfield->subfield("0"); - my $linkeditemnumber = $hostfield->subfield("9"); - my @hostitemInfos = GetItemsInfo($hostbiblionumber); - foreach my $hostitemInfo (@hostitemInfos) { - if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) { - push @returnitemsInfo, $hostitemInfo; - last; - } - } - } - return @returnitemsInfo; -} - =head2 get_hostitemnumbers_of my @itemnumbers_of = get_hostitemnumbers_of($biblionumber); @@ -884,13 +685,14 @@ sub get_hostitemnumbers_of { return (); } - my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber }); + my $biblio = Koha::Biblios->find($biblionumber); + my $marcrecord = $biblio->metadata->record; return unless $marcrecord; my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s ); my $marcflavor = C4::Context->preference('marcflavour'); - if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) { + if ( $marcflavor eq 'MARC21' ) { $tag = '773'; $biblio_s = '0'; $item_s = '9'; @@ -917,71 +719,6 @@ sub get_hostitemnumbers_of { return @returnhostitemnumbers; } -=head2 GetHiddenItemnumbers - - my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category }); - -Given a list of items it checks which should be hidden from the OPAC given -the current configuration. Returns a list of itemnumbers corresponding to -those that should be hidden. Optionally takes a borcat parameter for certain borrower types -to be excluded - -=cut - -sub GetHiddenItemnumbers { - my $params = shift; - my $items = $params->{items}; - if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){ - foreach my $except (split(/\|/, $exceptions)){ - if ($params->{'borcat'} eq $except){ - return; # we don't hide anything for this borrower category - } - } - } - my @resultitems; - - my $yaml = C4::Context->preference('OpacHiddenItems'); - return () if (! $yaml =~ /\S/ ); - $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt - my $hidingrules; - eval { - $hidingrules = YAML::Load($yaml); - }; - if ($@) { - warn "Unable to parse OpacHiddenItems syspref : $@"; - return (); - } - my $dbh = C4::Context->dbh; - - # For each item - foreach my $item (@$items) { - - # We check each rule - foreach my $field (keys %$hidingrules) { - my $val; - if (exists $item->{$field}) { - $val = $item->{$field}; - } - else { - my $query = "SELECT $field from items where itemnumber = ?"; - $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'}); - } - $val = '' unless defined $val; - - # If the results matches the values in the yaml file - if (any { $val eq $_ } @{$hidingrules->{$field}}) { - - # We add the itemnumber to the list - push @resultitems, $item->{'itemnumber'}; - - # If at least one rule matched for an item, no need to test the others - last; - } - } - } - return @resultitems; -} - =head1 LIMITED USE FUNCTIONS The following functions, while part of the public API, @@ -1056,56 +793,6 @@ the inner workings of C. =cut -=head2 MoveItemFromBiblio - - MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio); - -Moves an item from a biblio to another - -Returns undef if the move failed or the biblionumber of the destination record otherwise - -=cut - -sub MoveItemFromBiblio { - my ($itemnumber, $frombiblio, $tobiblio) = @_; - my $dbh = C4::Context->dbh; - my ( $tobiblioitem ) = $dbh->selectrow_array(q| - SELECT biblioitemnumber - FROM biblioitems - WHERE biblionumber = ? - |, undef, $tobiblio ); - my $return = $dbh->do(q| - UPDATE items - SET biblioitemnumber = ?, - biblionumber = ? - WHERE itemnumber = ? - AND biblionumber = ? - |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio ); - if ($return == 1) { - ModZebra( $tobiblio, "specialUpdate", "biblioserver" ); - ModZebra( $frombiblio, "specialUpdate", "biblioserver" ); - # Checking if the item we want to move is in an order - require C4::Acquisition; - my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber); - if ($order) { - # Replacing the biblionumber within the order if necessary - $order->{'biblionumber'} = $tobiblio; - C4::Acquisition::ModOrder($order); - } - - # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables - for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) { - $dbh->do( qq| - UPDATE $table_name - SET biblionumber = ? - WHERE itemnumber = ? - |, undef, $tobiblio, $itemnumber ); - } - return $tobiblio; - } - return; -} - =head2 _marc_from_item_hash my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]); @@ -1266,6 +953,10 @@ counts Usage of itemnumber in Analytical bibliorecords. sub GetAnalyticsCount { my ($itemnumber) = @_; + if ( !C4::Context->preference('EasyAnalyticalRecords') ) { + return 0; + } + ### ZOOM search here my $query; $query= "hi=".$itemnumber; @@ -1301,11 +992,14 @@ sub _SearchItems_build_where_fragment { my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns; - my @operators = qw(= != > < >= <= like); + push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns; + my @operators = qw(= != > < >= <= is like); + push @operators, 'not like'; my $field = $filter->{field} // q{}; if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) { my $op = $filter->{operator}; my $query = $filter->{query}; + my $ifnull = $filter->{ifnull}; if (!$op or (0 == grep { $_ eq $op } @operators)) { $op = '='; # default operator @@ -1342,19 +1036,52 @@ sub _SearchItems_build_where_fragment { } $column = "ExtractValue($sqlfield, '$xpath')"; } + } + elsif ($field eq 'isbn') { + if ( C4::Context->preference("SearchWithISBNVariations") and $query ) { + my @isbns = C4::Koha::GetVariationsOfISBN( $query ); + $query = []; + push @$query, @isbns; + } + $column = $field; + } + elsif ($field eq 'issn') { + if ( C4::Context->preference("SearchWithISSNVariations") and $query ) { + my @issns = C4::Koha::GetVariationsOfISSN( $query ); + $query = []; + push @$query, @issns; + } + $column = $field; } else { $column = $field; } + if ( defined $ifnull ) { + $column = "COALESCE($column, ?)"; + } + if (ref $query eq 'ARRAY') { - if ($op eq '=') { - $op = 'IN'; - } elsif ($op eq '!=') { - $op = 'NOT IN'; + if ($op eq 'like') { + $where_fragment = { + str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")", + args => $query, + }; } + else { + if ($op eq '=') { + $op = 'IN'; + } elsif ($op eq '!=') { + $op = 'NOT IN'; + } + $where_fragment = { + str => "$column $op (" . join (',', ('?') x @$query) . ")", + args => $query, + }; + } + } elsif ( $op eq 'is' ) { $where_fragment = { - str => "$column $op (" . join (',', ('?') x @$query) . ")", - args => $query, + str => "$column $op $query", + args => [], }; } else { $where_fragment = { @@ -1362,6 +1089,10 @@ sub _SearchItems_build_where_fragment { args => [ $query ], }; } + + if ( defined $ifnull ) { + unshift @{ $where_fragment->{args} }, $ifnull; + } } } @@ -1384,7 +1115,7 @@ A filter has the following keys: =item * query: the value to search in this column -=item * operator: comparison operator. Can be one of = != > < >= <= like +=item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is =back @@ -1443,6 +1174,7 @@ sub SearchItems { LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber + LEFT JOIN issues ON issues.itemnumber = items.itemnumber WHERE 1 }; if (defined $where_str and $where_str ne '') { @@ -1455,10 +1187,17 @@ sub SearchItems { my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns; - my $sortby = (0 < grep {$params->{sortby} eq $_} @columns) - ? $params->{sortby} : 'itemnumber'; - my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC'; - $query .= qq{ ORDER BY $sortby $sortorder }; + push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns; + + if ( $params->{sortby} eq 'availability' ) { + my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC'; + $query .= qq{ ORDER BY onloan $sortorder }; + } else { + my $sortby = (0 < grep {$params->{sortby} eq $_} @columns) + ? $params->{sortby} : 'itemnumber'; + my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC'; + $query .= qq{ ORDER BY $sortby $sortorder }; + } my $rows = $params->{rows}; my @limit_args; @@ -1522,10 +1261,12 @@ sub _find_value { =head2 PrepareItemrecordDisplay - PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode); + PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode); Returns a hash with all the fields for Display a given item data in a template +$defaultvalues should either contain a hashref of values for the new item, or be undefined. + The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided =cut @@ -1543,6 +1284,12 @@ sub PrepareItemrecordDisplay { # its contents. See also GetMarcStructure. my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } ); + # Pick the default location from NewItemsDefaultLocation + if ( C4::Context->preference('NewItemsDefaultLocation') ) { + $defaultvalues //= {}; + $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation'); + } + # return nothing if we don't have found an existing framework. return q{} unless $tagslib; my $itemrecord; @@ -1569,52 +1316,73 @@ sub PrepareItemrecordDisplay { # loop through each subfield my $cntsubf; - foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) { - next if IsMarcStructureInternal($tagslib->{$tag}{$subfield}); - next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} ); - next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" ); + foreach my $subfield ( + sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} } + grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty + values %{ $tagslib->{$tag} } ) + { + next unless ( $subfield->{'tab'} ); + next if ( $subfield->{'tab'} ne "10" ); my %subfield_data; $subfield_data{tag} = $tag; - $subfield_data{subfield} = $subfield; + $subfield_data{subfield} = $subfield->{subfield}; $subfield_data{countsubfield} = $cntsubf++; - $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'}; - $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000)); + $subfield_data{kohafield} = $subfield->{kohafield}; + $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000)); # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib}; - $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib}; - $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory}; - $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable}; + $subfield_data{marc_lib} = $subfield->{lib}; + $subfield_data{mandatory} = $subfield->{mandatory}; + $subfield_data{repeatable} = $subfield->{repeatable}; $subfield_data{hidden} = "display:none" - if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 ) - || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) ); + if ( ( $subfield->{hidden} > 4 ) + || ( $subfield->{hidden} < -4 ) ); my ( $x, $defaultvalue ); if ($itemrecord) { - ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord ); + ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord ); } - $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue; + $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue; if ( !defined $defaultvalue ) { $defaultvalue = q||; } else { $defaultvalue =~ s/"/"/g; + # get today date & replace <>, <>, <
> if provided in the default value + my $today_dt = dt_from_string; + my $year = $today_dt->strftime('%Y'); + my $shortyear = $today_dt->strftime('%y'); + my $month = $today_dt->strftime('%m'); + my $day = $today_dt->strftime('%d'); + $defaultvalue =~ s/<>/$year/g; + $defaultvalue =~ s/<>/$shortyear/g; + $defaultvalue =~ s/<>/$month/g; + $defaultvalue =~ s/<
>/$day/g; + + # And <> with surname (?) + my $username = + ( C4::Context->userenv + ? C4::Context->userenv->{'surname'} + : "superlibrarian" ); + $defaultvalue =~ s/<>/$username/g; } - my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength}; + my $maxlength = $subfield->{maxlength}; # search for itemcallnumber if applicable - if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber' + if ( $subfield->{kohafield} eq 'items.itemcallnumber' && C4::Context->preference('itemcallnumber') && $itemrecord) { foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){ my $CNtag = substr( $itemcn_pref, 0, 3 ); next unless my $field = $itemrecord->field($CNtag); my $CNsubfields = substr( $itemcn_pref, 3 ); + $CNsubfields = undef if $CNsubfields eq ''; $defaultvalue = $field->as_string( $CNsubfields, ' '); last if $defaultvalue; } } - if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber' + if ( $subfield->{kohafield} eq 'items.itemcallnumber' && $defaultvalues && $defaultvalues->{'callnumber'} ) { - if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){ + if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){ # if the item record exists, only use default value if the item has no callnumber $defaultvalue = $defaultvalues->{callnumber}; } elsif ( !$itemrecord and $defaultvalues ) { @@ -1622,18 +1390,18 @@ sub PrepareItemrecordDisplay { $defaultvalue = $defaultvalues->{callnumber}; } } - if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' ) + if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' ) && $defaultvalues && $defaultvalues->{'branchcode'} ) { if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) { $defaultvalue = $defaultvalues->{branchcode}; } } - if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' ) + if ( ( $subfield->{kohafield} eq 'items.location' ) && $defaultvalues && $defaultvalues->{'location'} ) { - if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) { + if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) { # if the item record exists, only use default value if the item has no locationr $defaultvalue = $defaultvalues->{location}; } elsif ( !$itemrecord and $defaultvalues ) { @@ -1641,19 +1409,28 @@ sub PrepareItemrecordDisplay { $defaultvalue = $defaultvalues->{location}; } } - if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) { + if ( ( $subfield->{kohafield} eq 'items.ccode' ) + && $defaultvalues + && $defaultvalues->{'ccode'} ) { + + if ( !$itemrecord and $defaultvalues ) { + # if the item record *doesn't* exists, always use the default value + $defaultvalue = $defaultvalues->{ccode}; + } + } + if ( $subfield->{authorised_value} ) { my @authorised_values; my %authorised_lib; # builds list, depending on authorised value... #---- branch - if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) { + if ( $subfield->{'authorised_value'} eq "branches" ) { if ( ( C4::Context->preference("IndependentBranches") ) && !C4::Context->IsSuperLibrarian() ) { my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" ); $sth->execute( C4::Context->userenv->{branch} ); push @authorised_values, "" - unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); + unless ( $subfield->{mandatory} ); while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) { push @authorised_values, $branchcode; $authorised_lib{$branchcode} = $branchname; @@ -1662,7 +1439,7 @@ sub PrepareItemrecordDisplay { my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" ); $sth->execute; push @authorised_values, "" - unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); + unless ( $subfield->{mandatory} ); while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) { push @authorised_values, $branchcode; $authorised_lib{$branchcode} = $branchname; @@ -1675,7 +1452,7 @@ sub PrepareItemrecordDisplay { } #----- itemtypes - } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) { + } elsif ( $subfield->{authorised_value} eq "itemtypes" ) { my $itemtypes = Koha::ItemTypes->search_with_localization; push @authorised_values, ""; while ( my $itemtype = $itemtypes->next ) { @@ -1687,7 +1464,7 @@ sub PrepareItemrecordDisplay { } #---- class_sources - } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) { + } elsif ( $subfield->{authorised_value} eq "cn_source" ) { push @authorised_values, ""; my $class_sources = GetClassSources(); @@ -1705,7 +1482,7 @@ sub PrepareItemrecordDisplay { #---- "true" authorised value } else { $authorised_values_sth->execute( - $tagslib->{$tag}->{$subfield}->{authorised_value}, + $subfield->{authorised_value}, $branch_limit ? $branch_limit : () ); push @authorised_values, ""; @@ -1720,17 +1497,17 @@ sub PrepareItemrecordDisplay { default => $defaultvalue // q{}, labels => \%authorised_lib, }; - } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) { + } elsif ( $subfield->{value_builder} ) { # it is a plugin require Koha::FrameworkPlugin; my $plugin = Koha::FrameworkPlugin->new({ - name => $tagslib->{$tag}->{$subfield}->{value_builder}, + name => $subfield->{value_builder}, item_style => 1, }); - my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef }; + my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} }; $plugin->build( $pars ); if ( $itemrecord and my $field = $itemrecord->field($tag) ) { - $defaultvalue = $field->subfield($subfield) || q{}; + $defaultvalue = $field->subfield($subfield->{subfield}) || q{}; } if( !$plugin->errstr ) { #TODO Move html to template; see report 12176/13397 @@ -1746,12 +1523,12 @@ sub PrepareItemrecordDisplay { elsif ( $tag eq '' ) { # it's an hidden field $subfield_data{marc_value} = qq(); } - elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ? + elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ? $subfield_data{marc_value} = qq(); } elsif ( length($defaultvalue) > 100 or (C4::Context->preference("marcflavour") eq "UNIMARC" and - 300 <= $tag && $tag < 400 && $subfield eq 'a' ) + 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' ) or (C4::Context->preference("marcflavour") eq "MARC21" and 500 <= $tag && $tag < 600 ) ) { @@ -1788,6 +1565,9 @@ sub ToggleNewStatus { my $report; for my $rule ( @rules ) { my $age = $rule->{age}; + # Default to using items.dateaccessioned if there's an old item modification rule + # missing an agefield value + my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned'; my $conditions = $rule->{conditions}; my $substitutions = $rule->{substitutions}; foreach ( @$substitutions ) { @@ -1802,6 +1582,7 @@ sub ToggleNewStatus { WHERE 1 |; for my $condition ( @$conditions ) { + next unless $condition->{field}; if ( grep { $_ eq $condition->{field} } @item_columns or grep { $_ eq $condition->{field} } @biblioitem_columns @@ -1819,7 +1600,7 @@ sub ToggleNewStatus { } } if ( defined $age ) { - $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |; + $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |; push @params, $age; } my $sth = $dbh->prepare($query);