Bug 30583: Fix hold system for translated templates
[koha.git] / reserve / request.pl
1 #!/usr/bin/perl
2
3
4 #written 2/1/00 by chris@katipo.oc.nz
5 # Copyright 2000-2002 Katipo Communications
6 # Parts Copyright 2011 Catalyst IT
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Koha is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22
23 =head1 request.pl
24
25 script to place reserves/requests
26
27 =cut
28
29 use Modern::Perl;
30
31 use CGI qw ( -utf8 );
32 use List::MoreUtils qw/uniq/;
33 use Date::Calc qw/Date_to_Days/;
34 use C4::Output;
35 use C4::Auth;
36 use C4::Reserves;
37 use C4::Biblio;
38 use C4::Items;
39 use C4::Koha;
40 use C4::Serials;
41 use C4::Circulation;
42 use Koha::DateUtils;
43 use C4::Utils::DataTables::Members;
44 use C4::Members;
45 use C4::Search;         # enabled_staff_search_views
46
47 use Koha::Biblios;
48 use Koha::DateUtils;
49 use Koha::Checkouts;
50 use Koha::Holds;
51 use Koha::CirculationRules;
52 use Koha::Items;
53 use Koha::ItemTypes;
54 use Koha::Libraries;
55 use Koha::Patrons;
56 use Koha::Clubs;
57
58 my $dbh = C4::Context->dbh;
59 my $input = CGI->new;
60 my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user(
61     {
62         template_name   => "reserve/request.tt",
63         query           => $input,
64         type            => "intranet",
65         flagsrequired   => { reserveforothers => 'place_holds' },
66     }
67 );
68
69 my $showallitems = $input->param('showallitems');
70 my $pickup = $input->param('pickup');
71
72 my $itemtypes = { map { $_->{itemtype} => $_ } @{ Koha::ItemTypes->search_with_localization->unblessed } };
73
74 # Select borrowers infos
75 my $findborrower = $input->param('findborrower');
76 $findborrower = '' unless defined $findborrower;
77 $findborrower =~ s|,| |g;
78 my $findclub = $input->param('findclub');
79 $findclub = '' unless defined $findclub && !$findborrower;
80 my $borrowernumber_hold = $input->param('borrowernumber') || '';
81 my $club_hold = $input->param('club')||'';
82 my $messageborrower;
83 my $messageclub;
84 my $warnings;
85 my $messages;
86 my $exceeded_maxreserves;
87 my $exceeded_holds_per_record;
88
89 my $date = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
90 my $action = $input->param('action');
91 $action ||= q{};
92
93 if ( $action eq 'move' ) {
94   my $where           = $input->param('where');
95   my $reserve_id      = $input->param('reserve_id');
96   my $prev_priority   = $input->param('prev_priority');
97   my $next_priority   = $input->param('next_priority');
98   my $first_priority  = $input->param('first_priority');
99   my $last_priority   = $input->param('last_priority');
100   my $hold_itemnumber = $input->param('itemnumber');
101   if ( $prev_priority == 0 && $next_priority == 1 ){
102       C4::Reserves::RevertWaitingStatus({ itemnumber => $hold_itemnumber });
103   } else {
104       AlterPriority( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority );
105   }
106 } elsif ( $action eq 'cancel' ) {
107   my $reserve_id = $input->param('reserve_id');
108   my $cancellation_reason = $input->param("cancellation-reason");
109   my $hold = Koha::Holds->find( $reserve_id );
110   $hold->cancel({ cancellation_reason => $cancellation_reason }) if $hold;
111 } elsif ( $action eq 'setLowestPriority' ) {
112   my $reserve_id = $input->param('reserve_id');
113   ToggleLowestPriority( $reserve_id );
114 } elsif ( $action eq 'toggleSuspend' ) {
115   my $reserve_id = $input->param('reserve_id');
116   my $suspend_until  = $input->param('suspend_until');
117   ToggleSuspend( $reserve_id, $suspend_until );
118 }
119
120 if ($findborrower) {
121     my $patron = Koha::Patrons->find( { cardnumber => $findborrower } );
122     if ( $patron ) {
123         $borrowernumber_hold = $patron->borrowernumber;
124     } else {
125         my $dt_params = { iDisplayLength => -1 };
126         my $results = C4::Utils::DataTables::Members::search(
127             {
128                 searchmember => $findborrower,
129                 dt_params => $dt_params,
130             }
131         );
132         my $borrowers = $results->{patrons};
133         if ( scalar @$borrowers == 1 ) {
134             $borrowernumber_hold = $borrowers->[0]->{borrowernumber};
135         } elsif ( @$borrowers ) {
136             $template->param( borrowers => $borrowers );
137         } else {
138             $messageborrower = "'$findborrower'";
139         }
140     }
141 }
142
143 if($findclub) {
144     my $club = Koha::Clubs->find( { name => $findclub } );
145     if( $club ) {
146         $club_hold = $club->id;
147     } else {
148         my @clubs = Koha::Clubs->search(
149             [
150                 { name        => { like => '%' . $findclub . '%' } },
151                 { description => { like => '%' . $findclub . '%' } }
152             ]
153         )->filter_out_empty->as_list;
154
155         if( scalar @clubs == 1 ) {
156             $club_hold = $clubs[0]->id;
157         } elsif ( @clubs ) {
158             $template->param( clubs => \@clubs );
159         } else {
160             $messageclub = "'$findclub'";
161         }
162     }
163 }
164
165 my @biblionumbers = ();
166 my $biblionumber = $input->param('biblionumber');
167 my $biblionumbers = $input->param('biblionumbers');
168 if ( $biblionumbers ) {
169     @biblionumbers = split '/', $biblionumbers;
170 } else {
171     push @biblionumbers, $input->multi_param('biblionumber');
172 }
173
174 my $multi_hold = @biblionumbers > 1;
175 $template->param(
176     multi_hold => $multi_hold,
177 );
178
179 # If we are coming from the search result and only 1 is selected
180 $biblionumber ||= $biblionumbers[0] unless $multi_hold;
181
182 # If we have the borrowernumber because we've performed an action, then we
183 # don't want to try to place another reserve.
184 if ($borrowernumber_hold && !$action) {
185     my $patron = Koha::Patrons->find( $borrowernumber_hold );
186     my $diffbranch;
187
188     # we check the reserves of the user, and if they can reserve a document
189     # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
190
191     my $reserves_count = $patron->holds->count;
192
193     my $new_reserves_count = scalar( @biblionumbers );
194
195     my $maxreserves = C4::Context->preference('maxreserves');
196     $template->param( maxreserves => $maxreserves );
197
198     if ( $maxreserves
199         && ( $reserves_count + $new_reserves_count > $maxreserves ) )
200     {
201         my $new_reserves_allowed =
202             $maxreserves - $reserves_count > 0
203           ? $maxreserves - $reserves_count
204           : 0;
205         $warnings             = 1;
206         $exceeded_maxreserves = 1;
207         $template->param(
208             new_reserves_allowed => $new_reserves_allowed,
209             new_reserves_count   => $new_reserves_count,
210             reserves_count       => $reserves_count,
211             maxreserves          => $maxreserves,
212         );
213     }
214
215     # we check the date expiry of the borrower (only if there is an expiry date, otherwise, set to 1 (warn)
216     my $expiry_date = $patron->dateexpiry;
217     my $expiry = 0; # flag set if patron account has expired
218     if ($expiry_date and
219         Date_to_Days(split /-/,$date) > Date_to_Days(split /-/,$expiry_date)) {
220         $expiry = 1;
221     }
222
223     # check if the borrower make the reserv in a different branch
224     if ( $patron->branchcode ne C4::Context->userenv->{'branch'} ) {
225         $diffbranch = 1;
226     }
227
228     my $amount_outstanding = $patron->account->balance;
229     $template->param(
230                 patron              => $patron,
231                 expiry              => $expiry,
232                 diffbranch          => $diffbranch,
233                 messages            => $messages,
234                 warnings            => $warnings,
235                 amount_outstanding  => $amount_outstanding,
236     );
237 }
238
239 if ($club_hold && !$borrowernumber_hold && !$action) {
240     my $club = Koha::Clubs->find($club_hold);
241
242     my $enrollments = $club->club_enrollments;
243
244     my $maxreserves = C4::Context->preference('maxreserves');
245     my $new_reserves_count = scalar( @biblionumbers );
246
247     my @members;
248
249     while(my $enrollment = $enrollments->next) {
250         next if $enrollment->is_canceled;
251         my $member = { patron => $enrollment->patron };
252         my $reserves_count = $enrollment->patron->holds->count;
253         if ( $maxreserves
254             && ( $reserves_count + $new_reserves_count > $maxreserves ) )
255         {
256             $member->{new_reserves_allowed} = $maxreserves - $reserves_count > 0
257                 ? $maxreserves - $reserves_count
258                 : 0;
259             $member->{exceeded_maxreserves} = 1;
260         }
261         $member->{amount_outstanding} = $enrollment->patron->account->balance;
262         if ( $enrollment->patron->branchcode ne C4::Context->userenv->{'branch'} ) {
263             $member->{diffbranch} = 1;
264         }
265
266         push @members, $member;
267     }
268
269     $template->param(
270         club                => $club,
271         members             => \@members,
272         maxreserves         => $maxreserves,
273         new_reserves_count  => $new_reserves_count
274     );
275 }
276
277 unless ( $club_hold or $borrowernumber_hold ) {
278     $template->param( clubcount => Koha::Clubs->search->count );
279 }
280
281 $template->param(
282     messageborrower => $messageborrower,
283     messageclub     => $messageclub
284 );
285
286 # Load the hold list if
287 #  - we are searching for a patron or club and found one
288 #  - we are not searching for anything
289 if (   ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
290     || ( !$findborrower && !$findclub ) )
291 {
292     # FIXME launch another time GetMember perhaps until (Joubu: Why?)
293     my $patron = Koha::Patrons->find( $borrowernumber_hold );
294
295     if ( $patron && $multi_hold ) {
296         my @multi_pickup_locations =
297           Koha::Biblios->search( { biblionumber => \@biblionumbers } )
298           ->pickup_locations( { patron => $patron } )->as_list;
299         $template->param( multi_pickup_locations => \@multi_pickup_locations );
300     }
301
302     my $logged_in_patron = Koha::Patrons->find( $borrowernumber );
303
304     my $wants_check;
305     if ($patron) {
306         $wants_check = $patron->wants_check_for_previous_checkout;
307     }
308     my $itemdata_enumchron = 0;
309     my $itemdata_ccode = 0;
310     my @biblioloop = ();
311     my $no_reserves_allowed = 0;
312     foreach my $biblionumber (@biblionumbers) {
313         next unless $biblionumber =~ m|^\d+$|;
314
315         my %biblioloopiter = ();
316
317         my $biblio = Koha::Biblios->find( $biblionumber );
318         unless ($biblio) {
319             $biblioloopiter{noitems} = 1;
320             $template->param('nobiblio' => 1);
321             last;
322         }
323
324         my $force_hold_level;
325         if ( $patron ) {
326             { # CanBookBeReserved
327                 my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber );
328                 if ( $canReserve->{status} eq 'OK' ) {
329
330                     #All is OK and we can continue
331                 }
332                 elsif ( $canReserve->{status} eq 'noReservesAllowed' || $canReserve->{status} eq 'notReservable' ) {
333                     $no_reserves_allowed = 1;
334                 }
335                 elsif ( $canReserve->{status} eq 'tooManyReserves' ) {
336                     $exceeded_maxreserves = 1;
337                     $template->param( maxreserves => $canReserve->{limit} );
338                 }
339                 elsif ( $canReserve->{status} eq 'tooManyHoldsForThisRecord' ) {
340                     $exceeded_holds_per_record = 1;
341                     $biblioloopiter{ $canReserve->{status} } = 1;
342                 }
343                 elsif ( $canReserve->{status} eq 'ageRestricted' ) {
344                     $template->param( $canReserve->{status} => 1 );
345                     $biblioloopiter{ $canReserve->{status} } = 1;
346                 }
347                 elsif ( $canReserve->{status} eq 'alreadypossession' ) {
348                     $template->param( $canReserve->{status} => 1);
349                     $biblioloopiter{ $canReserve->{status} } = 1;
350                 }
351                 else {
352                     $biblioloopiter{ $canReserve->{status} } = 1;
353                 }
354             }
355
356             # For multiple holds per record, if a patron has previously placed a hold,
357             # the patron can only place more holds of the same type. That is, if the
358             # patron placed a record level hold, all the holds the patron places must
359             # be record level. If the patron placed an item level hold, all holds
360             # the patron places must be item level
361             my $holds = Koha::Holds->search(
362                 {
363                     borrowernumber => $patron->borrowernumber,
364                     biblionumber   => $biblionumber,
365                     found          => undef,
366                 }
367             );
368             $force_hold_level = $holds->forced_hold_level();
369             $biblioloopiter{force_hold_level} = $force_hold_level;
370             $template->param( force_hold_level => $force_hold_level );
371
372             # For a librarian to be able to place multiple record holds for a patron for a record,
373             # we must find out what the maximum number of holds they can place for the patron is
374             my $max_holds_for_record = GetMaxPatronHoldsForRecord( $patron->borrowernumber, $biblionumber );
375             my $remaining_holds_for_record = $max_holds_for_record - $holds->count();
376             $biblioloopiter{remaining_holds_for_record} = $max_holds_for_record;
377             $template->param( max_holds_for_record => $max_holds_for_record );
378             $template->param( remaining_holds_for_record => $remaining_holds_for_record );
379         }
380
381
382         my $count = Koha::Holds->search( { biblionumber => $biblionumber } )->count();
383         my $totalcount = $count;
384
385         # adding a fixed value for priority options
386         my $fixedRank = $count+1;
387
388         my %itemnumbers_of_biblioitem;
389
390         my @hostitems = get_hostitemnumbers_of($biblionumber);
391         my @itemnumbers;
392         if (@hostitems){
393             $template->param('hostitemsflag' => 1);
394             push(@itemnumbers, @hostitems);
395         }
396
397         my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } });
398
399         unless ( $items->count ) {
400             # FIXME Then why do we continue?
401             $template->param('noitems' => 1) unless ( $multi_hold );
402             $biblioloopiter{noitems} = 1;
403         }
404
405         ## Here we go backwards again to create hash of biblioitemnumber to itemnumbers
406         ## this is important when we have analytic items which may be on another record
407         my ( $iteminfos_of );
408         while ( my $item = $items->next ) {
409             $item = $item->unblessed;
410             my $biblioitemnumber = $item->{biblioitemnumber};
411             my $itemnumber = $item->{itemnumber};
412             push( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} }, $itemnumber );
413             $iteminfos_of->{$itemnumber} = $item;
414         }
415
416         my @biblioitemnumbers = keys %itemnumbers_of_biblioitem;
417
418         my $biblioiteminfos_of = {
419             map {
420                 my $biblioitem = $_;
421                 ( $biblioitem->{biblioitemnumber} => $biblioitem )
422               } @{ Koha::Biblioitems->search(
423                     { biblioitemnumber => { -in => \@biblioitemnumbers } },
424                     { select => ['biblionumber', 'biblioitemnumber', 'publicationyear', 'itemtype']}
425                 )->unblessed
426               }
427         };
428
429         if ( $club_hold or $borrowernumber_hold ) {
430             my @bibitemloop;
431
432             my @available_itemtypes;
433             foreach my $biblioitemnumber (@biblioitemnumbers) {
434                 my $biblioitem = $biblioiteminfos_of->{$biblioitemnumber};
435                 my $num_available = 0;
436                 my $num_override  = 0;
437                 my $hiddencount   = 0;
438                 my $num_alreadyheld = 0;
439
440                 $biblioitem->{force_hold_level} = $force_hold_level;
441
442                 if ( $biblioitem->{biblioitemnumber} ne $biblionumber ) {
443                     $biblioitem->{hostitemsflag} = 1;
444                 }
445
446                 $biblioloopiter{description} = $biblioitem->{description};
447                 $biblioloopiter{itypename}   = $biblioitem->{description};
448                 if ( $biblioitem->{itemtype} ) {
449
450                     $biblioitem->{description} =
451                       $itemtypes->{ $biblioitem->{itemtype} }{description};
452
453                     $biblioloopiter{imageurl} =
454                       getitemtypeimagelocation( 'intranet',
455                         $itemtypes->{ $biblioitem->{itemtype} }{imageurl} );
456                 }
457
458                 # iterating through all items first to check if any of them available
459                 # to pass this value further inside down to IsAvailableForItemLevelRequest to
460                 # it's complicated logic to analyse.
461                 # (before this loop was inside that sub loop so it was O(n^2) )
462                 my $items_any_available;
463                 $items_any_available = ItemsAnyAvailableAndNotRestricted( { biblionumber => $biblioitem->{biblionumber}, patron => $patron })
464                     if $patron;
465
466                 foreach my $itemnumber ( @{ $itemnumbers_of_biblioitem{$biblioitemnumber} } )    {
467                     my $item = $iteminfos_of->{$itemnumber};
468                     my $do_check;
469                     if ( $patron ) {
470                         $do_check = $patron->do_check_for_previous_checkout($item) if $wants_check;
471                         if ( $do_check && $wants_check ) {
472                             $item->{checked_previously} = $do_check;
473                             if ( $multi_hold ) {
474                                 $biblioloopiter{checked_previously} = $do_check;
475                             } else {
476                                 $template->param( checked_previously => $do_check );
477                             }
478                         }
479                     }
480                     $item->{force_hold_level} = $force_hold_level;
481
482                     unless (C4::Context->preference('item-level_itypes')) {
483                         $item->{itype} = $biblioitem->{itemtype};
484                     }
485
486                     $item->{itypename} = $itemtypes->{ $item->{itype} }{description};
487                     $item->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtypes->{ $item->{itype} }{imageurl} );
488                     $item->{homebranch} = $item->{homebranch};
489
490                     # if the holdingbranch is different than the homebranch, we show the
491                     # holdingbranch of the document too
492                     if ( $item->{homebranch} ne $item->{holdingbranch} ) {
493                         $item->{holdingbranch} = $item->{holdingbranch};
494                     }
495
496                     if($item->{biblionumber} ne $biblionumber){
497                         $item->{hostitemsflag} = 1;
498                         $item->{hosttitle} = Koha::Biblios->find( $item->{biblionumber} )->title;
499                     }
500
501                     # if the item is currently on loan, we display its return date and
502                     # change the background color
503                     my $issue = Koha::Checkouts->find( { itemnumber => $itemnumber } );
504                     if ( $issue ) {
505                         $item->{date_due} = $issue->date_due;
506                         $item->{backgroundcolor} = 'onloan';
507                     }
508
509                     # checking reserve
510                     my $item_object = Koha::Items->find( $itemnumber );
511                     my $holds = $item_object->current_holds;
512                     if ( my $first_hold = $holds->next ) {
513                         my $p = Koha::Patrons->find( $first_hold->borrowernumber );
514
515                         $item->{backgroundcolor} = 'reserved';
516                         $item->{reservedate}     = output_pref({ dt => dt_from_string( $first_hold->reservedate ), dateonly => 1 }); # FIXME Should be formatted in the template
517                         $item->{ReservedFor}     = $p;
518                         $item->{ExpectedAtLibrary}     = $first_hold->branchcode;
519                         $item->{waitingdate} = $first_hold->waitingdate;
520                     }
521
522                     # Management of the notforloan document
523                     if ( $item->{notforloan} ) {
524                         $item->{backgroundcolor} = 'other';
525                     }
526
527                     # Management of lost or long overdue items
528                     if ( $item->{itemlost} ) {
529                         $item->{backgroundcolor} = 'other';
530                         if ($logged_in_patron->category->hidelostitems && !$showallitems) {
531                             $item->{hide} = 1;
532                             $hiddencount++;
533                         }
534                     }
535
536                     # Check the transit status
537                     my ( $transfertwhen, $transfertfrom, $transfertto ) =
538                       GetTransfers($itemnumber);
539
540                     if ( defined $transfertwhen && $transfertwhen ne '' ) {
541                         $item->{transfertwhen} = output_pref({ dt => dt_from_string( $transfertwhen ), dateonly => 1 });
542                         $item->{transfertfrom} = $transfertfrom;
543                         $item->{transfertto} = $transfertto;
544                         $item->{nocancel} = 1;
545                     }
546
547                     # If there is no loan, return and transfer, we show a checkbox.
548                     $item->{notforloan} ||= 0;
549
550                     # if independent branches is on we need to check if the person can reserve
551                     # for branches they arent logged in to
552                     if ( C4::Context->preference("IndependentBranches") ) {
553                         if (! C4::Context->preference("canreservefromotherbranches")){
554                             # can't reserve items so need to check if item homebranch and userenv branch match if not we can't reserve
555                             my $userenv = C4::Context->userenv;
556                             unless ( C4::Context->IsSuperLibrarian ) {
557                                 $item->{cantreserve} = 1 if ( $item->{homebranch} ne $userenv->{branch} );
558                             }
559                         }
560                     }
561
562                     if ( $patron ) {
563                         my $patron_unblessed = $patron->unblessed;
564                         my $branch = C4::Circulation::_GetCircControlBranch($item, $patron_unblessed);
565
566                         my $branchitemrule = GetBranchItemRule( $branch, $item->{'itype'} );
567
568                         $item->{'holdallowed'} = $branchitemrule->{'holdallowed'};
569
570                         my $can_item_be_reserved = CanItemBeReserved( $patron->borrowernumber, $itemnumber )->{status};
571                         $item->{not_holdable} = $can_item_be_reserved unless ( $can_item_be_reserved eq 'OK' );
572
573                         $item->{item_level_holds} = Koha::CirculationRules->get_opacitemholds_policy( { item => $item_object, patron => $patron } );
574
575                         if (
576                                !$item->{cantreserve}
577                             && !$exceeded_maxreserves
578                             && $can_item_be_reserved eq 'OK'
579                             # items_any_available defined outside of the current loop,
580                             # so we avoiding loop inside IsAvailableForItemLevelRequest:
581                             && IsAvailableForItemLevelRequest($item_object, $patron, undef, $items_any_available)
582                           )
583                         {
584                             # Send the pickup locations count to the UI, the pickup locations will be pulled using the API
585                             my @pickup_locations = $item_object->pickup_locations({ patron => $patron })->as_list;
586                             $item->{pickup_locations_count} = scalar @pickup_locations;
587
588                             if ( @pickup_locations ) {
589                                 $num_available++;
590                                 $item->{available} = 1;
591
592                                 my $default_pickup_location;
593
594                                 # Default to logged-in, if valid
595                                 if ( C4::Context->userenv->{branch} ) {
596                                     ($default_pickup_location) = grep { $_->branchcode eq C4::Context->userenv->{branch} } @pickup_locations;
597                                 }
598
599                                 $item->{default_pickup_location} = $default_pickup_location;
600                             }
601                             else {
602                                 $item->{available} = 0;
603                                 $item->{not_holdable} = "no_valid_pickup_location";
604                             }
605
606                             push( @available_itemtypes, $item->{itype} );
607                         }
608                         elsif ( C4::Context->preference('AllowHoldPolicyOverride') ) {
609                             # If AllowHoldPolicyOverride is set, it should override EVERY restriction, not just branch item rules
610                             # with the exception of itemAlreadyOnHold because, you know, the item is already on hold
611                             if ( $can_item_be_reserved ne 'itemAlreadyOnHold' ) {
612                                 # Send the pickup locations count to the UI, the pickup locations will be pulled using the API
613                                 my $pickup_locations = $item_object->pickup_locations({ patron => $patron });
614                                 $item->{pickup_locations_count} = $pickup_locations->count;
615                                 if ( $item->{pickup_locations_count} > 0 ) {
616                                     $item->{override} = 1;
617                                     $num_override++;
618                                     # pass the holding branch for use as default
619                                     my $default_pickup_location = $pickup_locations->search({ branchcode => $item->{holdingbranch} })->next;
620                                     $item->{default_pickup_location} = $default_pickup_location;
621                                 }
622                                 else {
623                                     $item->{available} = 0;
624                                     $item->{not_holdable} = "no_valid_pickup_location";
625                                 }
626                             } else { $num_alreadyheld++ }
627
628                             push( @available_itemtypes, $item->{itype} );
629                         }
630
631                         # If none of the conditions hold true, then neither override nor available is set and the item cannot be checked
632
633                         # Show serial enumeration when needed
634                         if ($item->{enumchron}) {
635                             $itemdata_enumchron = 1;
636                         }
637                         # Show collection when needed
638                         if ($item->{ccode}) {
639                             $itemdata_ccode = 1;
640                         }
641                     }
642
643                     push @{ $biblioitem->{itemloop} }, $item;
644                 }
645
646                 # While we can't override an alreay held item, we should be able to override the others
647                 # Unless all items are already held
648                 if ( $num_override > 0 && ($num_override + $num_alreadyheld) == scalar( @{ $biblioitem->{itemloop} } ) ) {
649                 # That is, if all items require an override
650                     $template->param( override_required => 1 );
651                 } elsif ( $num_available == 0 ) {
652                     $template->param( none_available => 1 );
653                     $biblioloopiter{warn} = 1;
654                     $biblioloopiter{none_avail} = 1;
655                 }
656                 $template->param( hiddencount => $hiddencount);
657
658                 push @bibitemloop, $biblioitem;
659             }
660
661             @available_itemtypes = uniq( @available_itemtypes );
662             $template->param(
663                 bibitemloop         => \@bibitemloop,
664                 available_itemtypes => \@available_itemtypes
665             );
666         }
667
668         # existingreserves building
669         my @reserveloop;
670         my @reserves = Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } );
671         foreach my $res (
672             sort {
673                 my $a_found = $a->found() || '';
674                 my $b_found = $a->found() || '';
675                 $a_found cmp $b_found;
676             } @reserves
677           )
678         {
679             my %reserve;
680             if ( $res->is_found() ) {
681                 $reserve{'holdingbranch'} = $res->item()->holdingbranch();
682                 $reserve{'biblionumber'}  = $res->item()->biblionumber();
683                 $reserve{'barcodenumber'} = $res->item()->barcode();
684                 $reserve{'wbrcode'}       = $res->branchcode();
685                 $reserve{'itemnumber'}    = $res->itemnumber();
686                 $reserve{'wbrname'}       = $res->branch()->branchname();
687                 $reserve{'atdestination'} = $res->is_at_destination();
688                 $reserve{'desk_name'}     = ( $res->desk() ) ? $res->desk()->desk_name() : '' ;
689                 $reserve{'found'}     = $res->is_found();
690                 $reserve{'inprocessing'} = $res->is_in_processing();
691                 $reserve{'intransit'} = $res->is_in_transit();
692             }
693             elsif ( $res->priority() > 0 ) {
694                 if ( my $item = $res->item() )  {
695                     $reserve{'itemnumber'}      = $item->id();
696                     $reserve{'barcodenumber'}   = $item->barcode();
697                     $reserve{'item_level_hold'} = 1;
698                 }
699             }
700
701             $reserve{'expirationdate'} = $res->expirationdate;
702             $reserve{'date'}           = $res->reservedate;
703             $reserve{'borrowernumber'} = $res->borrowernumber();
704             $reserve{'biblionumber'}   = $res->biblionumber();
705             $reserve{'patron'}         = $res->borrower;
706             $reserve{'notes'}          = $res->reservenotes();
707             $reserve{'waiting_date'}   = $res->waitingdate();
708             $reserve{'ccode'}          = $res->item() ? $res->item()->ccode() : undef;
709             $reserve{'barcode'}        = $res->item() ? $res->item()->barcode() : undef;
710             $reserve{'priority'}       = $res->priority();
711             $reserve{'lowestPriority'} = $res->lowestPriority();
712             $reserve{'suspend'}        = $res->suspend();
713             $reserve{'suspend_until'}  = $res->suspend_until();
714             $reserve{'reserve_id'}     = $res->reserve_id();
715             $reserve{itemtype}         = $res->itemtype();
716             $reserve{branchcode}       = $res->branchcode();
717             $reserve{non_priority}     = $res->non_priority();
718             $reserve{object}           = $res;
719
720             push( @reserveloop, \%reserve );
721         }
722
723         # get the time for the form name...
724         my $time = time();
725
726         $template->param(
727                          time        => $time,
728                          fixedRank   => $fixedRank,
729                         );
730
731         # display infos
732         $template->param(
733                          itemdata_enumchron => $itemdata_enumchron,
734                          itemdata_ccode    => $itemdata_ccode,
735                          date              => $date,
736                          biblionumber      => $biblionumber,
737                          findborrower      => $findborrower,
738                          biblio            => $biblio,
739                          holdsview         => 1,
740                          C4::Search::enabled_staff_search_views,
741                         );
742
743         $biblioloopiter{biblionumber} = $biblionumber;
744         $biblioloopiter{title} = $biblio->title;
745         $biblioloopiter{rank} = $fixedRank;
746         $biblioloopiter{reserveloop} = \@reserveloop;
747
748         if (@reserveloop) {
749             $template->param( reserveloop => \@reserveloop );
750         }
751
752         if ( $patron ) {
753             # Add the valid pickup locations
754             my @pickup_locations = $biblio->pickup_locations({ patron => $patron })->as_list;
755             $biblioloopiter{pickup_locations} = \@pickup_locations;
756             $biblioloopiter{pickup_locations_codes} = [ map { $_->branchcode } @pickup_locations ];
757         }
758
759         push @biblioloop, \%biblioloopiter;
760     }
761
762     $template->param( biblioloop => \@biblioloop );
763     $template->param( no_reserves_allowed => $no_reserves_allowed );
764     $template->param( exceeded_maxreserves => $exceeded_maxreserves );
765     $template->param( exceeded_holds_per_record => $exceeded_holds_per_record );
766     $template->param( subscriptionsnumber => CountSubscriptionFromBiblionumber($biblionumber));
767 } elsif ( ! $multi_hold ) {
768     my $biblio = Koha::Biblios->find( $biblionumber );
769     $template->param( biblio => $biblio );
770 }
771
772 if ( $multi_hold ) {
773     $template->param( biblionumbers => join('/', @biblionumbers) );
774 } else {
775     $template->param( biblionumber => $biblionumber || $biblionumbers[0] );
776 }
777
778 # pass the userenv branch if no pickup location selected
779 $template->param( pickup => $pickup || C4::Context->userenv->{branch} );
780
781 if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
782     $template->param( reserve_in_future => 1 );
783 }
784
785 $template->param(
786     SuspendHoldsIntranet => C4::Context->preference('SuspendHoldsIntranet'),
787     AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'),
788 );
789
790 # printout the page
791 output_html_with_http_headers $input, $cookie, $template->output;
792
793 sub sort_borrowerlist {
794     my $borrowerslist = shift;
795     my $ref           = [];
796     push @{$ref}, sort {
797         uc( $a->{surname} . $a->{firstname} ) cmp
798           uc( $b->{surname} . $b->{firstname} )
799     } @{$borrowerslist};
800     return $ref;
801 }