Koha/Koha/Items.pm
Nick Clemens a15f672e86
Bug 32019: (follow-up) Fix terminology and styling
This patch adds periods to description on batch mod form and alters
language to follow proper terminology

Styling is fixed to align forms

Blank option is removed, No made default

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
2023-02-20 09:54:20 -03:00

492 lines
14 KiB
Perl

package Koha::Items;
# Copyright ByWater Solutions 2014
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Array::Utils qw( array_minus );
use List::MoreUtils qw( uniq );
use Try::Tiny;
use C4::Context;
use C4::Biblio qw( GetMarcStructure GetMarcFromKohaField );
use C4::Circulation;
use Koha::Database;
use Koha::SearchEngine::Indexer;
use Koha::Item::Attributes;
use Koha::Item;
use Koha::CirculationRules;
use base qw(Koha::Objects);
use Koha::SearchEngine::Indexer;
=head1 NAME
Koha::Items - Koha Item object set class
=head1 API
=head2 Class methods
=cut
=head3 filter_by_for_hold
my $filtered_items = $items->filter_by_for_hold;
Return the items of the set that are *potentially* holdable.
Caller has the responsibility to call C4::Reserves::CanItemBeReserved before
placing a hold on one of those items.
=cut
sub filter_by_for_hold {
my ($self) = @_;
my @hold_not_allowed_itypes = Koha::CirculationRules->search(
{
rule_name => 'holdallowed',
branchcode => undef,
categorycode => undef,
rule_value => 'not_allowed',
}
)->get_column('itemtype');
push @hold_not_allowed_itypes, Koha::ItemTypes->search({ notforloan => 1 })->get_column('itemtype');
my $params = {
itemlost => 0,
withdrawn => 0,
notforloan => { '<=' => 0 }, # items with negative or zero notforloan value are holdable
( C4::Context->preference('AllowHoldsOnDamagedItems')? (): ( damaged => 0 ) ),
( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ),
};
if ( C4::Context->preference("item-level_itypes") ) {
return $self->search(
{
%$params,
itype => { -not_in => \@hold_not_allowed_itypes },
}
);
} else {
return $self->search(
{
%$params,
'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes },
},
{
join => 'biblioitem',
}
);
}
}
=head3 filter_by_visible_in_opac
my $filered_items = $items->filter_by_visible_in_opac(
{
[ patron => $patron ]
}
);
Returns a new resultset, containing those items that are not expected to be hidden in OPAC
for the passed I<Koha::Patron> object that is passed.
The I<OpacHiddenItems>, I<hidelostitems> and I<OpacHiddenItemsExceptions> system preferences
are honoured.
=cut
sub filter_by_visible_in_opac {
my ($self, $params) = @_;
my $patron = $params->{patron};
my $result = $self;
# Filter out OpacHiddenItems unless disabled by OpacHiddenItemsExceptions
unless ( $patron and $patron->category->override_hidden_items ) {
my $rules = C4::Context->yaml_preference('OpacHiddenItems') // {};
my $rules_params;
foreach my $field ( keys %$rules ) {
$rules_params->{'me.'.$field} =
[ { '-not_in' => $rules->{$field} }, undef ];
}
$result = $result->search( $rules_params );
}
if (C4::Context->preference('hidelostitems')) {
$result = $result->filter_out_lost;
}
return $result;
}
=head3 filter_out_lost
my $filered_items = $items->filter_out_lost;
Returns a new resultset, containing those items that are not marked as lost.
=cut
sub filter_out_lost {
my ($self) = @_;
my $params = { itemlost => 0 };
return $self->search( $params );
}
=head3 move_to_biblio
$items->move_to_biblio($to_biblio);
Move items to a given biblio.
=cut
sub move_to_biblio {
my ( $self, $to_biblio ) = @_;
my $biblionumbers = { $to_biblio->biblionumber => 1 };
while ( my $item = $self->next() ) {
$biblionumbers->{ $item->biblionumber } = 1;
$item->move_to_biblio( $to_biblio, { skip_record_index => 1 } );
}
my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
for my $biblionumber ( keys %{$biblionumbers} ) {
$indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" );
}
}
=head3 batch_update
Koha::Items->search->batch_update
{
new_values => {
itemnotes => $new_item_notes,
k => $k,
},
regex_mod => {
itemnotes_nonpublic => {
search => 'foo',
replace => 'bar',
modifiers => 'gi',
},
},
exclude_from_local_holds_priority => 1|0,
callback => sub {
# increment something here
},
}
);
Batch update the items.
Returns ( $report, $self )
Report has 2 keys:
* modified_itemnumbers - list of the modified itemnumbers
* modified_fields - number of fields modified
Parameters:
=over
=item new_values
Allows to set a new value for given fields.
The key can be one of the item's column name, or one subfieldcode of a MARC subfields not linked with a Koha field
=item regex_mod
Allows to modify existing subfield's values using a regular expression
=item exclude_from_local_holds_priority
Set the passed boolean value to items.exclude_from_local_holds_priority
=item mark_items_returned
Move issues on these items to the old issues table, do not mark items found, or
adjust damaged/withdrawn statuses, or fines, or locations.
=item callback
Callback function to call after an item has been modified
=back
=cut
sub batch_update {
my ( $self, $params ) = @_;
my $regex_mod = $params->{regex_mod} || {};
my $new_values = $params->{new_values} || {};
my $exclude_from_local_holds_priority = $params->{exclude_from_local_holds_priority};
my $mark_items_returned = $params->{mark_items_returned};
my $callback = $params->{callback};
my (@modified_itemnumbers, $modified_fields);
my $i;
my $schema = Koha::Database->new->schema;
while ( my $item = $self->next ) {
try {$schema->txn_do(sub {
my $modified_holds_priority = 0;
my $item_returned = 0;
if ( defined $exclude_from_local_holds_priority ) {
if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
$item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
$modified_holds_priority = 1;
}
}
my $modified = 0;
my $new_values = {%$new_values}; # Don't modify the original
my $old_values = $item->unblessed;
if ( $item->more_subfields_xml ) {
$old_values = {
%$old_values,
%{$item->additional_attributes->to_hashref},
};
}
for my $attr ( keys %$regex_mod ) {
my $old_value = $old_values->{$attr};
next unless $old_value;
my $value = apply_regex(
{
%{ $regex_mod->{$attr} },
value => $old_value,
}
);
$new_values->{$attr} = $value;
}
for my $attribute ( keys %$new_values ) {
next if $attribute eq 'more_subfields_xml'; # Already counted before
my $old = $old_values->{$attribute};
my $new = $new_values->{$attribute};
$modified++
if ( defined $old xor defined $new )
|| ( defined $old && defined $new && $new ne $old );
}
{ # Dealing with more_subfields_xml
my $frameworkcode = $item->biblio->frameworkcode;
my $tagslib = C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 });
my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
my @more_subfield_tags = map {
(
ref($_)
&& %$_
&& !$_->{kohafield} # Get subfields that are not mapped
)
? $_->{tagsubfield}
: ()
} values %{ $tagslib->{$itemtag} };
my $more_subfields_xml = Koha::Item::Attributes->new(
{
map {
exists $new_values->{$_} ? ( $_ => $new_values->{$_} )
: exists $old_values->{$_}
? ( $_ => $old_values->{$_} )
: ()
} @more_subfield_tags
}
)->to_marcxml($frameworkcode);
$new_values->{more_subfields_xml} = $more_subfields_xml;
delete $new_values->{$_} for @more_subfield_tags; # Clean the hash
}
if ( $modified ) {
my $itemlost_pre = $item->itemlost;
$item->set($new_values)->store({skip_record_index => 1});
C4::Circulation::LostItem(
$item->itemnumber, 'batchmod', undef,
{ skip_record_index => 1 }
) if $item->itemlost
and not $itemlost_pre;
}
if ( $mark_items_returned ){
my $issue = $item->checkout;
if( $issue ){
$item_returned = 1;
C4::Circulation::MarkIssueReturned(
$issue->borrowernumber,
$item->itemnumber,
undef,
$issue->patron->privacy,
{
skip_record_index => 1,
skip_holds_queue => 1,
}
);
}
}
push @modified_itemnumbers, $item->itemnumber if $modified || $modified_holds_priority || $item_returned;
$modified_fields += $modified + $modified_holds_priority + $item_returned;
})}
catch {
warn $_
};
if ( $callback ) {
$callback->(++$i);
}
}
if (@modified_itemnumbers) {
my @biblionumbers = uniq(
Koha::Items->search( { itemnumber => \@modified_itemnumbers } )
->get_column('biblionumber'));
if ( @biblionumbers ) {
my $indexer = Koha::SearchEngine::Indexer->new(
{ index => $Koha::SearchEngine::BIBLIOS_INDEX } );
$indexer->index_records( \@biblionumbers, 'specialUpdate',
"biblioserver", undef );
}
}
return ( { modified_itemnumbers => \@modified_itemnumbers, modified_fields => $modified_fields }, $self );
}
sub apply_regex {
# FIXME Should be moved outside of Koha::Items
# FIXME This is nearly identical to Koha::SimpleMARC::_modify_values
my ($params) = @_;
my $search = $params->{search};
my $replace = $params->{replace};
my $modifiers = $params->{modifiers} || q{};
my $value = $params->{value};
$replace =~ s/"/\\"/g; # Protection from embedded code
$replace = '"' . $replace . '"'; # Put in a string for /ee
my @available_modifiers = qw( i g );
my $retained_modifiers = q||;
for my $modifier ( split //, $modifiers ) {
$retained_modifiers .= $modifier
if grep { /$modifier/ } @available_modifiers;
}
if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
$value =~ s/$search/$replace/igee;
}
elsif ( $retained_modifiers eq 'i' ) {
$value =~ s/$search/$replace/iee;
}
elsif ( $retained_modifiers eq 'g' ) {
$value =~ s/$search/$replace/gee;
}
else {
$value =~ s/$search/$replace/ee;
}
return $value;
}
=head3 search_ordered
$items->search_ordered;
Search and sort items in a specific order, depending if serials are present or not
=cut
sub search_ordered {
my ($self, $params, $attributes) = @_;
$self = $self->search($params, $attributes);
my @biblionumbers = uniq $self->get_column('biblionumber');
if ( scalar ( @biblionumbers ) == 1
&& Koha::Biblios->find( $biblionumbers[0] )->serial )
{
return $self->search(
{},
{
order_by => [ 'serialid.publisheddate', 'me.enumchron' ],
join => { serialitem => 'serialid' }
}
);
} else {
return $self->search(
{},
{
order_by => [
'homebranch.branchname',
'me.enumchron',
\"LPAD( me.copynumber, 8, '0' )",
{-desc => 'me.dateaccessioned'}
],
join => ['homebranch']
}
);
}
}
=head2 Internal methods
=head3 _type
=cut
sub _type {
return 'Item';
}
=head3 object_class
=cut
sub object_class {
return 'Koha::Item';
}
=head1 AUTHOR
Kyle M Hall <kyle@bywatersolutions.com>
Tomas Cohen Arazi <tomascohen@theke.io>
Martin Renvoize <martin.renvoize@ptfs-europe.com>
=cut
1;