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