X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;ds=sidebyside;f=reserve%2Frequest.pl;h=b78faa9955ab94f08c7d511bd18099422cb16209;hb=c7045254e11d640e6358bf0f78c1b5116ee1b00f;hp=2ef00c8b95ea8a3c8fae9b07c9211e4c8fb5c43d;hpb=102b1ca4bf3b1721f496417473643ff951c13833;p=srvgit diff --git a/reserve/request.pl b/reserve/request.pl index 2ef00c8b95..b78faa9955 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -29,44 +29,44 @@ script to place reserves/requests use Modern::Perl; use CGI qw ( -utf8 ); -use List::MoreUtils qw/uniq/; -use Date::Calc qw/Date_to_Days/; -use C4::Output; -use C4::Auth; -use C4::Reserves; -use C4::Biblio; -use C4::Items; -use C4::Koha; -use C4::Serials; -use C4::Circulation; -use Koha::DateUtils; +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::Members; -use C4::Search; # enabled_staff_search_views +use C4::Search qw( enabled_staff_search_views ); use Koha::Biblios; -use Koha::DateUtils; +use Koha::DateUtils qw( dt_from_string output_pref ); use Koha::Checkouts; use Koha::Holds; -use Koha::IssuingRules; +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 = new CGI; +my $input = CGI->new; my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user( { template_name => "reserve/request.tt", query => $input, type => "intranet", - authnotrequired => 0, flagsrequired => { reserveforothers => 'place_holds' }, } ); my $showallitems = $input->param('showallitems'); +my $pickup = $input->param('pickup'); my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_localization->unblessed } }; @@ -74,8 +74,12 @@ my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_l 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; @@ -86,29 +90,51 @@ 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 $hold = Koha::Holds->find( $reserve_id ); - $hold->cancel 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 ); + 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) { @@ -134,6 +160,28 @@ if ($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 . '%' } } + ] + )->filter_out_empty->as_list; + + 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'); @@ -143,8 +191,13 @@ if ( $biblionumbers ) { push @biblionumbers, $input->multi_param('biblionumber'); } -# FIXME multi_hold should not be a variable but depends on the number of elements in @biblionumbers -$template->param(multi_hold => scalar $input->param('multi_hold')); +my $multi_hold = @biblionumbers > 1; +$template->param( + multi_hold => $multi_hold, +); + +# If we are coming from the search result and only 1 is selected +$biblionumber ||= $biblionumbers[0] unless $multi_hold; # If we have the borrowernumber because we've performed an action, then we # don't want to try to place another reserve. @@ -160,6 +213,8 @@ if ($borrowernumber_hold && !$action) { my $new_reserves_count = scalar( @biblionumbers ); my $maxreserves = C4::Context->preference('maxreserves'); + $template->param( maxreserves => $maxreserves ); + if ( $maxreserves && ( $reserves_count + $new_reserves_count > $maxreserves ) ) { @@ -177,14 +232,6 @@ if ($borrowernumber_hold && !$action) { ); } - # 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 $expiry_date ne '0000-00-00' 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; @@ -193,7 +240,6 @@ if ($borrowernumber_hold && !$action) { my $amount_outstanding = $patron->account->balance; $template->param( patron => $patron, - expiry => $expiry, diffbranch => $diffbranch, messages => $messages, warnings => $warnings, @@ -201,450 +247,553 @@ if ($borrowernumber_hold && !$action) { ); } -$template->param( messageborrower => $messageborrower ); - -# FIXME launch another time GetMember perhaps until (Joubu: Why?) -my $patron = Koha::Patrons->find( $borrowernumber_hold ); +if ($club_hold && !$borrowernumber_hold && !$action) { + my $club = Koha::Clubs->find($club_hold); -my $logged_in_patron = Koha::Patrons->find( $borrowernumber ); + my $enrollments = $club->club_enrollments; -my $itemdata_enumchron = 0; -my $itemdata_ccode = 0; -my @biblioloop = (); -foreach my $biblionumber (@biblionumbers) { - next unless $biblionumber =~ m|^\d+$|; - - my %biblioloopiter = (); - - my $biblio = Koha::Biblios->find( $biblionumber ); - - my $force_hold_level; - if ( $patron ) { - { # CanBookBeReserved - my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber ); - if ( $canReserve->{status} eq 'OK' ) { + my $maxreserves = C4::Context->preference('maxreserves'); + my $new_reserves_count = scalar( @biblionumbers ); - #All is OK and we can continue - } - 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; - } - else { - $biblioloopiter{ $canReserve->{status} } = 1; - } + my @members; + + while(my $enrollment = $enrollments->next) { + next if $enrollment->is_canceled; + my $member = { patron => $enrollment->patron }; + 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; } - - # 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 ); - - { # alreadypossession - # Check to see if patron is allowed to place holds on records where the - # patron already has an item from that record checked out - if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions') - && CheckIfIssuedToPatron( $patron->borrowernumber, $biblionumber ) ) - { - $template->param( alreadypossession => 1, ); - } + $member->{amount_outstanding} = $enrollment->patron->account->balance; + if ( $enrollment->patron->branchcode ne C4::Context->userenv->{'branch'} ) { + $member->{diffbranch} = 1; } - } + push @members, $member; + } - my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count(); - my $totalcount = $count; + $template->param( + club => $club, + members => \@members, + maxreserves => $maxreserves, + new_reserves_count => $new_reserves_count + ); +} - # FIXME think @optionloop, is maybe obsolete, or must be switchable by a systeme preference fixed rank or not - # make priorities options +unless ( $club_hold or $borrowernumber_hold ) { + $template->param( clubcount => Koha::Clubs->search->count ); +} - my @optionloop; - for ( 1 .. $count + 1 ) { - push( - @optionloop, - { - num => $_, - selected => ( $_ == $count + 1 ), - } - ); - } - # adding a fixed value for priority options - my $fixedRank = $count+1; +$template->param( + messageborrower => $messageborrower, + messageclub => $messageclub +); - my %itemnumbers_of_biblioitem; +# Load the hold list if +# - we are searching for a patron or club and found one +# - we are not searching for anything +if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) + || ( !$findborrower && !$findclub ) ) +{ + # FIXME launch another time GetMember perhaps until (Joubu: Why?) + my $patron = Koha::Patrons->find( $borrowernumber_hold ); - my @hostitems = get_hostitemnumbers_of($biblionumber); - my @itemnumbers; - if (@hostitems){ - $template->param('hostitemsflag' => 1); - push(@itemnumbers, @hostitems); + if ( $patron && $multi_hold ) { + my @multi_pickup_locations = + Koha::Biblios->search( { biblionumber => \@biblionumbers } ) + ->pickup_locations( { patron => $patron } )->as_list; + $template->param( multi_pickup_locations => \@multi_pickup_locations ); } - my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } }); + my $logged_in_patron = Koha::Patrons->find( $borrowernumber ); - unless ( $items->count ) { - # FIXME Then why do we continue? - $template->param('noitems' => 1); - $biblioloopiter{noitems} = 1; + 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; + } - ## Here we go backwards again to create hash of biblioitemnumber to itemnumbers, - ## when by definition all of the itemnumber have the same biblioitemnumber - 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 $force_hold_level; + if ( $patron ) { + { # CanBookBeReserved + my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber ); + if ( $canReserve->{status} eq 'OK' ) { - ## Should be same as biblionumber - my @biblioitemnumbers = keys %itemnumbers_of_biblioitem; - - my $biblioiteminfos_of = { - map { - my $biblioitem = $_; - ( $biblioitem->{biblioitemnumber} => $biblioitem ) - } @{ Koha::Biblioitems->search( - { biblioitemnumber => { -in => \@biblioitemnumbers } }, - { select => ['biblioitemnumber', 'publicationyear', 'itemtype']} - )->unblessed - } - }; + #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 $frameworkcode = GetFrameworkCode( $biblionumber ); - my @notforloan_avs = Koha::AuthorisedValues->search_by_koha_field({ kohafield => 'items.notforloan', frameworkcode => $frameworkcode }); - my $notforloan_label_of = { map { $_->authorised_value => $_->lib } @notforloan_avs }; - my @bibitemloop; + my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count(); + my $totalcount = $count; - my @available_itemtypes; - foreach my $biblioitemnumber (@biblioitemnumbers) { - my $biblioitem = $biblioiteminfos_of->{$biblioitemnumber}; - my $num_available = 0; - my $num_override = 0; - my $hiddencount = 0; + # adding a fixed value for priority options + my $fixedRank = $count+1; - $biblioitem->{force_hold_level} = $force_hold_level; + my %itemnumbers_of_biblioitem; - if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) { - $biblioitem->{hostitemsflag} = 1; + my @hostitems = get_hostitemnumbers_of($biblionumber); + my @itemnumbers; + if (@hostitems){ + $template->param('hostitemsflag' => 1); + push(@itemnumbers, @hostitems); } - $biblioloopiter{description} = $biblioitem->{description}; - $biblioloopiter{itypename} = $biblioitem->{description}; - if ( $biblioitem->{itemtype} ) { + my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } }); - $biblioitem->{description} = - $itemtypes->{ $biblioitem->{itemtype} }{description}; - - $biblioloopiter{imageurl} = - getitemtypeimagelocation( 'intranet', - $itemtypes->{ $biblioitem->{itemtype} }{imageurl} ); + unless ( $items->count ) { + # FIXME Then why do we continue? + $template->param('noitems' => 1) unless ( $multi_hold ); + $biblioloopiter{noitems} = 1; } - foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } ) { - my $item = $iteminfos_of->{$itemnumber}; - - $item->{force_hold_level} = $force_hold_level; + ## 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; + } - unless (C4::Context->preference('item-level_itypes')) { - $item->{itype} = $biblioitem->{itemtype}; - } + 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 + } + }; + + if ( $club_hold or $borrowernumber_hold ) { + 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; + } - $item->{itypename} = $itemtypes->{ $item->{itype} }{description}; - $item->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $item->{itype} }{imageurl} ); - $item->{homebranch} = $item->{homebranch}; + $biblioloopiter{description} = $biblioitem->{description}; + $biblioloopiter{itypename} = $biblioitem->{description}; + if ( $biblioitem->{itemtype} ) { - # 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}; - } + $biblioitem->{description} = + $itemtypes->{ $biblioitem->{itemtype} }{description}; - if($item->{biblionumber} ne $biblionumber){ - $item->{hostitemsflag} = 1; - $item->{hosttitle} = Koha::Biblios->find( $item->{biblionumber} )->title; - } + $biblioloopiter{imageurl} = + getitemtypeimagelocation( 'intranet', + $itemtypes->{ $biblioitem->{itemtype} }{imageurl} ); + } - # 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'; - } + # 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; - # 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; - } + unless (C4::Context->preference('item-level_itypes')) { + $item->{itype} = $biblioitem->{itemtype}; + } - # Management of the notforloan document - if ( $item->{notforloan} ) { - $item->{backgroundcolor} = 'other'; - $item->{notforloanvalue} = - $notforloan_label_of->{ $item->{notforloan} }; - } + $item->{itypename} = $itemtypes->{ $item->{itype} }{description}; + $item->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $item->{itype} }{imageurl} ); + $item->{homebranch} = $item->{homebranch}; - # Management of lost or long overdue items - if ( $item->{itemlost} ) { - - # FIXME localized strings should never be in Perl code - $item->{message} = - $item->{itemlost} == 1 ? "(lost)" - : $item->{itemlost} == 2 ? "(long overdue)" - : ""; - $item->{backgroundcolor} = 'other'; - if ($logged_in_patron->category->hidelostitems && !$showallitems) { - $item->{hide} = 1; - $hiddencount++; - } - } + # 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}; + } - # Check the transit status - my ( $transfertwhen, $transfertfrom, $transfertto ) = - GetTransfers($itemnumber); + if($item->{biblionumber} ne $biblionumber){ + $item->{hostitemsflag} = 1; + $item->{hosttitle} = Koha::Biblios->find( $item->{biblionumber} )->title; + } - 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 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'; + } - # 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} ); + # 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; } - } - } - if ( $patron ) { - my $patron_unblessed = $patron->unblessed; - my $branch = C4::Circulation::_GetCircControlBranch($item, $patron_unblessed); + # Management of the notforloan document + if ( $item->{notforloan} ) { + $item->{backgroundcolor} = 'other'; + } - my $branchitemrule = GetBranchItemRule( $branch, $item->{'itype'} ); + # Management of lost or long overdue items + if ( $item->{itemlost} ) { + $item->{backgroundcolor} = 'other'; + if ($logged_in_patron->category->hidelostitems && !$showallitems) { + $item->{hide} = 1; + $hiddencount++; + } + } - $item->{'holdallowed'} = $branchitemrule->{'holdallowed'}; + # Check the transit status + my ( $transfertwhen, $transfertfrom, $transfertto ) = + GetTransfers($itemnumber); - my $can_item_be_reserved = CanItemBeReserved( $patron->borrowernumber, $itemnumber ); - $item->{not_holdable} = $can_item_be_reserved->{status} unless ( $can_item_be_reserved->{status} eq 'OK' ); + 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; + } - $item->{item_level_holds} = Koha::IssuingRules->get_opacitemholds_policy( { item => $item_object, patron => $patron } ); + # 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 ( - !$item->{cantreserve} - && !$exceeded_maxreserves - && IsAvailableForItemLevelRequest($item, $patron_unblessed) - && $can_item_be_reserved->{status} eq 'OK' - ) - { - $item->{available} = 1; - $num_available++; + 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, $item_object )->{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 })->as_list; + $item->{pickup_locations_count} = scalar @pickup_locations; + + if ( @pickup_locations ) { + $num_available++; + $item->{available} = 1; + + my $default_pickup_location; + + # Default to logged-in, if valid + if ( C4::Context->userenv->{branch} ) { + ($default_pickup_location) = grep { $_->branchcode eq C4::Context->userenv->{branch} } @pickup_locations; + } + + $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( @available_itemtypes, $item->{itype} ); + push @{ $biblioitem->{itemloop} }, $item; } - elsif ( C4::Context->preference('AllowHoldPolicyOverride') ) { - # If AllowHoldPolicyOverride is set, it should override EVERY restriction, not just branch item rules - $item->{override} = 1; - $num_override++; - push( @available_itemtypes, $item->{itype} ); + # 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); - # 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 @bibitemloop, $biblioitem; } - push @{ $biblioitem->{itemloop} }, $item; - } - - if ( $num_override == 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 $priority = $res->priority(); - my %reserve; - my @optionloop; - for ( my $i = 1 ; $i <= $totalcount ; $i++ ) { - push( - @optionloop, - { - num => $i, - selected => ( $i == $priority ), - } + @available_itemtypes = uniq( @available_itemtypes ); + $template->param( + bibitemloop => \@bibitemloop, + available_itemtypes => \@available_itemtypes ); } - 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(); - - if ( $reserve{'holdingbranch'} eq $reserve{'wbrcode'} ) { - - # Just because the holdingbranch matches the reserve branch doesn't mean the item - # has arrived at the destination, check for an open transfer for the item as well - my ( $transfertwhen, $transfertfrom, $transferto ) = - C4::Circulation::GetTransfers( $res->itemnumber() ); - if ( not $transferto or $transferto ne $res->branchcode() ) { - $reserve{'atdestination'} = 1; + # existingreserves building + my @reserveloop; + my $always_show_holds = $input->cookie('always_show_holds'); + $template->param( always_show_holds => $always_show_holds ); + my $show_holds_now = $input->param('show_holds_now'); + unless( (defined $always_show_holds && $always_show_holds eq 'DONT') && !$show_holds_now ){ + my @reserves = Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list; + 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; + } } - } - # set found to 1 if reserve is waiting for patron pickup - $reserve{'found'} = $res->is_found(); - $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 ); } } - $reserve{'expirationdate'} = output_pref( { dt => dt_from_string( $res->expirationdate ), dateonly => 1 } ) - unless ( !defined( $res->expirationdate ) || $res->expirationdate eq '0000-00-00' ); - $reserve{'date'} = output_pref( { dt => dt_from_string( $res->reservedate ), dateonly => 1 } ); - $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{'optionloop'} = \@optionloop; - $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{object} = $res; - - push( @reserveloop, \%reserve ); - } + # get the time for the form name... + my $time = time(); - # get the time for the form name... - my $time = time(); + $template->param( + time => $time, + fixedRank => $fixedRank, + ); - $template->param( - time => $time, - fixedRank => $fixedRank, - ); + # display infos + $template->param( + 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{author} = $biblio->author; + $biblioloopiter{rank} = $fixedRank; + $biblioloopiter{reserveloop} = \@reserveloop; + + if (@reserveloop) { + $template->param( reserveloop => \@reserveloop ); + } - # display infos - $template->param( - optionloop => \@optionloop, - bibitemloop => \@bibitemloop, - itemdata_enumchron => $itemdata_enumchron, - itemdata_ccode => $itemdata_ccode, - date => $date, - biblionumber => $biblionumber, - findborrower => $findborrower, - title => $biblio->title, - author => $biblio->author, - 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 })->as_list; + $biblioloopiter{pickup_locations} = \@pickup_locations; + $biblioloopiter{pickup_locations_codes} = [ map { $_->branchcode } @pickup_locations ]; + } + + push @biblioloop, \%biblioloopiter; } - push @biblioloop, \%biblioloopiter; + $template->param( biblioloop => \@biblioloop ); + $template->param( no_reserves_allowed => $no_reserves_allowed ); + $template->param( exceeded_maxreserves => $exceeded_maxreserves ); + $template->param( exceeded_holds_per_record => $exceeded_holds_per_record ); + $template->param( subscriptionsnumber => CountSubscriptionFromBiblionumber($biblionumber)); +} elsif ( ! $multi_hold ) { + my $biblio = Koha::Biblios->find( $biblionumber ); + $template->param( biblio => $biblio ); +} + +if ( $multi_hold ) { + $template->param( biblionumbers => join('/', @biblionumbers) ); +} else { + $template->param( biblionumber => $biblionumber || $biblionumbers[0] ); } -$template->param( biblioloop => \@biblioloop ); -$template->param( biblionumbers => $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 );