Bug 11126 [QA Followup] - Make reserves returned by _Findgroupreserve sorted by priority
[koha.git] / C4 / Reserves.pm
1 package C4::Reserves;
2
3 # Copyright 2000-2002 Katipo Communications
4 #           2006 SAN Ouest Provence
5 #           2007-2010 BibLibre Paul POULAIN
6 #           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 under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
13 # version.
14 #
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License along
20 # with Koha; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23
24 use strict;
25 #use warnings; FIXME - Bug 2505
26 use C4::Context;
27 use C4::Biblio;
28 use C4::Members;
29 use C4::Items;
30 use C4::Circulation;
31 use C4::Accounts;
32
33 # for _koha_notify_reserve
34 use C4::Members::Messaging;
35 use C4::Members qw();
36 use C4::Letters;
37 use C4::Branch qw( GetBranchDetail );
38 use C4::Dates qw( format_date_in_iso );
39
40 use Koha::DateUtils;
41 use Koha::Calendar;
42 use Koha::Database;
43
44 use List::MoreUtils qw( firstidx );
45
46 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
47
48 =head1 NAME
49
50 C4::Reserves - Koha functions for dealing with reservation.
51
52 =head1 SYNOPSIS
53
54   use C4::Reserves;
55
56 =head1 DESCRIPTION
57
58 This modules provides somes functions to deal with reservations.
59
60   Reserves are stored in reserves table.
61   The following columns contains important values :
62   - priority >0      : then the reserve is at 1st stage, and not yet affected to any item.
63              =0      : then the reserve is being dealed
64   - found : NULL       : means the patron requested the 1st available, and we haven't choosen the item
65             T(ransit)  : the reserve is linked to an item but is in transit to the pickup branch
66             W(aiting)  : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
67             F(inished) : the reserve has been completed, and is done
68   - itemnumber : empty : the reserve is still unaffected to an item
69                  filled: the reserve is attached to an item
70   The complete workflow is :
71   ==== 1st use case ====
72   patron request a document, 1st available :                      P >0, F=NULL, I=NULL
73   a library having it run "transfertodo", and clic on the list    
74          if there is no transfer to do, the reserve waiting
75          patron can pick it up                                    P =0, F=W,    I=filled 
76          if there is a transfer to do, write in branchtransfer    P =0, F=T,    I=filled
77            The pickup library recieve the book, it check in       P =0, F=W,    I=filled
78   The patron borrow the book                                      P =0, F=F,    I=filled
79   
80   ==== 2nd use case ====
81   patron requests a document, a given item,
82     If pickup is holding branch                                   P =0, F=W,   I=filled
83     If transfer needed, write in branchtransfer                   P =0, F=T,    I=filled
84         The pickup library receive the book, it checks it in      P =0, F=W,    I=filled
85   The patron borrow the book                                      P =0, F=F,    I=filled
86
87 =head1 FUNCTIONS
88
89 =cut
90
91 BEGIN {
92     # set the version for version checking
93     $VERSION = 3.07.00.049;
94     require Exporter;
95     @ISA = qw(Exporter);
96     @EXPORT = qw(
97         &AddReserve
98
99         &GetReserve
100         &GetReservesFromItemnumber
101         &GetReservesFromBiblionumber
102         &GetReservesFromBorrowernumber
103         &GetReservesForBranch
104         &GetReservesToBranch
105         &GetReserveCount
106         &GetReserveFee
107         &GetReserveInfo
108         &GetReserveStatus
109         
110         &GetOtherReserves
111         
112         &ModReserveFill
113         &ModReserveAffect
114         &ModReserve
115         &ModReserveStatus
116         &ModReserveCancelAll
117         &ModReserveMinusPriority
118         &MoveReserve
119         
120         &CheckReserves
121         &CanBookBeReserved
122         &CanItemBeReserved
123         &CanReserveBeCanceledFromOpac
124         &CancelReserve
125         &CancelExpiredReserves
126
127         &AutoUnsuspendReserves
128
129         &IsAvailableForItemLevelRequest
130         
131         &AlterPriority
132         &ToggleLowestPriority
133
134         &ReserveSlip
135         &ToggleSuspend
136         &SuspendAll
137
138         &GetReservesControlBranch
139     );
140     @EXPORT_OK = qw( MergeHolds );
141 }    
142
143 =head2 AddReserve
144
145     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
146
147 =cut
148
149 sub AddReserve {
150     my (
151         $branch,    $borrowernumber, $biblionumber,
152         $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
153         $title,      $checkitem, $found
154     ) = @_;
155     my $fee =
156           GetReserveFee($borrowernumber, $biblionumber, $constraint,
157             $bibitems );
158     my $dbh     = C4::Context->dbh;
159     my $const   = lc substr( $constraint, 0, 1 );
160     $resdate = format_date_in_iso( $resdate ) if ( $resdate );
161     $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
162     if ($expdate) {
163         $expdate = format_date_in_iso( $expdate );
164     } else {
165         undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
166     }
167     if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
168         # Make room in reserves for this before those of a later reserve date
169         $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
170     }
171     my $waitingdate;
172
173     # If the reserv had the waiting status, we had the value of the resdate
174     if ( $found eq 'W' ) {
175         $waitingdate = $resdate;
176     }
177
178     #eval {
179     # updates take place here
180     if ( $fee > 0 ) {
181         my $nextacctno = &getnextacctno( $borrowernumber );
182         my $query      = qq/
183         INSERT INTO accountlines
184             (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding)
185         VALUES
186             (?,?,now(),?,?,'Res',?)
187     /;
188         my $usth = $dbh->prepare($query);
189         $usth->execute( $borrowernumber, $nextacctno, $fee,
190             "Reserve Charge - $title", $fee );
191     }
192
193     #if ($const eq 'a'){
194     my $query = qq/
195         INSERT INTO reserves
196             (borrowernumber,biblionumber,reservedate,branchcode,constrainttype,
197             priority,reservenotes,itemnumber,found,waitingdate,expirationdate)
198         VALUES
199              (?,?,?,?,?,
200              ?,?,?,?,?,?)
201     /;
202     my $sth = $dbh->prepare($query);
203     $sth->execute(
204         $borrowernumber, $biblionumber, $resdate, $branch,
205         $const,          $priority,     $notes,   $checkitem,
206         $found,          $waitingdate,  $expdate
207     );
208
209     # Send e-mail to librarian if syspref is active
210     if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){
211         my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
212         my $branch_details = C4::Branch::GetBranchDetail($borrower->{branchcode});
213         if ( my $letter =  C4::Letters::GetPreparedLetter (
214             module => 'reserves',
215             letter_code => 'HOLDPLACED',
216             branchcode => $branch,
217             tables => {
218                 'branches'  => $branch_details,
219                 'borrowers' => $borrower,
220                 'biblio'    => $biblionumber,
221                 'items'     => $checkitem,
222             },
223         ) ) {
224
225             my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
226
227             C4::Letters::EnqueueLetter(
228                 {   letter                 => $letter,
229                     borrowernumber         => $borrowernumber,
230                     message_transport_type => 'email',
231                     from_address           => $admin_email_address,
232                     to_address           => $admin_email_address,
233                 }
234             );
235         }
236     }
237
238     #}
239     ($const eq "o" || $const eq "e") or return;   # FIXME: why not have a useful return value?
240     $query = qq/
241         INSERT INTO reserveconstraints
242             (borrowernumber,biblionumber,reservedate,biblioitemnumber)
243         VALUES
244             (?,?,?,?)
245     /;
246     $sth = $dbh->prepare($query);    # keep prepare outside the loop!
247     foreach (@$bibitems) {
248         $sth->execute($borrowernumber, $biblionumber, $resdate, $_);
249     }
250         
251     return;     # FIXME: why not have a useful return value?
252 }
253
254 =head2 GetReserve
255
256     $res = GetReserve( $reserve_id );
257
258     Return the current reserve.
259
260 =cut
261
262 sub GetReserve {
263     my ($reserve_id) = @_;
264
265     my $dbh = C4::Context->dbh;
266     my $query = "SELECT * FROM reserves WHERE reserve_id = ?";
267     my $sth = $dbh->prepare( $query );
268     $sth->execute( $reserve_id );
269     return $sth->fetchrow_hashref();
270 }
271
272 =head2 GetReservesFromBiblionumber
273
274   my $reserves = GetReservesFromBiblionumber({
275     biblionumber => $biblionumber,
276     [ itemnumber => $itemnumber, ]
277     [ all_dates => 1|0 ]
278   });
279
280 This function gets the list of reservations for one C<$biblionumber>,
281 returning an arrayref pointing to the reserves for C<$biblionumber>.
282
283 By default, only reserves whose start date falls before the current
284 time are returned.  To return all reserves, including future ones,
285 the C<all_dates> parameter can be included and set to a true value.
286
287 If the C<itemnumber> parameter is supplied, reserves must be targeted
288 to that item or not targeted to any item at all; otherwise, they
289 are excluded from the list.
290
291 =cut
292
293 sub GetReservesFromBiblionumber {
294     my ( $params ) = @_;
295     my $biblionumber = $params->{biblionumber} or return [];
296     my $itemnumber = $params->{itemnumber};
297     my $all_dates = $params->{all_dates} // 0;
298     my $dbh   = C4::Context->dbh;
299
300     # Find the desired items in the reserves
301     my @params;
302     my $query = "
303         SELECT  reserve_id,
304                 branchcode,
305                 timestamp AS rtimestamp,
306                 priority,
307                 biblionumber,
308                 borrowernumber,
309                 reservedate,
310                 constrainttype,
311                 found,
312                 itemnumber,
313                 reservenotes,
314                 expirationdate,
315                 lowestPriority,
316                 suspend,
317                 suspend_until
318         FROM     reserves
319         WHERE biblionumber = ? ";
320     push( @params, $biblionumber );
321     unless ( $all_dates ) {
322         $query .= " AND reservedate <= CAST(NOW() AS DATE) ";
323     }
324     if ( $itemnumber ) {
325         $query .= " AND ( itemnumber IS NULL OR itemnumber = ? )";
326         push( @params, $itemnumber );
327     }
328     $query .= "ORDER BY priority";
329     my $sth = $dbh->prepare($query);
330     $sth->execute( @params );
331     my @results;
332     my $i = 0;
333     while ( my $data = $sth->fetchrow_hashref ) {
334
335         # FIXME - What is this doing? How do constraints work?
336         if ($data->{constrainttype} eq 'o') {
337             $query = '
338                 SELECT biblioitemnumber
339                 FROM  reserveconstraints
340                 WHERE  biblionumber   = ?
341                 AND   borrowernumber = ?
342                 AND   reservedate    = ?
343             ';
344             my $csth = $dbh->prepare($query);
345             $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate});
346             my @bibitemno;
347             while ( my $bibitemnos = $csth->fetchrow_array ) {
348                 push( @bibitemno, $bibitemnos );    # FIXME: inefficient: use fetchall_arrayref
349             }
350             my $count = scalar @bibitemno;
351     
352             # if we have two or more different specific itemtypes
353             # reserved by same person on same day
354             my $bdata;
355             if ( $count > 1 ) {
356                 $bdata = GetBiblioItemData( $bibitemno[$i] );   # FIXME: This doesn't make sense.
357                 $i++; #  $i can increase each pass, but the next @bibitemno might be smaller?
358             }
359             else {
360                 # Look up the book we just found.
361                 $bdata = GetBiblioItemData( $bibitemno[0] );
362             }
363             # Add the results of this latest search to the current
364             # results.
365             # FIXME - An 'each' would probably be more efficient.
366             foreach my $key ( keys %$bdata ) {
367                 $data->{$key} = $bdata->{$key};
368             }
369         }
370         push @results, $data;
371     }
372     return \@results;
373 }
374
375 =head2 GetReservesFromItemnumber
376
377  ( $reservedate, $borrowernumber, $branchcode, $reserve_id, $waitingdate ) = GetReservesFromItemnumber($itemnumber);
378
379 Get the first reserve for a specific item number (based on priority). Returns the abovementioned values for that reserve.
380
381 The routine does not look at future reserves (read: item level holds), but DOES include future waits (a confirmed future hold).
382
383 =cut
384
385 sub GetReservesFromItemnumber {
386     my ($itemnumber) = @_;
387
388     my $schema = Koha::Database->new()->schema();
389
390     my $r = $schema->resultset('Reserve')->search(
391         {
392             itemnumber => $itemnumber,
393             suspend    => 0,
394             -or        => [
395                 reservedate => \'<= CAST( NOW() AS DATE )',
396                 waitingdate => { '!=', undef }
397             ]
398         },
399         {
400             order_by => 'priority',
401         }
402     )->first();
403
404     return unless $r;
405
406     return (
407         $r->reservedate(),
408         $r->get_column('borrowernumber'),
409         $r->get_column('branchcode'),
410         $r->reserve_id(),
411         $r->waitingdate(),
412     );
413 }
414
415 =head2 GetReservesFromBorrowernumber
416
417     $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
418
419 TODO :: Descritpion
420
421 =cut
422
423 sub GetReservesFromBorrowernumber {
424     my ( $borrowernumber, $status ) = @_;
425     my $dbh   = C4::Context->dbh;
426     my $sth;
427     if ($status) {
428         $sth = $dbh->prepare("
429             SELECT *
430             FROM   reserves
431             WHERE  borrowernumber=?
432                 AND found =?
433             ORDER BY reservedate
434         ");
435         $sth->execute($borrowernumber,$status);
436     } else {
437         $sth = $dbh->prepare("
438             SELECT *
439             FROM   reserves
440             WHERE  borrowernumber=?
441             ORDER BY reservedate
442         ");
443         $sth->execute($borrowernumber);
444     }
445     my $data = $sth->fetchall_arrayref({});
446     return @$data;
447 }
448 #-------------------------------------------------------------------------------------
449 =head2 CanBookBeReserved
450
451   $error = &CanBookBeReserved($borrowernumber, $biblionumber)
452
453 =cut
454
455 sub CanBookBeReserved{
456     my ($borrowernumber, $biblionumber) = @_;
457
458     my $items = GetItemnumbersForBiblio($biblionumber);
459     #get items linked via host records
460     my @hostitems = get_hostitemnumbers_of($biblionumber);
461     if (@hostitems){
462     push (@$items,@hostitems);
463     }
464
465     foreach my $item (@$items){
466         return 1 if CanItemBeReserved($borrowernumber, $item);
467     }
468     return 0;
469 }
470
471 =head2 CanItemBeReserved
472
473   $error = &CanItemBeReserved($borrowernumber, $itemnumber)
474
475 This function return 1 if an item can be issued by this borrower.
476
477 =cut
478
479 sub CanItemBeReserved{
480     my ($borrowernumber, $itemnumber) = @_;
481     
482     my $dbh             = C4::Context->dbh;
483     my $ruleitemtype; # itemtype of the matching issuing rule
484     my $allowedreserves = 0;
485             
486     # we retrieve borrowers and items informations #
487     # item->{itype} will come for biblioitems if necessery
488     my $item = GetItem($itemnumber);
489
490     # If an item is damaged and we don't allow holds on damaged items, we can stop right here
491     return 0 if ( $item->{damaged} && !C4::Context->preference('AllowHoldsOnDamagedItems') );
492
493     my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);     
494     
495     my $controlbranch = C4::Context->preference('ReservesControlBranch');
496     my $itemtypefield = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
497
498     # we retrieve user rights on this itemtype and branchcode
499     my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed 
500                              FROM issuingrules 
501                              WHERE (categorycode in (?,'*') ) 
502                              AND (itemtype IN (?,'*')) 
503                              AND (branchcode IN (?,'*')) 
504                              ORDER BY 
505                                categorycode DESC, 
506                                itemtype     DESC, 
507                                branchcode   DESC;"
508                            );
509                            
510     my $querycount ="SELECT 
511                             count(*) as count
512                             FROM reserves
513                                 LEFT JOIN items USING (itemnumber)
514                                 LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
515                                 LEFT JOIN borrowers USING (borrowernumber)
516                             WHERE borrowernumber = ?
517                                 ";
518     
519     
520     my $branchcode   = "";
521     my $branchfield  = "reserves.branchcode";
522     
523     if( $controlbranch eq "ItemHomeLibrary" ){
524         $branchfield = "items.homebranch";
525         $branchcode = $item->{homebranch};
526     }elsif( $controlbranch eq "PatronLibrary" ){
527         $branchfield = "borrowers.branchcode";
528         $branchcode = $borrower->{branchcode};
529     }
530     
531     # we retrieve rights 
532     $sth->execute($borrower->{'categorycode'}, $item->{'itype'}, $branchcode);
533     if(my $rights = $sth->fetchrow_hashref()){
534         $ruleitemtype    = $rights->{itemtype};
535         $allowedreserves = $rights->{reservesallowed}; 
536     }else{
537         $ruleitemtype = '*';
538     }
539     
540     # we retrieve count
541     
542     $querycount .= "AND $branchfield = ?";
543     
544     $querycount .= " AND $itemtypefield = ?" if ($ruleitemtype ne "*");
545     my $sthcount = $dbh->prepare($querycount);
546     
547     if($ruleitemtype eq "*"){
548         $sthcount->execute($borrowernumber, $branchcode);
549     }else{
550         $sthcount->execute($borrowernumber, $branchcode, $ruleitemtype);
551     }
552     
553     my $reservecount = "0";
554     if(my $rowcount = $sthcount->fetchrow_hashref()){
555         $reservecount = $rowcount->{count};
556     }
557     
558     # we check if it's ok or not
559     if( $reservecount >= $allowedreserves ){
560         return 0;
561     }
562
563     # If reservecount is ok, we check item branch if IndependentBranches is ON
564     # and canreservefromotherbranches is OFF
565     if ( C4::Context->preference('IndependentBranches')
566         and !C4::Context->preference('canreservefromotherbranches') )
567     {
568         my $itembranch = $item->{homebranch};
569         if ($itembranch ne $borrower->{branchcode}) {
570             return 0;
571         }
572     }
573
574     return 1;
575 }
576
577 =head2 CanReserveBeCanceledFromOpac
578
579     $number = CanReserveBeCanceledFromOpac($reserve_id, $borrowernumber);
580
581     returns 1 if reserve can be cancelled by user from OPAC.
582     First check if reserve belongs to user, next checks if reserve is not in
583     transfer or waiting status
584
585 =cut
586
587 sub CanReserveBeCanceledFromOpac {
588     my ($reserve_id, $borrowernumber) = @_;
589
590     return unless $reserve_id and $borrowernumber;
591     my $reserve = GetReserve($reserve_id);
592
593     return 0 unless $reserve->{borrowernumber} == $borrowernumber;
594     return 0 if ( $reserve->{found} eq 'W' ) or ( $reserve->{found} eq 'T' );
595
596     return 1;
597
598 }
599
600 #--------------------------------------------------------------------------------
601 =head2 GetReserveCount
602
603   $number = &GetReserveCount($borrowernumber);
604
605 this function returns the number of reservation for a borrower given on input arg.
606
607 =cut
608
609 sub GetReserveCount {
610     my ($borrowernumber) = @_;
611
612     my $dbh = C4::Context->dbh;
613
614     my $query = "
615         SELECT COUNT(*) AS counter
616         FROM reserves
617         WHERE borrowernumber = ?
618     ";
619     my $sth = $dbh->prepare($query);
620     $sth->execute($borrowernumber);
621     my $row = $sth->fetchrow_hashref;
622     return $row->{counter};
623 }
624
625 =head2 GetOtherReserves
626
627   ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
628
629 Check queued list of this document and check if this document must be  transfered
630
631 =cut
632
633 sub GetOtherReserves {
634     my ($itemnumber) = @_;
635     my $messages;
636     my $nextreservinfo;
637     my ( undef, $checkreserves, undef ) = CheckReserves($itemnumber);
638     if ($checkreserves) {
639         my $iteminfo = GetItem($itemnumber);
640         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
641             $messages->{'transfert'} = $checkreserves->{'branchcode'};
642             #minus priorities of others reservs
643             ModReserveMinusPriority(
644                 $itemnumber,
645                 $checkreserves->{'reserve_id'},
646             );
647
648             #launch the subroutine dotransfer
649             C4::Items::ModItemTransfer(
650                 $itemnumber,
651                 $iteminfo->{'holdingbranch'},
652                 $checkreserves->{'branchcode'}
653               ),
654               ;
655         }
656
657      #step 2b : case of a reservation on the same branch, set the waiting status
658         else {
659             $messages->{'waiting'} = 1;
660             ModReserveMinusPriority(
661                 $itemnumber,
662                 $checkreserves->{'reserve_id'},
663             );
664             ModReserveStatus($itemnumber,'W');
665         }
666
667         $nextreservinfo = $checkreserves->{'borrowernumber'};
668     }
669
670     return ( $messages, $nextreservinfo );
671 }
672
673 =head2 GetReserveFee
674
675   $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
676
677 Calculate the fee for a reserve
678
679 =cut
680
681 sub GetReserveFee {
682     my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
683
684     #check for issues;
685     my $dbh   = C4::Context->dbh;
686     my $const = lc substr( $constraint, 0, 1 );
687     my $query = qq/
688       SELECT * FROM borrowers
689     LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
690     WHERE borrowernumber = ?
691     /;
692     my $sth = $dbh->prepare($query);
693     $sth->execute($borrowernumber);
694     my $data = $sth->fetchrow_hashref;
695     my $fee      = $data->{'reservefee'};
696     my $cntitems = @- > $bibitems;
697
698     if ( $fee > 0 ) {
699
700         # check for items on issue
701         # first find biblioitem records
702         my @biblioitems;
703         my $sth1 = $dbh->prepare(
704             "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
705                    WHERE (biblio.biblionumber = ?)"
706         );
707         $sth1->execute($biblionumber);
708         while ( my $data1 = $sth1->fetchrow_hashref ) {
709             if ( $const eq "a" ) {
710                 push @biblioitems, $data1;
711             }
712             else {
713                 my $found = 0;
714                 my $x     = 0;
715                 while ( $x < $cntitems ) {
716                     if ( @$bibitems->{'biblioitemnumber'} ==
717                         $data->{'biblioitemnumber'} )
718                     {
719                         $found = 1;
720                     }
721                     $x++;
722                 }
723                 if ( $const eq 'o' ) {
724                     if ( $found == 1 ) {
725                         push @biblioitems, $data1;
726                     }
727                 }
728                 else {
729                     if ( $found == 0 ) {
730                         push @biblioitems, $data1;
731                     }
732                 }
733             }
734         }
735         my $cntitemsfound = @biblioitems;
736         my $issues        = 0;
737         my $x             = 0;
738         my $allissued     = 1;
739         while ( $x < $cntitemsfound ) {
740             my $bitdata = $biblioitems[$x];
741             my $sth2    = $dbh->prepare(
742                 "SELECT * FROM items
743                      WHERE biblioitemnumber = ?"
744             );
745             $sth2->execute( $bitdata->{'biblioitemnumber'} );
746             while ( my $itdata = $sth2->fetchrow_hashref ) {
747                 my $sth3 = $dbh->prepare(
748                     "SELECT * FROM issues
749                        WHERE itemnumber = ?"
750                 );
751                 $sth3->execute( $itdata->{'itemnumber'} );
752                 if ( my $isdata = $sth3->fetchrow_hashref ) {
753                 }
754                 else {
755                     $allissued = 0;
756                 }
757             }
758             $x++;
759         }
760         if ( $allissued == 0 ) {
761             my $rsth =
762               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
763             $rsth->execute($biblionumber);
764             if ( my $rdata = $rsth->fetchrow_hashref ) {
765             }
766             else {
767                 $fee = 0;
768             }
769         }
770     }
771     return $fee;
772 }
773
774 =head2 GetReservesToBranch
775
776   @transreserv = GetReservesToBranch( $frombranch );
777
778 Get reserve list for a given branch
779
780 =cut
781
782 sub GetReservesToBranch {
783     my ( $frombranch ) = @_;
784     my $dbh = C4::Context->dbh;
785     my $sth = $dbh->prepare(
786         "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
787          FROM reserves 
788          WHERE priority='0' 
789            AND branchcode=?"
790     );
791     $sth->execute( $frombranch );
792     my @transreserv;
793     my $i = 0;
794     while ( my $data = $sth->fetchrow_hashref ) {
795         $transreserv[$i] = $data;
796         $i++;
797     }
798     return (@transreserv);
799 }
800
801 =head2 GetReservesForBranch
802
803   @transreserv = GetReservesForBranch($frombranch);
804
805 =cut
806
807 sub GetReservesForBranch {
808     my ($frombranch) = @_;
809     my $dbh = C4::Context->dbh;
810
811     my $query = "
812         SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
813         FROM   reserves 
814         WHERE   priority='0'
815         AND found='W'
816     ";
817     $query .= " AND branchcode=? " if ( $frombranch );
818     $query .= "ORDER BY waitingdate" ;
819
820     my $sth = $dbh->prepare($query);
821     if ($frombranch){
822      $sth->execute($frombranch);
823     } else {
824         $sth->execute();
825     }
826
827     my @transreserv;
828     my $i = 0;
829     while ( my $data = $sth->fetchrow_hashref ) {
830         $transreserv[$i] = $data;
831         $i++;
832     }
833     return (@transreserv);
834 }
835
836 =head2 GetReserveStatus
837
838   $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
839
840 Take an itemnumber or a biblionumber and return the status of the reserve places on it.
841 If several reserves exist, the reserve with the lower priority is given.
842
843 =cut
844
845 ## FIXME: I don't think this does what it thinks it does.
846 ## It only ever checks the first reserve result, even though
847 ## multiple reserves for that bib can have the itemnumber set
848 ## the sub is only used once in the codebase.
849 sub GetReserveStatus {
850     my ($itemnumber, $biblionumber) = @_;
851
852     my $dbh = C4::Context->dbh;
853
854     my ($sth, $found, $priority);
855     if ( $itemnumber ) {
856         $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
857         $sth->execute($itemnumber);
858         ($found, $priority) = $sth->fetchrow_array;
859     }
860
861     if ( $biblionumber and not defined $found and not defined $priority ) {
862         $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
863         $sth->execute($biblionumber);
864         ($found, $priority) = $sth->fetchrow_array;
865     }
866
867     if(defined $found) {
868         return 'Waiting'  if $found eq 'W' and $priority == 0;
869         return 'Finished' if $found eq 'F';
870     }
871
872     return 'Reserved' if $priority > 0;
873
874     return ''; # empty string here will remove need for checking undef, or less log lines
875 }
876
877 =head2 CheckReserves
878
879   ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber);
880   ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode);
881   ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber,undef,$lookahead);
882
883 Find a book in the reserves.
884
885 C<$itemnumber> is the book's item number.
886 C<$lookahead> is the number of days to look in advance for future reserves.
887
888 As I understand it, C<&CheckReserves> looks for the given item in the
889 reserves. If it is found, that's a match, and C<$status> is set to
890 C<Waiting>.
891
892 Otherwise, it finds the most important item in the reserves with the
893 same biblio number as this book (I'm not clear on this) and returns it
894 with C<$status> set to C<Reserved>.
895
896 C<&CheckReserves> returns a two-element list:
897
898 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
899
900 C<$reserve> is the reserve item that matched. It is a
901 reference-to-hash whose keys are mostly the fields of the reserves
902 table in the Koha database.
903
904 =cut
905
906 sub CheckReserves {
907     my ( $item, $barcode, $lookahead_days) = @_;
908     my $dbh = C4::Context->dbh;
909     my $sth;
910     my $select;
911     if (C4::Context->preference('item-level_itypes')){
912         $select = "
913            SELECT items.biblionumber,
914            items.biblioitemnumber,
915            itemtypes.notforloan,
916            items.notforloan AS itemnotforloan,
917            items.itemnumber,
918            items.damaged,
919            items.homebranch,
920            items.holdingbranch
921            FROM   items
922            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
923            LEFT JOIN itemtypes   ON items.itype   = itemtypes.itemtype
924         ";
925     }
926     else {
927         $select = "
928            SELECT items.biblionumber,
929            items.biblioitemnumber,
930            itemtypes.notforloan,
931            items.notforloan AS itemnotforloan,
932            items.itemnumber,
933            items.damaged,
934            items.homebranch,
935            items.holdingbranch
936            FROM   items
937            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
938            LEFT JOIN itemtypes   ON biblioitems.itemtype   = itemtypes.itemtype
939         ";
940     }
941    
942     if ($item) {
943         $sth = $dbh->prepare("$select WHERE itemnumber = ?");
944         $sth->execute($item);
945     }
946     else {
947         $sth = $dbh->prepare("$select WHERE barcode = ?");
948         $sth->execute($barcode);
949     }
950     # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
951     my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber, $damaged, $item_homebranch, $item_holdingbranch ) = $sth->fetchrow_array;
952
953     return if ( $damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
954
955     return unless $itemnumber; # bail if we got nothing.
956
957     # if item is not for loan it cannot be reserved either.....
958     # except where items.notforloan < 0 :  This indicates the item is holdable.
959     return if  ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
960
961     # Find this item in the reserves
962     my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber, $lookahead_days);
963
964     # $priority and $highest are used to find the most important item
965     # in the list returned by &_Findgroupreserve. (The lower $priority,
966     # the more important the item.)
967     # $highest is the most important item we've seen so far.
968     my $highest;
969     if (scalar @reserves) {
970         my $LocalHoldsPriority = C4::Context->preference('LocalHoldsPriority');
971         my $LocalHoldsPriorityPatronControl = C4::Context->preference('LocalHoldsPriorityPatronControl');
972         my $LocalHoldsPriorityItemControl = C4::Context->preference('LocalHoldsPriorityItemControl');
973
974         my $priority = 10000000;
975         foreach my $res (@reserves) {
976             if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
977                 return ( "Waiting", $res, \@reserves ); # Found it
978             } else {
979                 # Lazy fetch for borrower and item. We only need to know about the patron and item
980                 # each and every time if we are using LocalHoldsPriority. This is a great place to
981                 # leverage the inherent lazy fetching of DBIx::Class.
982                 my $borrowerinfo;
983                 my $iteminfo;
984
985                 my $local_hold_match;
986                 if ($LocalHoldsPriority) {
987                     $borrowerinfo = C4::Members::GetMember( borrowernumber => $res->{'borrowernumber'} );
988                     $iteminfo = C4::Items::GetItem($itemnumber);
989
990                     my $local_holds_priority_item_branchcode =
991                       $iteminfo->{$LocalHoldsPriorityItemControl};
992                     my $local_holds_priority_patron_branchcode =
993                       ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' )
994                       ? $res->{branchcode}
995                       : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' )
996                       ? $borrowerinfo->{branchcode}
997                       : undef;
998                     $local_hold_match =
999                       $local_holds_priority_item_branchcode eq
1000                       $local_holds_priority_patron_branchcode;
1001                 }
1002
1003                 # See if this item is more important than what we've got so far
1004                 if ( ( $res->{'priority'} && $res->{'priority'} < $priority ) || $local_hold_match ) {
1005                     $borrowerinfo ||= C4::Members::GetMember( borrowernumber => $res->{'borrowernumber'} );
1006                     $iteminfo ||= C4::Items::GetItem($itemnumber);
1007                     my $branch = GetReservesControlBranch( $iteminfo, $borrowerinfo );
1008                     my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
1009                     next if ($branchitemrule->{'holdallowed'} == 0);
1010                     next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
1011                     $priority = $res->{'priority'};
1012                     $highest  = $res;
1013                     last if $local_hold_match;
1014                 }
1015             }
1016         }
1017     }
1018
1019     # If we get this far, then no exact match was found.
1020     # We return the most important (i.e. next) reservation.
1021     if ($highest) {
1022         $highest->{'itemnumber'} = $item;
1023         return ( "Reserved", $highest, \@reserves );
1024     }
1025
1026     return ( '' );
1027 }
1028
1029 =head2 CancelExpiredReserves
1030
1031   CancelExpiredReserves();
1032
1033 Cancels all reserves with an expiration date from before today.
1034
1035 =cut
1036
1037 sub CancelExpiredReserves {
1038
1039     # Cancel reserves that have passed their expiration date.
1040     my $dbh = C4::Context->dbh;
1041     my $sth = $dbh->prepare( "
1042         SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) 
1043         AND expirationdate IS NOT NULL
1044         AND found IS NULL
1045     " );
1046     $sth->execute();
1047
1048     while ( my $res = $sth->fetchrow_hashref() ) {
1049         CancelReserve({ reserve_id => $res->{'reserve_id'} });
1050     }
1051   
1052     # Cancel reserves that have been waiting too long
1053     if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) {
1054         my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
1055         my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge");
1056         my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
1057
1058         my $today = dt_from_string();
1059
1060         my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
1061         $sth = $dbh->prepare( $query );
1062         $sth->execute( $max_pickup_delay );
1063
1064         while ( my $res = $sth->fetchrow_hashref ) {
1065             my $do_cancel = 1;
1066             unless ( $cancel_on_holidays ) {
1067                 my $calendar = Koha::Calendar->new( branchcode => $res->{'branchcode'} );
1068                 my $is_holiday = $calendar->is_holiday( $today );
1069
1070                 if ( $is_holiday ) {
1071                     $do_cancel = 0;
1072                 }
1073             }
1074
1075             if ( $do_cancel ) {
1076                 if ( $charge ) {
1077                     manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
1078                 }
1079
1080                 CancelReserve({ reserve_id => $res->{'reserve_id'} });
1081             }
1082         }
1083     }
1084
1085 }
1086
1087 =head2 AutoUnsuspendReserves
1088
1089   AutoUnsuspendReserves();
1090
1091 Unsuspends all suspended reserves with a suspend_until date from before today.
1092
1093 =cut
1094
1095 sub AutoUnsuspendReserves {
1096
1097     my $dbh = C4::Context->dbh;
1098
1099     my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )";
1100     my $sth = $dbh->prepare( $query );
1101     $sth->execute();
1102
1103 }
1104
1105 =head2 CancelReserve
1106
1107   CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber ] });
1108
1109 Cancels a reserve.
1110
1111 =cut
1112
1113 sub CancelReserve {
1114     my ( $params ) = @_;
1115
1116     my $reserve_id = $params->{'reserve_id'};
1117     $reserve_id = GetReserveId( $params ) unless ( $reserve_id );
1118
1119     return unless ( $reserve_id );
1120
1121     my $dbh = C4::Context->dbh;
1122
1123     my $reserve = GetReserve( $reserve_id );
1124     if ($reserve) {
1125         my $query = "
1126             UPDATE reserves
1127             SET    cancellationdate = now(),
1128                    found            = Null,
1129                    priority         = 0
1130             WHERE  reserve_id = ?
1131         ";
1132         my $sth = $dbh->prepare($query);
1133         $sth->execute( $reserve_id );
1134
1135         $query = "
1136             INSERT INTO old_reserves
1137             SELECT * FROM reserves
1138             WHERE  reserve_id = ?
1139         ";
1140         $sth = $dbh->prepare($query);
1141         $sth->execute( $reserve_id );
1142
1143         $query = "
1144             DELETE FROM reserves
1145             WHERE  reserve_id = ?
1146         ";
1147         $sth = $dbh->prepare($query);
1148         $sth->execute( $reserve_id );
1149
1150         # now fix the priority on the others....
1151         _FixPriority({ biblionumber => $reserve->{biblionumber} });
1152     }
1153
1154     return $reserve;
1155 }
1156
1157 =head2 ModReserve
1158
1159   ModReserve({ rank => $rank,
1160                reserve_id => $reserve_id,
1161                branchcode => $branchcode
1162                [, itemnumber => $itemnumber ]
1163                [, biblionumber => $biblionumber, $borrowernumber => $borrowernumber ]
1164               });
1165
1166 Change a hold request's priority or cancel it.
1167
1168 C<$rank> specifies the effect of the change.  If C<$rank>
1169 is 'W' or 'n', nothing happens.  This corresponds to leaving a
1170 request alone when changing its priority in the holds queue
1171 for a bib.
1172
1173 If C<$rank> is 'del', the hold request is cancelled.
1174
1175 If C<$rank> is an integer greater than zero, the priority of
1176 the request is set to that value.  Since priority != 0 means
1177 that the item is not waiting on the hold shelf, setting the 
1178 priority to a non-zero value also sets the request's found
1179 status and waiting date to NULL. 
1180
1181 The optional C<$itemnumber> parameter is used only when
1182 C<$rank> is a non-zero integer; if supplied, the itemnumber 
1183 of the hold request is set accordingly; if omitted, the itemnumber
1184 is cleared.
1185
1186 B<FIXME:> Note that the forgoing can have the effect of causing
1187 item-level hold requests to turn into title-level requests.  This
1188 will be fixed once reserves has separate columns for requested
1189 itemnumber and supplying itemnumber.
1190
1191 =cut
1192
1193 sub ModReserve {
1194     my ( $params ) = @_;
1195
1196     my $rank = $params->{'rank'};
1197     my $reserve_id = $params->{'reserve_id'};
1198     my $branchcode = $params->{'branchcode'};
1199     my $itemnumber = $params->{'itemnumber'};
1200     my $suspend_until = $params->{'suspend_until'};
1201     my $borrowernumber = $params->{'borrowernumber'};
1202     my $biblionumber = $params->{'biblionumber'};
1203
1204     return if $rank eq "W";
1205     return if $rank eq "n";
1206
1207     return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
1208     $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber }) unless ( $reserve_id );
1209
1210     my $dbh = C4::Context->dbh;
1211     if ( $rank eq "del" ) {
1212         CancelReserve({ reserve_id => $reserve_id });
1213     }
1214     elsif ($rank =~ /^\d+/ and $rank > 0) {
1215         my $query = "
1216             UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1217             WHERE reserve_id = ?
1218         ";
1219         my $sth = $dbh->prepare($query);
1220         $sth->execute( $rank, $branchcode, $itemnumber, $reserve_id );
1221
1222         if ( defined( $suspend_until ) ) {
1223             if ( $suspend_until ) {
1224                 $suspend_until = C4::Dates->new( $suspend_until )->output("iso");
1225                 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE reserve_id = ?", undef, ( $suspend_until, $reserve_id ) );
1226             } else {
1227                 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE reserve_id = ?", undef, ( $reserve_id ) );
1228             }
1229         }
1230
1231         _FixPriority({ reserve_id => $reserve_id, rank =>$rank });
1232     }
1233 }
1234
1235 =head2 ModReserveFill
1236
1237   &ModReserveFill($reserve);
1238
1239 Fill a reserve. If I understand this correctly, this means that the
1240 reserved book has been found and given to the patron who reserved it.
1241
1242 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1243 whose keys are fields from the reserves table in the Koha database.
1244
1245 =cut
1246
1247 sub ModReserveFill {
1248     my ($res) = @_;
1249     my $dbh = C4::Context->dbh;
1250     # fill in a reserve record....
1251     my $reserve_id = $res->{'reserve_id'};
1252     my $biblionumber = $res->{'biblionumber'};
1253     my $borrowernumber    = $res->{'borrowernumber'};
1254     my $resdate = $res->{'reservedate'};
1255
1256     # get the priority on this record....
1257     my $priority;
1258     my $query = "SELECT priority
1259                  FROM   reserves
1260                  WHERE  biblionumber   = ?
1261                   AND   borrowernumber = ?
1262                   AND   reservedate    = ?";
1263     my $sth = $dbh->prepare($query);
1264     $sth->execute( $biblionumber, $borrowernumber, $resdate );
1265     ($priority) = $sth->fetchrow_array;
1266
1267     # update the database...
1268     $query = "UPDATE reserves
1269                   SET    found            = 'F',
1270                          priority         = 0
1271                  WHERE  biblionumber     = ?
1272                     AND reservedate      = ?
1273                     AND borrowernumber   = ?
1274                 ";
1275     $sth = $dbh->prepare($query);
1276     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1277
1278     # move to old_reserves
1279     $query = "INSERT INTO old_reserves
1280                  SELECT * FROM reserves
1281                  WHERE  biblionumber     = ?
1282                     AND reservedate      = ?
1283                     AND borrowernumber   = ?
1284                 ";
1285     $sth = $dbh->prepare($query);
1286     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1287     $query = "DELETE FROM reserves
1288                  WHERE  biblionumber     = ?
1289                     AND reservedate      = ?
1290                     AND borrowernumber   = ?
1291                 ";
1292     $sth = $dbh->prepare($query);
1293     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1294     
1295     # now fix the priority on the others (if the priority wasn't
1296     # already sorted!)....
1297     unless ( $priority == 0 ) {
1298         _FixPriority({ reserve_id => $reserve_id });
1299     }
1300 }
1301
1302 =head2 ModReserveStatus
1303
1304   &ModReserveStatus($itemnumber, $newstatus);
1305
1306 Update the reserve status for the active (priority=0) reserve.
1307
1308 $itemnumber is the itemnumber the reserve is on
1309
1310 $newstatus is the new status.
1311
1312 =cut
1313
1314 sub ModReserveStatus {
1315
1316     #first : check if we have a reservation for this item .
1317     my ($itemnumber, $newstatus) = @_;
1318     my $dbh = C4::Context->dbh;
1319
1320     my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1321     my $sth_set = $dbh->prepare($query);
1322     $sth_set->execute( $newstatus, $itemnumber );
1323
1324     if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1325       CartToShelf( $itemnumber );
1326     }
1327 }
1328
1329 =head2 ModReserveAffect
1330
1331   &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1332
1333 This function affect an item and a status for a given reserve
1334 The itemnumber parameter is used to find the biblionumber.
1335 with the biblionumber & the borrowernumber, we can affect the itemnumber
1336 to the correct reserve.
1337
1338 if $transferToDo is not set, then the status is set to "Waiting" as well.
1339 otherwise, a transfer is on the way, and the end of the transfer will 
1340 take care of the waiting status
1341
1342 =cut
1343
1344 sub ModReserveAffect {
1345     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1346     my $dbh = C4::Context->dbh;
1347
1348     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1349     # attached to $itemnumber
1350     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1351     $sth->execute($itemnumber);
1352     my ($biblionumber) = $sth->fetchrow;
1353
1354     # get request - need to find out if item is already
1355     # waiting in order to not send duplicate hold filled notifications
1356     my $reserve_id = GetReserveId({
1357         borrowernumber => $borrowernumber,
1358         biblionumber   => $biblionumber,
1359     });
1360     return unless defined $reserve_id;
1361     my $request = GetReserveInfo($reserve_id);
1362     my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1363
1364     # If we affect a reserve that has to be transfered, don't set to Waiting
1365     my $query;
1366     if ($transferToDo) {
1367     $query = "
1368         UPDATE reserves
1369         SET    priority = 0,
1370                itemnumber = ?,
1371                found = 'T'
1372         WHERE borrowernumber = ?
1373           AND biblionumber = ?
1374     ";
1375     }
1376     else {
1377     # affect the reserve to Waiting as well.
1378         $query = "
1379             UPDATE reserves
1380             SET     priority = 0,
1381                     found = 'W',
1382                     waitingdate = NOW(),
1383                     itemnumber = ?
1384             WHERE borrowernumber = ?
1385               AND biblionumber = ?
1386         ";
1387     }
1388     $sth = $dbh->prepare($query);
1389     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1390     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1391     _FixPriority( { biblionumber => $biblionumber } );
1392     if ( C4::Context->preference("ReturnToShelvingCart") ) {
1393       CartToShelf( $itemnumber );
1394     }
1395
1396     return;
1397 }
1398
1399 =head2 ModReserveCancelAll
1400
1401   ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1402
1403 function to cancel reserv,check other reserves, and transfer document if it's necessary
1404
1405 =cut
1406
1407 sub ModReserveCancelAll {
1408     my $messages;
1409     my $nextreservinfo;
1410     my ( $itemnumber, $borrowernumber ) = @_;
1411
1412     #step 1 : cancel the reservation
1413     my $CancelReserve = CancelReserve({ itemnumber => $itemnumber, borrowernumber => $borrowernumber });
1414
1415     #step 2 launch the subroutine of the others reserves
1416     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1417
1418     return ( $messages, $nextreservinfo );
1419 }
1420
1421 =head2 ModReserveMinusPriority
1422
1423   &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1424
1425 Reduce the values of queued list
1426
1427 =cut
1428
1429 sub ModReserveMinusPriority {
1430     my ( $itemnumber, $reserve_id ) = @_;
1431
1432     #first step update the value of the first person on reserv
1433     my $dbh   = C4::Context->dbh;
1434     my $query = "
1435         UPDATE reserves
1436         SET    priority = 0 , itemnumber = ? 
1437         WHERE  reserve_id = ?
1438     ";
1439     my $sth_upd = $dbh->prepare($query);
1440     $sth_upd->execute( $itemnumber, $reserve_id );
1441     # second step update all others reserves
1442     _FixPriority({ reserve_id => $reserve_id, rank => '0' });
1443 }
1444
1445 =head2 GetReserveInfo
1446
1447   &GetReserveInfo($reserve_id);
1448
1449 Get item and borrower details for a current hold.
1450 Current implementation this query should have a single result.
1451
1452 =cut
1453
1454 sub GetReserveInfo {
1455     my ( $reserve_id ) = @_;
1456     my $dbh = C4::Context->dbh;
1457     my $strsth="SELECT
1458                    reserve_id,
1459                    reservedate,
1460                    reservenotes,
1461                    reserves.borrowernumber,
1462                    reserves.biblionumber,
1463                    reserves.branchcode,
1464                    reserves.waitingdate,
1465                    notificationdate,
1466                    reminderdate,
1467                    priority,
1468                    found,
1469                    firstname,
1470                    surname,
1471                    phone,
1472                    email,
1473                    address,
1474                    address2,
1475                    cardnumber,
1476                    city,
1477                    zipcode,
1478                    biblio.title,
1479                    biblio.author,
1480                    items.holdingbranch,
1481                    items.itemcallnumber,
1482                    items.itemnumber,
1483                    items.location,
1484                    barcode,
1485                    notes
1486                 FROM reserves
1487                 LEFT JOIN items USING(itemnumber)
1488                 LEFT JOIN borrowers USING(borrowernumber)
1489                 LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber)
1490                 WHERE reserves.reserve_id = ?";
1491     my $sth = $dbh->prepare($strsth);
1492     $sth->execute($reserve_id);
1493
1494     my $data = $sth->fetchrow_hashref;
1495     return $data;
1496 }
1497
1498 =head2 IsAvailableForItemLevelRequest
1499
1500   my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1501
1502 Checks whether a given item record is available for an
1503 item-level hold request.  An item is available if
1504
1505 * it is not lost AND 
1506 * it is not damaged AND 
1507 * it is not withdrawn AND 
1508 * does not have a not for loan value > 0
1509
1510 Whether or not the item is currently on loan is 
1511 also checked - if the AllowOnShelfHolds system preference
1512 is ON, an item can be requested even if it is currently
1513 on loan to somebody else.  If the system preference
1514 is OFF, an item that is currently checked out cannot
1515 be the target of an item-level hold request.
1516
1517 Note that IsAvailableForItemLevelRequest() does not
1518 check if the staff operator is authorized to place
1519 a request on the item - in particular,
1520 this routine does not check IndependentBranches
1521 and canreservefromotherbranches.
1522
1523 =cut
1524
1525 sub IsAvailableForItemLevelRequest {
1526     my $itemnumber = shift;
1527    
1528     my $item = GetItem($itemnumber);
1529
1530     # must check the notforloan setting of the itemtype
1531     # FIXME - a lot of places in the code do this
1532     #         or something similar - need to be
1533     #         consolidated
1534     my $dbh = C4::Context->dbh;
1535     my $notforloan_query;
1536     if (C4::Context->preference('item-level_itypes')) {
1537         $notforloan_query = "SELECT itemtypes.notforloan
1538                              FROM items
1539                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1540                              WHERE itemnumber = ?";
1541     } else {
1542         $notforloan_query = "SELECT itemtypes.notforloan
1543                              FROM items
1544                              JOIN biblioitems USING (biblioitemnumber)
1545                              JOIN itemtypes USING (itemtype)
1546                              WHERE itemnumber = ?";
1547     }
1548     my $sth = $dbh->prepare($notforloan_query);
1549     $sth->execute($itemnumber);
1550     my $notforloan_per_itemtype = 0;
1551     if (my ($notforloan) = $sth->fetchrow_array) {
1552         $notforloan_per_itemtype = 1 if $notforloan;
1553     }
1554
1555     my $available_per_item = 1;
1556     $available_per_item = 0 if $item->{itemlost} or
1557                                ( $item->{notforloan} > 0 ) or
1558                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1559                                $item->{withdrawn} or
1560                                $notforloan_per_itemtype;
1561
1562
1563     if (C4::Context->preference('AllowOnShelfHolds')) {
1564         return $available_per_item;
1565     } else {
1566         return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "Waiting"));
1567     }
1568 }
1569
1570 =head2 AlterPriority
1571
1572   AlterPriority( $where, $reserve_id );
1573
1574 This function changes a reserve's priority up, down, to the top, or to the bottom.
1575 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1576
1577 =cut
1578
1579 sub AlterPriority {
1580     my ( $where, $reserve_id ) = @_;
1581
1582     my $dbh = C4::Context->dbh;
1583
1584     my $reserve = GetReserve( $reserve_id );
1585
1586     if ( $reserve->{cancellationdate} ) {
1587         warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (".$reserve->{cancellationdate}.')';
1588         return;
1589     }
1590
1591     if ( $where eq 'up' || $where eq 'down' ) {
1592
1593       my $priority = $reserve->{'priority'};
1594       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1595       _FixPriority({ reserve_id => $reserve_id, rank => $priority })
1596
1597     } elsif ( $where eq 'top' ) {
1598
1599       _FixPriority({ reserve_id => $reserve_id, rank => '1' })
1600
1601     } elsif ( $where eq 'bottom' ) {
1602
1603       _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1604
1605     }
1606 }
1607
1608 =head2 ToggleLowestPriority
1609
1610   ToggleLowestPriority( $borrowernumber, $biblionumber );
1611
1612 This function sets the lowestPriority field to true if is false, and false if it is true.
1613
1614 =cut
1615
1616 sub ToggleLowestPriority {
1617     my ( $reserve_id ) = @_;
1618
1619     my $dbh = C4::Context->dbh;
1620
1621     my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
1622     $sth->execute( $reserve_id );
1623     
1624     _FixPriority({ reserve_id => $reserve_id, rank => '999999' });
1625 }
1626
1627 =head2 ToggleSuspend
1628
1629   ToggleSuspend( $reserve_id );
1630
1631 This function sets the suspend field to true if is false, and false if it is true.
1632 If the reserve is currently suspended with a suspend_until date, that date will
1633 be cleared when it is unsuspended.
1634
1635 =cut
1636
1637 sub ToggleSuspend {
1638     my ( $reserve_id, $suspend_until ) = @_;
1639
1640     $suspend_until = output_pref(
1641         {
1642             dt         => dt_from_string($suspend_until),
1643             dateformat => 'iso',
1644             dateonly   => 1
1645         }
1646     ) if ($suspend_until);
1647
1648     my $do_until = ( $suspend_until ) ? '?' : 'NULL';
1649
1650     my $dbh = C4::Context->dbh;
1651
1652     my $sth = $dbh->prepare(
1653         "UPDATE reserves SET suspend = NOT suspend,
1654         suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
1655         WHERE reserve_id = ?
1656     ");
1657
1658     my @params;
1659     push( @params, $suspend_until ) if ( $suspend_until );
1660     push( @params, $reserve_id );
1661
1662     $sth->execute( @params );
1663 }
1664
1665 =head2 SuspendAll
1666
1667   SuspendAll(
1668       borrowernumber   => $borrowernumber,
1669       [ biblionumber   => $biblionumber, ]
1670       [ suspend_until  => $suspend_until, ]
1671       [ suspend        => $suspend ]
1672   );
1673
1674   This function accepts a set of hash keys as its parameters.
1675   It requires either borrowernumber or biblionumber, or both.
1676
1677   suspend_until is wholly optional.
1678
1679 =cut
1680
1681 sub SuspendAll {
1682     my %params = @_;
1683
1684     my $borrowernumber = $params{'borrowernumber'} || undef;
1685     my $biblionumber   = $params{'biblionumber'}   || undef;
1686     my $suspend_until  = $params{'suspend_until'}  || undef;
1687     my $suspend        = defined( $params{'suspend'} ) ? $params{'suspend'} :  1;
1688
1689     $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1690
1691     return unless ( $borrowernumber || $biblionumber );
1692
1693     my ( $query, $sth, $dbh, @query_params );
1694
1695     $query = "UPDATE reserves SET suspend = ? ";
1696     push( @query_params, $suspend );
1697     if ( !$suspend ) {
1698         $query .= ", suspend_until = NULL ";
1699     } elsif ( $suspend_until ) {
1700         $query .= ", suspend_until = ? ";
1701         push( @query_params, $suspend_until );
1702     }
1703     $query .= " WHERE ";
1704     if ( $borrowernumber ) {
1705         $query .= " borrowernumber = ? ";
1706         push( @query_params, $borrowernumber );
1707     }
1708     $query .= " AND " if ( $borrowernumber && $biblionumber );
1709     if ( $biblionumber ) {
1710         $query .= " biblionumber = ? ";
1711         push( @query_params, $biblionumber );
1712     }
1713     $query .= " AND found IS NULL ";
1714
1715     $dbh = C4::Context->dbh;
1716     $sth = $dbh->prepare( $query );
1717     $sth->execute( @query_params );
1718 }
1719
1720
1721 =head2 _FixPriority
1722
1723   _FixPriority({
1724     reserve_id => $reserve_id,
1725     [rank => $rank,]
1726     [ignoreSetLowestRank => $ignoreSetLowestRank]
1727   });
1728
1729   or
1730
1731   _FixPriority({ biblionumber => $biblionumber});
1732
1733 This routine adjusts the priority of a hold request and holds
1734 on the same bib.
1735
1736 In the first form, where a reserve_id is passed, the priority of the
1737 hold is set to supplied rank, and other holds for that bib are adjusted
1738 accordingly.  If the rank is "del", the hold is cancelled.  If no rank
1739 is supplied, all of the holds on that bib have their priority adjusted
1740 as if the second form had been used.
1741
1742 In the second form, where a biblionumber is passed, the holds on that
1743 bib (that are not captured) are sorted in order of increasing priority,
1744 then have reserves.priority set so that the first non-captured hold
1745 has its priority set to 1, the second non-captured hold has its priority
1746 set to 2, and so forth.
1747
1748 In both cases, holds that have the lowestPriority flag on are have their
1749 priority adjusted to ensure that they remain at the end of the line.
1750
1751 Note that the ignoreSetLowestRank parameter is meant to be used only
1752 when _FixPriority calls itself.
1753
1754 =cut
1755
1756 sub _FixPriority {
1757     my ( $params ) = @_;
1758     my $reserve_id = $params->{reserve_id};
1759     my $rank = $params->{rank} // '';
1760     my $ignoreSetLowestRank = $params->{ignoreSetLowestRank};
1761     my $biblionumber = $params->{biblionumber};
1762
1763     my $dbh = C4::Context->dbh;
1764
1765     unless ( $biblionumber ) {
1766         my $res = GetReserve( $reserve_id );
1767         $biblionumber = $res->{biblionumber};
1768     }
1769
1770     if ( $rank eq "del" ) {
1771          CancelReserve({ reserve_id => $reserve_id });
1772     }
1773     elsif ( $rank eq "W" || $rank eq "0" ) {
1774
1775         # make sure priority for waiting or in-transit items is 0
1776         my $query = "
1777             UPDATE reserves
1778             SET    priority = 0
1779             WHERE reserve_id = ?
1780             AND found IN ('W', 'T')
1781         ";
1782         my $sth = $dbh->prepare($query);
1783         $sth->execute( $reserve_id );
1784     }
1785     my @priority;
1786
1787     # get whats left
1788     my $query = "
1789         SELECT reserve_id, borrowernumber, reservedate, constrainttype
1790         FROM   reserves
1791         WHERE  biblionumber   = ?
1792           AND  ((found <> 'W' AND found <> 'T') OR found IS NULL)
1793         ORDER BY priority ASC
1794     ";
1795     my $sth = $dbh->prepare($query);
1796     $sth->execute( $biblionumber );
1797     while ( my $line = $sth->fetchrow_hashref ) {
1798         push( @priority,     $line );
1799     }
1800
1801     # To find the matching index
1802     my $i;
1803     my $key = -1;    # to allow for 0 to be a valid result
1804     for ( $i = 0 ; $i < @priority ; $i++ ) {
1805         if ( $reserve_id == $priority[$i]->{'reserve_id'} ) {
1806             $key = $i;    # save the index
1807             last;
1808         }
1809     }
1810
1811     # if index exists in array then move it to new position
1812     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1813         my $new_rank = $rank -
1814           1;    # $new_rank is what you want the new index to be in the array
1815         my $moving_item = splice( @priority, $key, 1 );
1816         splice( @priority, $new_rank, 0, $moving_item );
1817     }
1818
1819     # now fix the priority on those that are left....
1820     $query = "
1821         UPDATE reserves
1822         SET    priority = ?
1823         WHERE  reserve_id = ?
1824     ";
1825     $sth = $dbh->prepare($query);
1826     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1827         $sth->execute(
1828             $j + 1,
1829             $priority[$j]->{'reserve_id'}
1830         );
1831     }
1832     
1833     $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1834     $sth->execute();
1835     
1836     unless ( $ignoreSetLowestRank ) {
1837       while ( my $res = $sth->fetchrow_hashref() ) {
1838         _FixPriority({
1839             reserve_id => $res->{'reserve_id'},
1840             rank => '999999',
1841             ignoreSetLowestRank => 1
1842         });
1843       }
1844     }
1845 }
1846
1847 =head2 _Findgroupreserve
1848
1849   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber, $lookahead);
1850
1851 Looks for an item-specific match first, then for a title-level match, returning the
1852 first match found.  If neither, then we look for a 3rd kind of match based on
1853 reserve constraints.
1854 Lookahead is the number of days to look in advance.
1855
1856 TODO: add more explanation about reserve constraints
1857
1858 C<&_Findgroupreserve> returns :
1859 C<@results> is an array of references-to-hash whose keys are mostly
1860 fields from the reserves table of the Koha database, plus
1861 C<biblioitemnumber>.
1862
1863 =cut
1864
1865 sub _Findgroupreserve {
1866     my ( $bibitem, $biblio, $itemnumber, $lookahead) = @_;
1867     my $dbh   = C4::Context->dbh;
1868
1869     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1870     # check for exact targetted match
1871     my $item_level_target_query = qq/
1872         SELECT reserves.biblionumber        AS biblionumber,
1873                reserves.borrowernumber      AS borrowernumber,
1874                reserves.reservedate         AS reservedate,
1875                reserves.branchcode          AS branchcode,
1876                reserves.cancellationdate    AS cancellationdate,
1877                reserves.found               AS found,
1878                reserves.reservenotes        AS reservenotes,
1879                reserves.priority            AS priority,
1880                reserves.timestamp           AS timestamp,
1881                biblioitems.biblioitemnumber AS biblioitemnumber,
1882                reserves.itemnumber          AS itemnumber,
1883                reserves.reserve_id          AS reserve_id
1884         FROM reserves
1885         JOIN biblioitems USING (biblionumber)
1886         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1887         WHERE found IS NULL
1888         AND priority > 0
1889         AND item_level_request = 1
1890         AND itemnumber = ?
1891         AND reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1892         AND suspend = 0
1893         ORDER BY priority
1894     /;
1895     my $sth = $dbh->prepare($item_level_target_query);
1896     $sth->execute($itemnumber, $lookahead||0);
1897     my @results;
1898     if ( my $data = $sth->fetchrow_hashref ) {
1899         push( @results, $data );
1900     }
1901     return @results if @results;
1902     
1903     # check for title-level targetted match
1904     my $title_level_target_query = qq/
1905         SELECT reserves.biblionumber        AS biblionumber,
1906                reserves.borrowernumber      AS borrowernumber,
1907                reserves.reservedate         AS reservedate,
1908                reserves.branchcode          AS branchcode,
1909                reserves.cancellationdate    AS cancellationdate,
1910                reserves.found               AS found,
1911                reserves.reservenotes        AS reservenotes,
1912                reserves.priority            AS priority,
1913                reserves.timestamp           AS timestamp,
1914                biblioitems.biblioitemnumber AS biblioitemnumber,
1915                reserves.itemnumber          AS itemnumber,
1916                reserves.reserve_id          AS reserve_id
1917         FROM reserves
1918         JOIN biblioitems USING (biblionumber)
1919         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1920         WHERE found IS NULL
1921         AND priority > 0
1922         AND item_level_request = 0
1923         AND hold_fill_targets.itemnumber = ?
1924         AND reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1925         AND suspend = 0
1926         ORDER BY priority
1927     /;
1928     $sth = $dbh->prepare($title_level_target_query);
1929     $sth->execute($itemnumber, $lookahead||0);
1930     @results = ();
1931     if ( my $data = $sth->fetchrow_hashref ) {
1932         push( @results, $data );
1933     }
1934     return @results if @results;
1935
1936     my $query = qq/
1937         SELECT reserves.biblionumber               AS biblionumber,
1938                reserves.borrowernumber             AS borrowernumber,
1939                reserves.reservedate                AS reservedate,
1940                reserves.waitingdate                AS waitingdate,
1941                reserves.branchcode                 AS branchcode,
1942                reserves.cancellationdate           AS cancellationdate,
1943                reserves.found                      AS found,
1944                reserves.reservenotes               AS reservenotes,
1945                reserves.priority                   AS priority,
1946                reserves.timestamp                  AS timestamp,
1947                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1948                reserves.itemnumber                 AS itemnumber,
1949                reserves.reserve_id                 AS reserve_id
1950         FROM reserves
1951           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1952         WHERE reserves.biblionumber = ?
1953           AND ( ( reserveconstraints.biblioitemnumber = ?
1954           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1955           AND reserves.reservedate    = reserveconstraints.reservedate )
1956           OR  reserves.constrainttype='a' )
1957           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1958           AND reserves.reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
1959           AND suspend = 0
1960           ORDER BY priority
1961     /;
1962     $sth = $dbh->prepare($query);
1963     $sth->execute( $biblio, $bibitem, $itemnumber, $lookahead||0);
1964     @results = ();
1965     while ( my $data = $sth->fetchrow_hashref ) {
1966         push( @results, $data );
1967     }
1968     return @results;
1969 }
1970
1971 =head2 _koha_notify_reserve
1972
1973   _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1974
1975 Sends a notification to the patron that their hold has been filled (through
1976 ModReserveAffect, _not_ ModReserveFill)
1977
1978 =cut
1979
1980 sub _koha_notify_reserve {
1981     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1982
1983     my $dbh = C4::Context->dbh;
1984     my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1985     
1986     # Try to get the borrower's email address
1987     my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber);
1988
1989     my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( {
1990             borrowernumber => $borrowernumber,
1991             message_name => 'Hold_Filled'
1992     } );
1993
1994     my $sth = $dbh->prepare("
1995         SELECT *
1996         FROM   reserves
1997         WHERE  borrowernumber = ?
1998             AND biblionumber = ?
1999     ");
2000     $sth->execute( $borrowernumber, $biblionumber );
2001     my $reserve = $sth->fetchrow_hashref;
2002     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
2003
2004     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
2005
2006     my %letter_params = (
2007         module => 'reserves',
2008         branchcode => $reserve->{branchcode},
2009         tables => {
2010             'branches'  => $branch_details,
2011             'borrowers' => $borrower,
2012             'biblio'    => $biblionumber,
2013             'reserves'  => $reserve,
2014             'items', $reserve->{'itemnumber'},
2015         },
2016         substitute => { today => C4::Dates->new()->output() },
2017     );
2018
2019     my $notification_sent = 0; #Keeping track if a Hold_filled message is sent. If no message can be sent, then default to a print message.
2020     my $send_notification = sub {
2021         my ( $mtt, $letter_code ) = (@_);
2022         return unless defined $letter_code;
2023         $letter_params{letter_code} = $letter_code;
2024         $letter_params{message_transport_type} = $mtt;
2025         my $letter =  C4::Letters::GetPreparedLetter ( %letter_params );
2026         unless ($letter) {
2027             warn "Could not find a letter called '$letter_params{'letter_code'}' for $mtt in the 'reserves' module";
2028             return;
2029         }
2030
2031         C4::Letters::EnqueueLetter( {
2032             letter => $letter,
2033             borrowernumber => $borrowernumber,
2034             from_address => $admin_email_address,
2035             message_transport_type => $mtt,
2036         } );
2037     };
2038
2039     while ( my ( $mtt, $letter_code ) = each %{ $messagingprefs->{transports} } ) {
2040         if ( ($mtt eq 'email' and not $to_address) or ($mtt eq 'sms' and not $borrower->{smsalertnumber}) ) {
2041             # email or sms is requested but not exist
2042             next;
2043         }
2044         &$send_notification($mtt, $letter_code);
2045         $notification_sent++;
2046     }
2047     #Making sure that a print notification is sent if no other transport types can be utilized.
2048     if (! $notification_sent) {
2049         &$send_notification('print', 'HOLD');
2050     }
2051     
2052 }
2053
2054 =head2 _ShiftPriorityByDateAndPriority
2055
2056   $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
2057
2058 This increments the priority of all reserves after the one
2059 with either the lowest date after C<$reservedate>
2060 or the lowest priority after C<$priority>.
2061
2062 It effectively makes room for a new reserve to be inserted with a certain
2063 priority, which is returned.
2064
2065 This is most useful when the reservedate can be set by the user.  It allows
2066 the new reserve to be placed before other reserves that have a later
2067 reservedate.  Since priority also is set by the form in reserves/request.pl
2068 the sub accounts for that too.
2069
2070 =cut
2071
2072 sub _ShiftPriorityByDateAndPriority {
2073     my ( $biblio, $resdate, $new_priority ) = @_;
2074
2075     my $dbh = C4::Context->dbh;
2076     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
2077     my $sth = $dbh->prepare( $query );
2078     $sth->execute( $biblio, $resdate, $new_priority );
2079     my $min_priority = $sth->fetchrow;
2080     # if no such matches are found, $new_priority remains as original value
2081     $new_priority = $min_priority if ( $min_priority );
2082
2083     # Shift the priority up by one; works in conjunction with the next SQL statement
2084     $query = "UPDATE reserves
2085               SET priority = priority+1
2086               WHERE biblionumber = ?
2087               AND borrowernumber = ?
2088               AND reservedate = ?
2089               AND found IS NULL";
2090     my $sth_update = $dbh->prepare( $query );
2091
2092     # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
2093     $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
2094     $sth = $dbh->prepare( $query );
2095     $sth->execute( $new_priority, $biblio );
2096     while ( my $row = $sth->fetchrow_hashref ) {
2097         $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
2098     }
2099
2100     return $new_priority;  # so the caller knows what priority they wind up receiving
2101 }
2102
2103 =head2 MoveReserve
2104
2105   MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
2106
2107 Use when checking out an item to handle reserves
2108 If $cancelreserve boolean is set to true, it will remove existing reserve
2109
2110 =cut
2111
2112 sub MoveReserve {
2113     my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
2114
2115     my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber );
2116     return unless $res;
2117
2118     my $biblionumber     =  $res->{biblionumber};
2119     my $biblioitemnumber = $res->{biblioitemnumber};
2120
2121     if ($res->{borrowernumber} == $borrowernumber) {
2122         ModReserveFill($res);
2123     }
2124     else {
2125         # warn "Reserved";
2126         # The item is reserved by someone else.
2127         # Find this item in the reserves
2128
2129         my $borr_res;
2130         foreach (@$all_reserves) {
2131             $_->{'borrowernumber'} == $borrowernumber or next;
2132             $_->{'biblionumber'}   == $biblionumber   or next;
2133
2134             $borr_res = $_;
2135             last;
2136         }
2137
2138         if ( $borr_res ) {
2139             # The item is reserved by the current patron
2140             ModReserveFill($borr_res);
2141         }
2142
2143         if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2144             RevertWaitingStatus({ itemnumber => $itemnumber });
2145         }
2146         elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2147             CancelReserve({
2148                 biblionumber   => $res->{'biblionumber'},
2149                 itemnumber     => $res->{'itemnumber'},
2150                 borrowernumber => $res->{'borrowernumber'}
2151             });
2152         }
2153     }
2154 }
2155
2156 =head2 MergeHolds
2157
2158   MergeHolds($dbh,$to_biblio, $from_biblio);
2159
2160 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2161
2162 =cut
2163
2164 sub MergeHolds {
2165     my ( $dbh, $to_biblio, $from_biblio ) = @_;
2166     my $sth = $dbh->prepare(
2167         "SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?"
2168     );
2169     $sth->execute($from_biblio);
2170     if ( my $data = $sth->fetchrow_hashref() ) {
2171
2172         # holds exist on old record, if not we don't need to do anything
2173         $sth = $dbh->prepare(
2174             "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2175         $sth->execute( $to_biblio, $from_biblio );
2176
2177         # Reorder by date
2178         # don't reorder those already waiting
2179
2180         $sth = $dbh->prepare(
2181 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2182         );
2183         my $upd_sth = $dbh->prepare(
2184 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2185         AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2186         );
2187         $sth->execute( $to_biblio, 'W', 'T' );
2188         my $priority = 1;
2189         while ( my $reserve = $sth->fetchrow_hashref() ) {
2190             $upd_sth->execute(
2191                 $priority,                    $to_biblio,
2192                 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2193                 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2194             );
2195             $priority++;
2196         }
2197     }
2198 }
2199
2200 =head2 RevertWaitingStatus
2201
2202   RevertWaitingStatus({ itemnumber => $itemnumber });
2203
2204   Reverts a 'waiting' hold back to a regular hold with a priority of 1.
2205
2206   Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
2207           item level hold, even if it was only a bibliolevel hold to
2208           begin with. This is because we can no longer know if a hold
2209           was item-level or bib-level after a hold has been set to
2210           waiting status.
2211
2212 =cut
2213
2214 sub RevertWaitingStatus {
2215     my ( $params ) = @_;
2216     my $itemnumber = $params->{'itemnumber'};
2217
2218     return unless ( $itemnumber );
2219
2220     my $dbh = C4::Context->dbh;
2221
2222     ## Get the waiting reserve we want to revert
2223     my $query = "
2224         SELECT * FROM reserves
2225         WHERE itemnumber = ?
2226         AND found IS NOT NULL
2227     ";
2228     my $sth = $dbh->prepare( $query );
2229     $sth->execute( $itemnumber );
2230     my $reserve = $sth->fetchrow_hashref();
2231
2232     ## Increment the priority of all other non-waiting
2233     ## reserves for this bib record
2234     $query = "
2235         UPDATE reserves
2236         SET
2237           priority = priority + 1
2238         WHERE
2239           biblionumber =  ?
2240         AND
2241           priority > 0
2242     ";
2243     $sth = $dbh->prepare( $query );
2244     $sth->execute( $reserve->{'biblionumber'} );
2245
2246     ## Fix up the currently waiting reserve
2247     $query = "
2248     UPDATE reserves
2249     SET
2250       priority = 1,
2251       found = NULL,
2252       waitingdate = NULL
2253     WHERE
2254       reserve_id = ?
2255     ";
2256     $sth = $dbh->prepare( $query );
2257     $sth->execute( $reserve->{'reserve_id'} );
2258     _FixPriority( { biblionumber => $reserve->{biblionumber} } );
2259 }
2260
2261 =head2 GetReserveId
2262
2263   $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber [, itemnumber => $itemnumber ] });
2264
2265   Returnes the first reserve id that matches the given criteria
2266
2267 =cut
2268
2269 sub GetReserveId {
2270     my ( $params ) = @_;
2271
2272     return unless ( ( $params->{'biblionumber'} || $params->{'itemnumber'} ) && $params->{'borrowernumber'} );
2273
2274     my $dbh = C4::Context->dbh();
2275
2276     my $sql = "SELECT reserve_id FROM reserves WHERE ";
2277
2278     my @params;
2279     my @limits;
2280     foreach my $key ( keys %$params ) {
2281         if ( defined( $params->{$key} ) ) {
2282             push( @limits, "$key = ?" );
2283             push( @params, $params->{$key} );
2284         }
2285     }
2286
2287     $sql .= join( " AND ", @limits );
2288
2289     my $sth = $dbh->prepare( $sql );
2290     $sth->execute( @params );
2291     my $row = $sth->fetchrow_hashref();
2292
2293     return $row->{'reserve_id'};
2294 }
2295
2296 =head2 ReserveSlip
2297
2298   ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2299
2300   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2301
2302 =cut
2303
2304 sub ReserveSlip {
2305     my ($branch, $borrowernumber, $biblionumber) = @_;
2306
2307 #   return unless ( C4::Context->boolean_preference('printreserveslips') );
2308
2309     my $reserve_id = GetReserveId({
2310         biblionumber => $biblionumber,
2311         borrowernumber => $borrowernumber
2312     }) or return;
2313     my $reserve = GetReserveInfo($reserve_id) or return;
2314
2315     return  C4::Letters::GetPreparedLetter (
2316         module => 'circulation',
2317         letter_code => 'RESERVESLIP',
2318         branchcode => $branch,
2319         tables => {
2320             'reserves'    => $reserve,
2321             'branches'    => $reserve->{branchcode},
2322             'borrowers'   => $reserve->{borrowernumber},
2323             'biblio'      => $reserve->{biblionumber},
2324             'items'       => $reserve->{itemnumber},
2325         },
2326     );
2327 }
2328
2329 =head2 GetReservesControlBranch
2330
2331   my $reserves_control_branch = GetReservesControlBranch($item, $borrower);
2332
2333   Return the branchcode to be used to determine which reserves
2334   policy applies to a transaction.
2335
2336   C<$item> is a hashref for an item. Only 'homebranch' is used.
2337
2338   C<$borrower> is a hashref to borrower. Only 'branchcode' is used.
2339
2340 =cut
2341
2342 sub GetReservesControlBranch {
2343     my ( $item, $borrower ) = @_;
2344
2345     my $reserves_control = C4::Context->preference('ReservesControlBranch');
2346
2347     my $branchcode =
2348         ( $reserves_control eq 'ItemHomeLibrary' ) ? $item->{'homebranch'}
2349       : ( $reserves_control eq 'PatronLibrary' )   ? $borrower->{'branchcode'}
2350       :                                              undef;
2351
2352     return $branchcode;
2353 }
2354
2355 =head2 CalculatePriority
2356
2357     my $p = CalculatePriority($biblionumber, $resdate);
2358
2359 Calculate priority for a new reserve on biblionumber, placing it at
2360 the end of the line of all holds whose start date falls before
2361 the current system time and that are neither on the hold shelf
2362 or in transit.
2363
2364 The reserve date parameter is optional; if it is supplied, the
2365 priority is based on the set of holds whose start date falls before
2366 the parameter value.
2367
2368 After calculation of this priority, it is recommended to call
2369 _ShiftPriorityByDateAndPriority. Note that this is currently done in
2370 AddReserves.
2371
2372 =cut
2373
2374 sub CalculatePriority {
2375     my ( $biblionumber, $resdate ) = @_;
2376
2377     my $sql = q{
2378         SELECT COUNT(*) FROM reserves
2379         WHERE biblionumber = ?
2380         AND   priority > 0
2381         AND   (found IS NULL OR found = '')
2382     };
2383     #skip found==W or found==T (waiting or transit holds)
2384     if( $resdate ) {
2385         $sql.= ' AND ( reservedate <= ? )';
2386     }
2387     else {
2388         $sql.= ' AND ( reservedate < NOW() )';
2389     }
2390     my $dbh = C4::Context->dbh();
2391     my @row = $dbh->selectrow_array(
2392         $sql,
2393         undef,
2394         $resdate ? ($biblionumber, $resdate) : ($biblionumber)
2395     );
2396
2397     return @row ? $row[0]+1 : 1;
2398 }
2399
2400 =head1 AUTHOR
2401
2402 Koha Development Team <http://koha-community.org/>
2403
2404 =cut
2405
2406 1;