Nick Clemens
ffbea4faac
The logic in request.pl only passes pickup locations if a hold is allowed, they are not calculated when the hold is overridden. This patch copies the pickup locations code into the override conditional, adjusting to mark the item overridden This also highlights an condition of override: If a hold is allowed, but there are no valid pickup locations the override is not allowed. To test: 1 - Set a holds rule to allow on-shelf holds only if all unavailable 2 - Set AllowHoldPolicyOverride to 'Allow' 3 - Find a record with several items available 4 - Attempt to place a hold 5 - The items have a yellow triangle, but no pickup locations 6 - Place an item level hold 7 - Note the hold has no pickup location 8 - Cancel the hold 9 - Apply patch and restart all 10 - Attempt hold again 11 - Items still have yellow triangle, but there are dropdowns for pickup location 12 - Place an item level hold, verify a pickup location is set 13 - Cancel the hold 14 - Alter circ rule to allow on shelf holds 15 - Change 'Default checkout, hold and return policy' - 'Hold pickup library match' to "item's hold group" 16 - Ensure there is no group for the item 17 - Attempt to place hold 18 - Override is not allowed 'No valid pickup locations' Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
795 lines
31 KiB
Perl
Executable file
795 lines
31 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
|
|
|
|
#written 2/1/00 by chris@katipo.oc.nz
|
|
# Copyright 2000-2002 Katipo Communications
|
|
# Parts Copyright 2011 Catalyst IT
|
|
#
|
|
# 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>.
|
|
|
|
=head1 request.pl
|
|
|
|
script to place reserves/requests
|
|
|
|
=cut
|
|
|
|
use Modern::Perl;
|
|
|
|
use CGI qw ( -utf8 );
|
|
use List::MoreUtils qw( uniq );
|
|
use Date::Calc qw( Date_to_Days );
|
|
use C4::Output qw( output_html_with_http_headers );
|
|
use C4::Auth qw( get_template_and_user );
|
|
use C4::Reserves qw( RevertWaitingStatus AlterPriority ToggleLowestPriority ToggleSuspend CanBookBeReserved GetMaxPatronHoldsForRecord ItemsAnyAvailableAndNotRestricted CanItemBeReserved IsAvailableForItemLevelRequest );
|
|
use C4::Items qw( get_hostitemnumbers_of );
|
|
use C4::Koha qw( getitemtypeimagelocation );
|
|
use C4::Serials qw( CountSubscriptionFromBiblionumber );
|
|
use C4::Circulation qw( GetTransfers _GetCircControlBranch GetBranchItemRule );
|
|
use Koha::DateUtils qw( dt_from_string output_pref );
|
|
use C4::Utils::DataTables::Members;
|
|
use C4::Search qw( enabled_staff_search_views );
|
|
|
|
use Koha::Biblios;
|
|
use Koha::DateUtils qw( dt_from_string output_pref );
|
|
use Koha::Checkouts;
|
|
use Koha::Holds;
|
|
use Koha::CirculationRules;
|
|
use Koha::Items;
|
|
use Koha::ItemTypes;
|
|
use Koha::Libraries;
|
|
use Koha::Patrons;
|
|
use Koha::Clubs;
|
|
use Koha::BackgroundJob::BatchCancelHold;
|
|
|
|
my $dbh = C4::Context->dbh;
|
|
my $input = CGI->new;
|
|
my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user(
|
|
{
|
|
template_name => "reserve/request.tt",
|
|
query => $input,
|
|
type => "intranet",
|
|
flagsrequired => { reserveforothers => 'place_holds' },
|
|
}
|
|
);
|
|
|
|
my $showallitems = $input->param('showallitems');
|
|
my $pickup = $input->param('pickup');
|
|
|
|
my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_localization->unblessed } };
|
|
|
|
# Select borrowers infos
|
|
my $findborrower = $input->param('findborrower');
|
|
$findborrower = '' unless defined $findborrower;
|
|
$findborrower =~ s|,| |g;
|
|
my $findclub = $input->param('findclub');
|
|
$findclub = '' unless defined $findclub && !$findborrower;
|
|
my $borrowernumber_hold = $input->param('borrowernumber') || '';
|
|
my $club_hold = $input->param('club')||'';
|
|
my $messageborrower;
|
|
my $messageclub;
|
|
my $warnings;
|
|
my $messages;
|
|
my $exceeded_maxreserves;
|
|
my $exceeded_holds_per_record;
|
|
|
|
my $date = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
|
|
my $action = $input->param('action');
|
|
$action ||= q{};
|
|
|
|
if ( $action eq 'move' ) {
|
|
my $where = $input->param('where');
|
|
my $reserve_id = $input->param('reserve_id');
|
|
my $prev_priority = $input->param('prev_priority');
|
|
my $next_priority = $input->param('next_priority');
|
|
my $first_priority = $input->param('first_priority');
|
|
my $last_priority = $input->param('last_priority');
|
|
my $hold_itemnumber = $input->param('itemnumber');
|
|
if ( $prev_priority == 0 && $next_priority == 1 ) {
|
|
C4::Reserves::RevertWaitingStatus( { itemnumber => $hold_itemnumber } );
|
|
}
|
|
else {
|
|
AlterPriority(
|
|
$where, $reserve_id, $prev_priority,
|
|
$next_priority, $first_priority, $last_priority
|
|
);
|
|
}
|
|
}
|
|
elsif ( $action eq 'cancel' ) {
|
|
my $reserve_id = $input->param('reserve_id');
|
|
my $cancellation_reason = $input->param("cancellation-reason");
|
|
my $hold = Koha::Holds->find($reserve_id);
|
|
$hold->cancel( { cancellation_reason => $cancellation_reason } ) if $hold;
|
|
}
|
|
elsif ( $action eq 'setLowestPriority' ) {
|
|
my $reserve_id = $input->param('reserve_id');
|
|
ToggleLowestPriority($reserve_id);
|
|
}
|
|
elsif ( $action eq 'toggleSuspend' ) {
|
|
my $reserve_id = $input->param('reserve_id');
|
|
my $suspend_until = $input->param('suspend_until');
|
|
ToggleSuspend( $reserve_id, $suspend_until );
|
|
}
|
|
elsif ( $action eq 'cancelBulk' ) {
|
|
my $cancellation_reason = $input->param("cancellation-reason");
|
|
my @hold_ids = split ',', $input->param("ids");
|
|
my $params = {
|
|
reason => $cancellation_reason,
|
|
hold_ids => \@hold_ids,
|
|
};
|
|
my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params);
|
|
|
|
$template->param(
|
|
enqueued => 1,
|
|
job_id => $job_id
|
|
);
|
|
}
|
|
|
|
if ($findborrower) {
|
|
my $patron = Koha::Patrons->find( { cardnumber => $findborrower } );
|
|
if ( $patron ) {
|
|
$borrowernumber_hold = $patron->borrowernumber;
|
|
} else {
|
|
my $dt_params = { iDisplayLength => -1 };
|
|
my $results = C4::Utils::DataTables::Members::search(
|
|
{
|
|
searchmember => $findborrower,
|
|
dt_params => $dt_params,
|
|
}
|
|
);
|
|
my $borrowers = $results->{patrons};
|
|
if ( scalar @$borrowers == 1 ) {
|
|
$borrowernumber_hold = $borrowers->[0]->{borrowernumber};
|
|
} elsif ( @$borrowers ) {
|
|
$template->param( borrowers => $borrowers );
|
|
} else {
|
|
$messageborrower = "'$findborrower'";
|
|
}
|
|
}
|
|
}
|
|
|
|
if($findclub) {
|
|
my $club = Koha::Clubs->find( { name => $findclub } );
|
|
if( $club ) {
|
|
$club_hold = $club->id;
|
|
} else {
|
|
my @clubs = Koha::Clubs->search( [
|
|
{ name => { like => '%'.$findclub.'%' } },
|
|
{ description => { like => '%'.$findclub.'%' } }
|
|
] );
|
|
if( scalar @clubs == 1 ) {
|
|
$club_hold = $clubs[0]->id;
|
|
} elsif ( @clubs ) {
|
|
$template->param( clubs => \@clubs );
|
|
} else {
|
|
$messageclub = "'$findclub'";
|
|
}
|
|
}
|
|
}
|
|
|
|
my @biblionumbers = ();
|
|
my $biblionumber = $input->param('biblionumber');
|
|
my $biblionumbers = $input->param('biblionumbers');
|
|
if ( $biblionumbers ) {
|
|
@biblionumbers = split '/', $biblionumbers;
|
|
} else {
|
|
push @biblionumbers, $input->multi_param('biblionumber');
|
|
}
|
|
|
|
my $multi_hold = @biblionumbers > 1;
|
|
$template->param(
|
|
multi_hold => $multi_hold,
|
|
);
|
|
|
|
# If we have the borrowernumber because we've performed an action, then we
|
|
# don't want to try to place another reserve.
|
|
if ($borrowernumber_hold && !$action) {
|
|
my $patron = Koha::Patrons->find( $borrowernumber_hold );
|
|
my $diffbranch;
|
|
|
|
# we check the reserves of the user, and if they can reserve a document
|
|
# FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
|
|
|
|
my $reserves_count = $patron->holds->count;
|
|
|
|
my $new_reserves_count = scalar( @biblionumbers );
|
|
|
|
my $maxreserves = C4::Context->preference('maxreserves');
|
|
$template->param( maxreserves => $maxreserves );
|
|
|
|
if ( $maxreserves
|
|
&& ( $reserves_count + $new_reserves_count > $maxreserves ) )
|
|
{
|
|
my $new_reserves_allowed =
|
|
$maxreserves - $reserves_count > 0
|
|
? $maxreserves - $reserves_count
|
|
: 0;
|
|
$warnings = 1;
|
|
$exceeded_maxreserves = 1;
|
|
$template->param(
|
|
new_reserves_allowed => $new_reserves_allowed,
|
|
new_reserves_count => $new_reserves_count,
|
|
reserves_count => $reserves_count,
|
|
maxreserves => $maxreserves,
|
|
);
|
|
}
|
|
|
|
# we check the date expiry of the borrower (only if there is an expiry date, otherwise, set to 1 (warn)
|
|
my $expiry_date = $patron->dateexpiry;
|
|
my $expiry = 0; # flag set if patron account has expired
|
|
if ($expiry_date and
|
|
Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) {
|
|
$expiry = 1;
|
|
}
|
|
|
|
# check if the borrower make the reserv in a different branch
|
|
if ( $patron->branchcode ne C4::Context->userenv->{'branch'} ) {
|
|
$diffbranch = 1;
|
|
}
|
|
|
|
my $amount_outstanding = $patron->account->balance;
|
|
$template->param(
|
|
patron => $patron,
|
|
expiry => $expiry,
|
|
diffbranch => $diffbranch,
|
|
messages => $messages,
|
|
warnings => $warnings,
|
|
amount_outstanding => $amount_outstanding,
|
|
);
|
|
}
|
|
|
|
if ($club_hold && !$borrowernumber_hold && !$action) {
|
|
my $club = Koha::Clubs->find($club_hold);
|
|
|
|
my $enrollments = $club->club_enrollments;
|
|
|
|
my $maxreserves = C4::Context->preference('maxreserves');
|
|
my $new_reserves_count = scalar( @biblionumbers );
|
|
|
|
my @members;
|
|
|
|
while(my $enrollment = $enrollments->next) {
|
|
next if $enrollment->is_canceled;
|
|
my $member = { patron => $enrollment->patron->unblessed };
|
|
my $reserves_count = $enrollment->patron->holds->count;
|
|
if ( $maxreserves
|
|
&& ( $reserves_count + $new_reserves_count > $maxreserves ) )
|
|
{
|
|
$member->{new_reserves_allowed} = $maxreserves - $reserves_count > 0
|
|
? $maxreserves - $reserves_count
|
|
: 0;
|
|
$member->{exceeded_maxreserves} = 1;
|
|
}
|
|
my $expiry_date = $enrollment->patron->dateexpiry;
|
|
$member->{expiry} = 0; # flag set if patron account has expired
|
|
if ($expiry_date and
|
|
Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) {
|
|
$member->{expiry} = 1;
|
|
}
|
|
$member->{amount_outstanding} = $enrollment->patron->account->balance;
|
|
if ( $enrollment->patron->branchcode ne C4::Context->userenv->{'branch'} ) {
|
|
$member->{diffbranch} = 1;
|
|
}
|
|
|
|
push @members, $member;
|
|
}
|
|
|
|
$template->param(
|
|
club => $club,
|
|
members => \@members,
|
|
maxreserves => $maxreserves,
|
|
new_reserves_count => $new_reserves_count
|
|
);
|
|
}
|
|
|
|
unless ( $club_hold or $borrowernumber_hold ) {
|
|
$template->param( clubcount => Koha::Clubs->search->count );
|
|
}
|
|
|
|
$template->param(
|
|
messageborrower => $messageborrower,
|
|
messageclub => $messageclub
|
|
);
|
|
|
|
# FIXME launch another time GetMember perhaps until (Joubu: Why?)
|
|
my $patron = Koha::Patrons->find( $borrowernumber_hold );
|
|
|
|
if ( $patron && $multi_hold ) {
|
|
my @multi_pickup_locations =
|
|
Koha::Biblios->search( { biblionumber => \@biblionumbers } )
|
|
->pickup_locations( { patron => $patron } );
|
|
$template->param( multi_pickup_locations => \@multi_pickup_locations );
|
|
}
|
|
|
|
my $logged_in_patron = Koha::Patrons->find( $borrowernumber );
|
|
|
|
my $wants_check;
|
|
if ($patron) {
|
|
$wants_check = $patron->wants_check_for_previous_checkout;
|
|
}
|
|
my $itemdata_enumchron = 0;
|
|
my $itemdata_ccode = 0;
|
|
my @biblioloop = ();
|
|
my $no_reserves_allowed = 0;
|
|
foreach my $biblionumber (@biblionumbers) {
|
|
next unless $biblionumber =~ m|^\d+$|;
|
|
|
|
my %biblioloopiter = ();
|
|
|
|
my $biblio = Koha::Biblios->find( $biblionumber );
|
|
unless ($biblio) {
|
|
$biblioloopiter{noitems} = 1;
|
|
$template->param('nobiblio' => 1);
|
|
last;
|
|
}
|
|
|
|
my $force_hold_level;
|
|
if ( $patron ) {
|
|
{ # CanBookBeReserved
|
|
my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber );
|
|
if ( $canReserve->{status} eq 'OK' ) {
|
|
|
|
#All is OK and we can continue
|
|
}
|
|
elsif ( $canReserve->{status} eq 'noReservesAllowed' || $canReserve->{status} eq 'notReservable' ) {
|
|
$no_reserves_allowed = 1;
|
|
}
|
|
elsif ( $canReserve->{status} eq 'tooManyReserves' ) {
|
|
$exceeded_maxreserves = 1;
|
|
$template->param( maxreserves => $canReserve->{limit} );
|
|
}
|
|
elsif ( $canReserve->{status} eq 'tooManyHoldsForThisRecord' ) {
|
|
$exceeded_holds_per_record = 1;
|
|
$biblioloopiter{ $canReserve->{status} } = 1;
|
|
}
|
|
elsif ( $canReserve->{status} eq 'ageRestricted' ) {
|
|
$template->param( $canReserve->{status} => 1 );
|
|
$biblioloopiter{ $canReserve->{status} } = 1;
|
|
}
|
|
elsif ( $canReserve->{status} eq 'alreadypossession' ) {
|
|
$template->param( $canReserve->{status} => 1);
|
|
$biblioloopiter{ $canReserve->{status} } = 1;
|
|
}
|
|
else {
|
|
$biblioloopiter{ $canReserve->{status} } = 1;
|
|
}
|
|
}
|
|
|
|
# For multiple holds per record, if a patron has previously placed a hold,
|
|
# the patron can only place more holds of the same type. That is, if the
|
|
# patron placed a record level hold, all the holds the patron places must
|
|
# be record level. If the patron placed an item level hold, all holds
|
|
# the patron places must be item level
|
|
my $holds = Koha::Holds->search(
|
|
{
|
|
borrowernumber => $patron->borrowernumber,
|
|
biblionumber => $biblionumber,
|
|
found => undef,
|
|
}
|
|
);
|
|
$force_hold_level = $holds->forced_hold_level();
|
|
$biblioloopiter{force_hold_level} = $force_hold_level;
|
|
$template->param( force_hold_level => $force_hold_level );
|
|
|
|
# For a librarian to be able to place multiple record holds for a patron for a record,
|
|
# we must find out what the maximum number of holds they can place for the patron is
|
|
my $max_holds_for_record = GetMaxPatronHoldsForRecord( $patron->borrowernumber, $biblionumber );
|
|
my $remaining_holds_for_record = $max_holds_for_record - $holds->count();
|
|
$biblioloopiter{remaining_holds_for_record} = $max_holds_for_record;
|
|
$template->param( max_holds_for_record => $max_holds_for_record );
|
|
$template->param( remaining_holds_for_record => $remaining_holds_for_record );
|
|
}
|
|
|
|
|
|
my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count();
|
|
my $totalcount = $count;
|
|
|
|
# adding a fixed value for priority options
|
|
my $fixedRank = $count+1;
|
|
|
|
my %itemnumbers_of_biblioitem;
|
|
|
|
my @hostitems = get_hostitemnumbers_of($biblionumber);
|
|
my @itemnumbers;
|
|
if (@hostitems){
|
|
$template->param('hostitemsflag' => 1);
|
|
push(@itemnumbers, @hostitems);
|
|
}
|
|
|
|
my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } });
|
|
|
|
unless ( $items->count ) {
|
|
# FIXME Then why do we continue?
|
|
$template->param('noitems' => 1) unless ( $multi_hold );
|
|
$biblioloopiter{noitems} = 1;
|
|
}
|
|
|
|
## Here we go backwards again to create hash of biblioitemnumber to itemnumbers
|
|
## this is important when we have analytic items which may be on another record
|
|
my ( $iteminfos_of );
|
|
while ( my $item = $items->next ) {
|
|
$item = $item->unblessed;
|
|
my $biblioitemnumber = $item->{biblioitemnumber};
|
|
my $itemnumber = $item->{itemnumber};
|
|
push( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} }, $itemnumber );
|
|
$iteminfos_of->{$itemnumber} = $item;
|
|
}
|
|
|
|
my @biblioitemnumbers = keys %itemnumbers_of_biblioitem;
|
|
|
|
my $biblioiteminfos_of = {
|
|
map {
|
|
my $biblioitem = $_;
|
|
( $biblioitem->{biblioitemnumber} => $biblioitem )
|
|
} @{ Koha::Biblioitems->search(
|
|
{ biblioitemnumber => { -in => \@biblioitemnumbers } },
|
|
{ select => ['biblionumber', 'biblioitemnumber', 'publicationyear', 'itemtype']}
|
|
)->unblessed
|
|
}
|
|
};
|
|
|
|
my @bibitemloop;
|
|
|
|
my @available_itemtypes;
|
|
foreach my $biblioitemnumber (@biblioitemnumbers) {
|
|
my $biblioitem = $biblioiteminfos_of->{$biblioitemnumber};
|
|
my $num_available = 0;
|
|
my $num_override = 0;
|
|
my $hiddencount = 0;
|
|
my $num_alreadyheld = 0;
|
|
|
|
$biblioitem->{force_hold_level} = $force_hold_level;
|
|
|
|
if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
|
|
$biblioitem->{hostitemsflag} = 1;
|
|
}
|
|
|
|
$biblioloopiter{description} = $biblioitem->{description};
|
|
$biblioloopiter{itypename} = $biblioitem->{description};
|
|
if ( $biblioitem->{itemtype} ) {
|
|
|
|
$biblioitem->{description} =
|
|
$itemtypes->{ $biblioitem->{itemtype} }{description};
|
|
|
|
$biblioloopiter{imageurl} =
|
|
getitemtypeimagelocation( 'intranet',
|
|
$itemtypes->{ $biblioitem->{itemtype} }{imageurl} );
|
|
}
|
|
|
|
# iterating through all items first to check if any of them available
|
|
# to pass this value further inside down to IsAvailableForItemLevelRequest to
|
|
# it's complicated logic to analyse.
|
|
# (before this loop was inside that sub loop so it was O(n^2) )
|
|
my $items_any_available;
|
|
$items_any_available = ItemsAnyAvailableAndNotRestricted( { biblionumber => $biblioitem->{biblionumber}, patron => $patron })
|
|
if $patron;
|
|
|
|
foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } ) {
|
|
my $item = $iteminfos_of->{$itemnumber};
|
|
my $do_check;
|
|
if ( $patron ) {
|
|
$do_check = $patron->do_check_for_previous_checkout($item) if $wants_check;
|
|
if ( $do_check && $wants_check ) {
|
|
$item->{checked_previously} = $do_check;
|
|
if ( $multi_hold ) {
|
|
$biblioloopiter{checked_previously} = $do_check;
|
|
} else {
|
|
$template->param( checked_previously => $do_check );
|
|
}
|
|
}
|
|
}
|
|
$item->{force_hold_level} = $force_hold_level;
|
|
|
|
unless (C4::Context->preference('item-level_itypes')) {
|
|
$item->{itype} = $biblioitem->{itemtype};
|
|
}
|
|
|
|
$item->{itypename} = $itemtypes->{ $item->{itype} }{description};
|
|
$item->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $item->{itype} }{imageurl} );
|
|
$item->{homebranch} = $item->{homebranch};
|
|
|
|
# if the holdingbranch is different than the homebranch, we show the
|
|
# holdingbranch of the document too
|
|
if ( $item->{homebranch} ne $item->{holdingbranch} ) {
|
|
$item->{holdingbranch} = $item->{holdingbranch};
|
|
}
|
|
|
|
if($item->{biblionumber} ne $biblionumber){
|
|
$item->{hostitemsflag} = 1;
|
|
$item->{hosttitle} = Koha::Biblios->find( $item->{biblionumber} )->title;
|
|
}
|
|
|
|
# if the item is currently on loan, we display its return date and
|
|
# change the background color
|
|
my $issue = Koha::Checkouts->find( { itemnumber => $itemnumber } );
|
|
if ( $issue ) {
|
|
$item->{date_due} = $issue->date_due;
|
|
$item->{backgroundcolor} = 'onloan';
|
|
}
|
|
|
|
# checking reserve
|
|
my $item_object = Koha::Items->find( $itemnumber );
|
|
my $holds = $item_object->current_holds;
|
|
if ( my $first_hold = $holds->next ) {
|
|
my $p = Koha::Patrons->find( $first_hold->borrowernumber );
|
|
|
|
$item->{backgroundcolor} = 'reserved';
|
|
$item->{reservedate} = output_pref({ dt => dt_from_string( $first_hold->reservedate ), dateonly => 1 }); # FIXME Should be formatted in the template
|
|
$item->{ReservedFor} = $p;
|
|
$item->{ExpectedAtLibrary} = $first_hold->branchcode;
|
|
$item->{waitingdate} = $first_hold->waitingdate;
|
|
}
|
|
|
|
# Management of the notforloan document
|
|
if ( $item->{notforloan} ) {
|
|
$item->{backgroundcolor} = 'other';
|
|
}
|
|
|
|
# Management of lost or long overdue items
|
|
if ( $item->{itemlost} ) {
|
|
$item->{backgroundcolor} = 'other';
|
|
if ($logged_in_patron->category->hidelostitems && !$showallitems) {
|
|
$item->{hide} = 1;
|
|
$hiddencount++;
|
|
}
|
|
}
|
|
|
|
# Check the transit status
|
|
my ( $transfertwhen, $transfertfrom, $transfertto ) =
|
|
GetTransfers($itemnumber);
|
|
|
|
if ( defined $transfertwhen && $transfertwhen ne '' ) {
|
|
$item->{transfertwhen} = output_pref({ dt => dt_from_string( $transfertwhen ), dateonly => 1 });
|
|
$item->{transfertfrom} = $transfertfrom;
|
|
$item->{transfertto} = $transfertto;
|
|
$item->{nocancel} = 1;
|
|
}
|
|
|
|
# If there is no loan, return and transfer, we show a checkbox.
|
|
$item->{notforloan} ||= 0;
|
|
|
|
# if independent branches is on we need to check if the person can reserve
|
|
# for branches they arent logged in to
|
|
if ( C4::Context->preference("IndependentBranches") ) {
|
|
if (! C4::Context->preference("canreservefromotherbranches")){
|
|
# can't reserve items so need to check if item homebranch and userenv branch match if not we can't reserve
|
|
my $userenv = C4::Context->userenv;
|
|
unless ( C4::Context->IsSuperLibrarian ) {
|
|
$item->{cantreserve} = 1 if ( $item->{homebranch} ne $userenv->{branch} );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $patron ) {
|
|
my $patron_unblessed = $patron->unblessed;
|
|
my $branch = C4::Circulation::_GetCircControlBranch($item, $patron_unblessed);
|
|
|
|
my $branchitemrule = GetBranchItemRule( $branch, $item->{'itype'} );
|
|
|
|
$item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
|
|
|
|
my $can_item_be_reserved = CanItemBeReserved( $patron->borrowernumber, $itemnumber )->{status};
|
|
$item->{not_holdable} = $can_item_be_reserved unless ( $can_item_be_reserved eq 'OK' );
|
|
|
|
$item->{item_level_holds} = Koha::CirculationRules->get_opacitemholds_policy( { item => $item_object, patron => $patron } );
|
|
|
|
if (
|
|
!$item->{cantreserve}
|
|
&& !$exceeded_maxreserves
|
|
&& $can_item_be_reserved eq 'OK'
|
|
# items_any_available defined outside of the current loop,
|
|
# so we avoiding loop inside IsAvailableForItemLevelRequest:
|
|
&& IsAvailableForItemLevelRequest($item_object, $patron, undef, $items_any_available)
|
|
)
|
|
{
|
|
# Send the pickup locations count to the UI, the pickup locations will be pulled using the API
|
|
my $pickup_locations = $item_object->pickup_locations({ patron => $patron });
|
|
$item->{pickup_locations_count} = $pickup_locations->count;
|
|
if ( $item->{pickup_locations_count} > 0 ) {
|
|
$num_available++;
|
|
$item->{available} = 1;
|
|
# pass the holding branch for use as default
|
|
my $default_pickup_location = $pickup_locations->search({ branchcode => $item->{holdingbranch} })->next;
|
|
$item->{default_pickup_location} = $default_pickup_location;
|
|
}
|
|
else {
|
|
$item->{available} = 0;
|
|
$item->{not_holdable} = "no_valid_pickup_location";
|
|
}
|
|
|
|
push( @available_itemtypes, $item->{itype} );
|
|
}
|
|
elsif ( C4::Context->preference('AllowHoldPolicyOverride') ) {
|
|
# If AllowHoldPolicyOverride is set, it should override EVERY restriction, not just branch item rules
|
|
# with the exception of itemAlreadyOnHold because, you know, the item is already on hold
|
|
if ( $can_item_be_reserved ne 'itemAlreadyOnHold' ) {
|
|
# Send the pickup locations count to the UI, the pickup locations will be pulled using the API
|
|
my $pickup_locations = $item_object->pickup_locations({ patron => $patron });
|
|
$item->{pickup_locations_count} = $pickup_locations->count;
|
|
if ( $item->{pickup_locations_count} > 0 ) {
|
|
$item->{override} = 1;
|
|
$num_override++;
|
|
# pass the holding branch for use as default
|
|
my $default_pickup_location = $pickup_locations->search({ branchcode => $item->{holdingbranch} })->next;
|
|
$item->{default_pickup_location} = $default_pickup_location;
|
|
}
|
|
else {
|
|
$item->{available} = 0;
|
|
$item->{not_holdable} = "no_valid_pickup_location";
|
|
}
|
|
} else { $num_alreadyheld++ }
|
|
|
|
push( @available_itemtypes, $item->{itype} );
|
|
}
|
|
|
|
# If none of the conditions hold true, then neither override nor available is set and the item cannot be checked
|
|
|
|
# Show serial enumeration when needed
|
|
if ($item->{enumchron}) {
|
|
$itemdata_enumchron = 1;
|
|
}
|
|
# Show collection when needed
|
|
if ($item->{ccode}) {
|
|
$itemdata_ccode = 1;
|
|
}
|
|
}
|
|
|
|
push @{ $biblioitem->{itemloop} }, $item;
|
|
}
|
|
|
|
# While we can't override an alreay held item, we should be able to override the others
|
|
# Unless all items are already held
|
|
if ( $num_override > 0 && ($num_override + $num_alreadyheld) == scalar( @{ $biblioitem->{itemloop} } ) ) {
|
|
# That is, if all items require an override
|
|
$template->param( override_required => 1 );
|
|
} elsif ( $num_available == 0 ) {
|
|
$template->param( none_available => 1 );
|
|
$biblioloopiter{warn} = 1;
|
|
$biblioloopiter{none_avail} = 1;
|
|
}
|
|
$template->param( hiddencount => $hiddencount);
|
|
|
|
push @bibitemloop, $biblioitem;
|
|
}
|
|
|
|
@available_itemtypes = uniq( @available_itemtypes );
|
|
$template->param( available_itemtypes => \@available_itemtypes );
|
|
|
|
# existingreserves building
|
|
my @reserveloop;
|
|
my @reserves = Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } );
|
|
foreach my $res (
|
|
sort {
|
|
my $a_found = $a->found() || '';
|
|
my $b_found = $a->found() || '';
|
|
$a_found cmp $b_found;
|
|
} @reserves
|
|
)
|
|
{
|
|
my %reserve;
|
|
if ( $res->is_found() ) {
|
|
$reserve{'holdingbranch'} = $res->item()->holdingbranch();
|
|
$reserve{'biblionumber'} = $res->item()->biblionumber();
|
|
$reserve{'barcodenumber'} = $res->item()->barcode();
|
|
$reserve{'wbrcode'} = $res->branchcode();
|
|
$reserve{'itemnumber'} = $res->itemnumber();
|
|
$reserve{'wbrname'} = $res->branch()->branchname();
|
|
$reserve{'atdestination'} = $res->is_at_destination();
|
|
$reserve{'desk_name'} = ( $res->desk() ) ? $res->desk()->desk_name() : '' ;
|
|
$reserve{'found'} = $res->is_found();
|
|
$reserve{'inprocessing'} = $res->is_in_processing();
|
|
$reserve{'intransit'} = $res->is_in_transit();
|
|
}
|
|
elsif ( $res->priority() > 0 ) {
|
|
if ( my $item = $res->item() ) {
|
|
$reserve{'itemnumber'} = $item->id();
|
|
$reserve{'barcodenumber'} = $item->barcode();
|
|
$reserve{'item_level_hold'} = 1;
|
|
}
|
|
}
|
|
|
|
$reserve{'expirationdate'} = $res->expirationdate;
|
|
$reserve{'date'} = $res->reservedate;
|
|
$reserve{'borrowernumber'} = $res->borrowernumber();
|
|
$reserve{'biblionumber'} = $res->biblionumber();
|
|
$reserve{'patron'} = $res->borrower;
|
|
$reserve{'notes'} = $res->reservenotes();
|
|
$reserve{'waiting_date'} = $res->waitingdate();
|
|
$reserve{'ccode'} = $res->item() ? $res->item()->ccode() : undef;
|
|
$reserve{'barcode'} = $res->item() ? $res->item()->barcode() : undef;
|
|
$reserve{'priority'} = $res->priority();
|
|
$reserve{'lowestPriority'} = $res->lowestPriority();
|
|
$reserve{'suspend'} = $res->suspend();
|
|
$reserve{'suspend_until'} = $res->suspend_until();
|
|
$reserve{'reserve_id'} = $res->reserve_id();
|
|
$reserve{itemtype} = $res->itemtype();
|
|
$reserve{branchcode} = $res->branchcode();
|
|
$reserve{non_priority} = $res->non_priority();
|
|
$reserve{object} = $res;
|
|
|
|
push( @reserveloop, \%reserve );
|
|
}
|
|
|
|
# get the time for the form name...
|
|
my $time = time();
|
|
|
|
$template->param(
|
|
time => $time,
|
|
fixedRank => $fixedRank,
|
|
);
|
|
|
|
# display infos
|
|
$template->param(
|
|
bibitemloop => \@bibitemloop,
|
|
itemdata_enumchron => $itemdata_enumchron,
|
|
itemdata_ccode => $itemdata_ccode,
|
|
date => $date,
|
|
biblionumber => $biblionumber,
|
|
findborrower => $findborrower,
|
|
biblio => $biblio,
|
|
holdsview => 1,
|
|
C4::Search::enabled_staff_search_views,
|
|
);
|
|
|
|
$biblioloopiter{biblionumber} = $biblionumber;
|
|
$biblioloopiter{title} = $biblio->title;
|
|
$biblioloopiter{rank} = $fixedRank;
|
|
$biblioloopiter{reserveloop} = \@reserveloop;
|
|
|
|
if (@reserveloop) {
|
|
$template->param( reserveloop => \@reserveloop );
|
|
}
|
|
|
|
if ( $patron ) {
|
|
# Add the valid pickup locations
|
|
my @pickup_locations = $biblio->pickup_locations({ patron => $patron });
|
|
$biblioloopiter{pickup_locations} = \@pickup_locations;
|
|
$biblioloopiter{pickup_locations_codes} = [ map { $_->branchcode } @pickup_locations ];
|
|
}
|
|
|
|
push @biblioloop, \%biblioloopiter;
|
|
}
|
|
|
|
$template->param( biblioloop => \@biblioloop );
|
|
$template->param( no_reserves_allowed => $no_reserves_allowed );
|
|
$template->param( biblionumbers => join('/', @biblionumbers) );
|
|
$template->param( exceeded_maxreserves => $exceeded_maxreserves );
|
|
$template->param( exceeded_holds_per_record => $exceeded_holds_per_record );
|
|
$template->param( subscriptionsnumber => CountSubscriptionFromBiblionumber($biblionumber));
|
|
|
|
# pass the userenv branch if no pickup location selected
|
|
$template->param( pickup => $pickup || C4::Context->userenv->{branch} );
|
|
|
|
if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
|
|
$template->param( reserve_in_future => 1 );
|
|
}
|
|
|
|
$template->param(
|
|
SuspendHoldsIntranet => C4::Context->preference('SuspendHoldsIntranet'),
|
|
AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'),
|
|
);
|
|
|
|
# printout the page
|
|
output_html_with_http_headers $input, $cookie, $template->output;
|
|
|
|
sub sort_borrowerlist {
|
|
my $borrowerslist = shift;
|
|
my $ref = [];
|
|
push @{$ref}, sort {
|
|
uc( $a->{surname} . $a->{firstname} ) cmp
|
|
uc( $b->{surname} . $b->{firstname} )
|
|
} @{$borrowerslist};
|
|
return $ref;
|
|
}
|