Bug 2394: Use syspref canreservefromotherbranches in CanItemBeReserved
[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 0;
496     }
497
498     # If reservecount is ok, we check item branch if IndependentBranches is ON
499     # and canreservefromotherbranches is OFF
500     if ( C4::Context->preference('IndependentBranches')
501         and !C4::Context->preference('canreservefromotherbranches') )
502     {
503         my $itembranch = $item->{homebranch};
504         if ($itembranch ne $borrower->{branchcode}) {
505             return 0;
506         }
507     }
508
509     return 1;
510 }
511 #--------------------------------------------------------------------------------
512 =head2 GetReserveCount
513
514   $number = &GetReserveCount($borrowernumber);
515
516 this function returns the number of reservation for a borrower given on input arg.
517
518 =cut
519
520 sub GetReserveCount {
521     my ($borrowernumber) = @_;
522
523     my $dbh = C4::Context->dbh;
524
525     my $query = '
526         SELECT COUNT(*) AS counter
527         FROM reserves
528           WHERE borrowernumber = ?
529     ';
530     my $sth = $dbh->prepare($query);
531     $sth->execute($borrowernumber);
532     my $row = $sth->fetchrow_hashref;
533     return $row->{counter};
534 }
535
536 =head2 GetOtherReserves
537
538   ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
539
540 Check queued list of this document and check if this document must be  transfered
541
542 =cut
543
544 sub GetOtherReserves {
545     my ($itemnumber) = @_;
546     my $messages;
547     my $nextreservinfo;
548     my ( undef, $checkreserves, undef ) = CheckReserves($itemnumber);
549     if ($checkreserves) {
550         my $iteminfo = GetItem($itemnumber);
551         if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) {
552             $messages->{'transfert'} = $checkreserves->{'branchcode'};
553             #minus priorities of others reservs
554             ModReserveMinusPriority(
555                 $itemnumber,
556                 $checkreserves->{'borrowernumber'},
557                 $iteminfo->{'biblionumber'}
558             );
559
560             #launch the subroutine dotransfer
561             C4::Items::ModItemTransfer(
562                 $itemnumber,
563                 $iteminfo->{'holdingbranch'},
564                 $checkreserves->{'branchcode'}
565               ),
566               ;
567         }
568
569      #step 2b : case of a reservation on the same branch, set the waiting status
570         else {
571             $messages->{'waiting'} = 1;
572             ModReserveMinusPriority(
573                 $itemnumber,
574                 $checkreserves->{'borrowernumber'},
575                 $iteminfo->{'biblionumber'}
576             );
577             ModReserveStatus($itemnumber,'W');
578         }
579
580         $nextreservinfo = $checkreserves->{'borrowernumber'};
581     }
582
583     return ( $messages, $nextreservinfo );
584 }
585
586 =head2 GetReserveFee
587
588   $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
589
590 Calculate the fee for a reserve
591
592 =cut
593
594 sub GetReserveFee {
595     my ($borrowernumber, $biblionumber, $constraint, $bibitems ) = @_;
596
597     #check for issues;
598     my $dbh   = C4::Context->dbh;
599     my $const = lc substr( $constraint, 0, 1 );
600     my $query = qq/
601       SELECT * FROM borrowers
602     LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
603     WHERE borrowernumber = ?
604     /;
605     my $sth = $dbh->prepare($query);
606     $sth->execute($borrowernumber);
607     my $data = $sth->fetchrow_hashref;
608     $sth->finish();
609     my $fee      = $data->{'reservefee'};
610     my $cntitems = @- > $bibitems;
611
612     if ( $fee > 0 ) {
613
614         # check for items on issue
615         # first find biblioitem records
616         my @biblioitems;
617         my $sth1 = $dbh->prepare(
618             "SELECT * FROM biblio LEFT JOIN biblioitems on biblio.biblionumber = biblioitems.biblionumber
619                    WHERE (biblio.biblionumber = ?)"
620         );
621         $sth1->execute($biblionumber);
622         while ( my $data1 = $sth1->fetchrow_hashref ) {
623             if ( $const eq "a" ) {
624                 push @biblioitems, $data1;
625             }
626             else {
627                 my $found = 0;
628                 my $x     = 0;
629                 while ( $x < $cntitems ) {
630                     if ( @$bibitems->{'biblioitemnumber'} ==
631                         $data->{'biblioitemnumber'} )
632                     {
633                         $found = 1;
634                     }
635                     $x++;
636                 }
637                 if ( $const eq 'o' ) {
638                     if ( $found == 1 ) {
639                         push @biblioitems, $data1;
640                     }
641                 }
642                 else {
643                     if ( $found == 0 ) {
644                         push @biblioitems, $data1;
645                     }
646                 }
647             }
648         }
649         $sth1->finish;
650         my $cntitemsfound = @biblioitems;
651         my $issues        = 0;
652         my $x             = 0;
653         my $allissued     = 1;
654         while ( $x < $cntitemsfound ) {
655             my $bitdata = $biblioitems[$x];
656             my $sth2    = $dbh->prepare(
657                 "SELECT * FROM items
658                      WHERE biblioitemnumber = ?"
659             );
660             $sth2->execute( $bitdata->{'biblioitemnumber'} );
661             while ( my $itdata = $sth2->fetchrow_hashref ) {
662                 my $sth3 = $dbh->prepare(
663                     "SELECT * FROM issues
664                        WHERE itemnumber = ?"
665                 );
666                 $sth3->execute( $itdata->{'itemnumber'} );
667                 if ( my $isdata = $sth3->fetchrow_hashref ) {
668                 }
669                 else {
670                     $allissued = 0;
671                 }
672             }
673             $x++;
674         }
675         if ( $allissued == 0 ) {
676             my $rsth =
677               $dbh->prepare("SELECT * FROM reserves WHERE biblionumber = ?");
678             $rsth->execute($biblionumber);
679             if ( my $rdata = $rsth->fetchrow_hashref ) {
680             }
681             else {
682                 $fee = 0;
683             }
684         }
685     }
686     return $fee;
687 }
688
689 =head2 GetReservesToBranch
690
691   @transreserv = GetReservesToBranch( $frombranch );
692
693 Get reserve list for a given branch
694
695 =cut
696
697 sub GetReservesToBranch {
698     my ( $frombranch ) = @_;
699     my $dbh = C4::Context->dbh;
700     my $sth = $dbh->prepare(
701         "SELECT borrowernumber,reservedate,itemnumber,timestamp
702          FROM reserves 
703          WHERE priority='0' 
704            AND branchcode=?"
705     );
706     $sth->execute( $frombranch );
707     my @transreserv;
708     my $i = 0;
709     while ( my $data = $sth->fetchrow_hashref ) {
710         $transreserv[$i] = $data;
711         $i++;
712     }
713     return (@transreserv);
714 }
715
716 =head2 GetReservesForBranch
717
718   @transreserv = GetReservesForBranch($frombranch);
719
720 =cut
721
722 sub GetReservesForBranch {
723     my ($frombranch) = @_;
724     my $dbh          = C4::Context->dbh;
725         my $query        = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
726         FROM   reserves 
727         WHERE   priority='0'
728             AND found='W' ";
729     if ($frombranch){
730         $query .= " AND branchcode=? ";
731         }
732     $query .= "ORDER BY waitingdate" ;
733     my $sth = $dbh->prepare($query);
734     if ($frombranch){
735                 $sth->execute($frombranch);
736         }
737     else {
738                 $sth->execute();
739         }
740     my @transreserv;
741     my $i = 0;
742     while ( my $data = $sth->fetchrow_hashref ) {
743         $transreserv[$i] = $data;
744         $i++;
745     }
746     return (@transreserv);
747 }
748
749 =head2 GetReserveStatus
750
751   $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
752
753 Take an itemnumber or a biblionumber and return the status of the reserve places on it.
754 If several reserves exist, the reserve with the lower priority is given.
755
756 =cut
757
758 sub GetReserveStatus {
759     my ($itemnumber, $biblionumber) = @_;
760
761     my $dbh = C4::Context->dbh;
762
763     my ($sth, $found, $priority);
764     if ( $itemnumber ) {
765         $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
766         $sth->execute($itemnumber);
767         ($found, $priority) = $sth->fetchrow_array;
768     }
769
770     if ( $biblionumber and not defined $found and not defined $priority ) {
771         $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
772         $sth->execute($biblionumber);
773         ($found, $priority) = $sth->fetchrow_array;
774     }
775
776     if(defined $found) {
777         return 'Waiting'  if $found eq 'W' and $priority == 0;
778         return 'Finished' if $found eq 'F';
779         return 'Reserved' if $priority > 0;
780     }
781     return '';
782     #empty string here will remove need for checking undef, or less log lines
783 }
784
785 =head2 CheckReserves
786
787   ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber);
788   ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode);
789
790 Find a book in the reserves.
791
792 C<$itemnumber> is the book's item number.
793
794 As I understand it, C<&CheckReserves> looks for the given item in the
795 reserves. If it is found, that's a match, and C<$status> is set to
796 C<Waiting>.
797
798 Otherwise, it finds the most important item in the reserves with the
799 same biblio number as this book (I'm not clear on this) and returns it
800 with C<$status> set to C<Reserved>.
801
802 C<&CheckReserves> returns a two-element list:
803
804 C<$status> is either C<Waiting>, C<Reserved> (see above), or 0.
805
806 C<$reserve> is the reserve item that matched. It is a
807 reference-to-hash whose keys are mostly the fields of the reserves
808 table in the Koha database.
809
810 =cut
811
812 sub CheckReserves {
813     my ( $item, $barcode ) = @_;
814     my $dbh = C4::Context->dbh;
815     my $sth;
816     my $select;
817     if (C4::Context->preference('item-level_itypes')){
818         $select = "
819            SELECT items.biblionumber,
820            items.biblioitemnumber,
821            itemtypes.notforloan,
822            items.notforloan AS itemnotforloan,
823            items.itemnumber
824            FROM   items
825            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
826            LEFT JOIN itemtypes   ON items.itype   = itemtypes.itemtype
827         ";
828     }
829     else {
830         $select = "
831            SELECT items.biblionumber,
832            items.biblioitemnumber,
833            itemtypes.notforloan,
834            items.notforloan AS itemnotforloan,
835            items.itemnumber
836            FROM   items
837            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
838            LEFT JOIN itemtypes   ON biblioitems.itemtype   = itemtypes.itemtype
839         ";
840     }
841    
842     if ($item) {
843         $sth = $dbh->prepare("$select WHERE itemnumber = ?");
844         $sth->execute($item);
845     }
846     else {
847         $sth = $dbh->prepare("$select WHERE barcode = ?");
848         $sth->execute($barcode);
849     }
850     # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
851     my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
852
853     return ( '' ) unless $itemnumber; # bail if we got nothing.
854
855     # if item is not for loan it cannot be reserved either.....
856     #    execpt where items.notforloan < 0 :  This indicates the item is holdable. 
857     return ( '' ) if  ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
858
859     # Find this item in the reserves
860     my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
861
862     # $priority and $highest are used to find the most important item
863     # in the list returned by &_Findgroupreserve. (The lower $priority,
864     # the more important the item.)
865     # $highest is the most important item we've seen so far.
866     my $highest;
867     if (scalar @reserves) {
868         my $priority = 10000000;
869         foreach my $res (@reserves) {
870             if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
871                 return ( "Waiting", $res, \@reserves ); # Found it
872             } else {
873                 # See if this item is more important than what we've got so far
874                 if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
875                     my $borrowerinfo=C4::Members::GetMember(borrowernumber => $res->{'borrowernumber'});
876                     my $iteminfo=C4::Items::GetItem($itemnumber);
877                     my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo);
878                     my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'});
879                     next if ($branchitemrule->{'holdallowed'} == 0);
880                     next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'}));
881                     $priority = $res->{'priority'};
882                     $highest  = $res;
883                 }
884             }
885         }
886     }
887
888     # If we get this far, then no exact match was found.
889     # We return the most important (i.e. next) reservation.
890     if ($highest) {
891         $highest->{'itemnumber'} = $item;
892         return ( "Reserved", $highest, \@reserves );
893     }
894
895     return ( '' );
896 }
897
898 =head2 CancelExpiredReserves
899
900   CancelExpiredReserves();
901
902 Cancels all reserves with an expiration date from before today.
903
904 =cut
905
906 sub CancelExpiredReserves {
907
908     # Cancel reserves that have passed their expiration date.
909     my $dbh = C4::Context->dbh;
910     my $sth = $dbh->prepare( "
911         SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) 
912         AND expirationdate IS NOT NULL
913         AND found IS NULL
914     " );
915     $sth->execute();
916
917     while ( my $res = $sth->fetchrow_hashref() ) {
918         CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
919     }
920   
921     # Cancel reserves that have been waiting too long
922     if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) {
923         my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
924         my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge");
925
926         my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0";
927         $sth = $dbh->prepare( $query );
928         $sth->execute( $max_pickup_delay );
929
930         while (my $res = $sth->fetchrow_hashref ) {
931             if ( $charge ) {
932                 manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
933             }
934
935             CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
936         }
937     }
938
939 }
940
941 =head2 AutoUnsuspendReserves
942
943   AutoUnsuspendReserves();
944
945 Unsuspends all suspended reserves with a suspend_until date from before today.
946
947 =cut
948
949 sub AutoUnsuspendReserves {
950
951     my $dbh = C4::Context->dbh;
952
953     my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )";
954     my $sth = $dbh->prepare( $query );
955     $sth->execute();
956
957 }
958
959 =head2 CancelReserve
960
961   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
962
963 Cancels a reserve.
964
965 Use either C<$biblionumber> or C<$itemnumber> to specify the item to
966 cancel, but not both: if both are given, C<&CancelReserve> uses
967 C<$itemnumber>.
968
969 C<$borrowernumber> is the borrower number of the patron on whose
970 behalf the book was reserved.
971
972 If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
973 priorities of the other people who are waiting on the book.
974
975 =cut
976
977 sub CancelReserve {
978     my ( $biblio, $item, $borr ) = @_;
979     my $dbh = C4::Context->dbh;
980         if ( $item and $borr ) {
981         # removing a waiting reserve record....
982         # update the database...
983         my $query = "
984             UPDATE reserves
985             SET    cancellationdate = now(),
986                    found            = Null,
987                    priority         = 0
988             WHERE  itemnumber       = ?
989              AND   borrowernumber   = ?
990         ";
991         my $sth = $dbh->prepare($query);
992         $sth->execute( $item, $borr );
993         $sth->finish;
994         $query = "
995             INSERT INTO old_reserves
996             SELECT * FROM reserves
997             WHERE  itemnumber       = ?
998              AND   borrowernumber   = ?
999         ";
1000         $sth = $dbh->prepare($query);
1001         $sth->execute( $item, $borr );
1002         $query = "
1003             DELETE FROM reserves
1004             WHERE  itemnumber       = ?
1005              AND   borrowernumber   = ?
1006         ";
1007         $sth = $dbh->prepare($query);
1008         $sth->execute( $item, $borr );
1009     }
1010     else {
1011         # removing a reserve record....
1012         # get the prioritiy on this record....
1013         my $priority;
1014         my $query = qq/
1015             SELECT priority FROM reserves
1016             WHERE biblionumber   = ?
1017               AND borrowernumber = ?
1018               AND cancellationdate IS NULL
1019               AND itemnumber IS NULL
1020         /;
1021         my $sth = $dbh->prepare($query);
1022         $sth->execute( $biblio, $borr );
1023         ($priority) = $sth->fetchrow_array;
1024         $sth->finish;
1025         $query = qq/
1026             UPDATE reserves
1027             SET    cancellationdate = now(),
1028                    found            = Null,
1029                    priority         = 0
1030             WHERE  biblionumber     = ?
1031               AND  borrowernumber   = ?
1032         /;
1033
1034         # update the database, removing the record...
1035         $sth = $dbh->prepare($query);
1036         $sth->execute( $biblio, $borr );
1037         $sth->finish;
1038
1039         $query = qq/
1040             INSERT INTO old_reserves
1041             SELECT * FROM reserves
1042             WHERE  biblionumber     = ?
1043               AND  borrowernumber   = ?
1044         /;
1045         $sth = $dbh->prepare($query);
1046         $sth->execute( $biblio, $borr );
1047
1048         $query = qq/
1049             DELETE FROM reserves
1050             WHERE  biblionumber     = ?
1051               AND  borrowernumber   = ?
1052         /;
1053         $sth = $dbh->prepare($query);
1054         $sth->execute( $biblio, $borr );
1055
1056         # now fix the priority on the others....
1057         _FixPriority( $biblio, $borr );
1058     }
1059 }
1060
1061 =head2 ModReserve
1062
1063   ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
1064
1065 Change a hold request's priority or cancel it.
1066
1067 C<$rank> specifies the effect of the change.  If C<$rank>
1068 is 'W' or 'n', nothing happens.  This corresponds to leaving a
1069 request alone when changing its priority in the holds queue
1070 for a bib.
1071
1072 If C<$rank> is 'del', the hold request is cancelled.
1073
1074 If C<$rank> is an integer greater than zero, the priority of
1075 the request is set to that value.  Since priority != 0 means
1076 that the item is not waiting on the hold shelf, setting the 
1077 priority to a non-zero value also sets the request's found
1078 status and waiting date to NULL. 
1079
1080 The optional C<$itemnumber> parameter is used only when
1081 C<$rank> is a non-zero integer; if supplied, the itemnumber 
1082 of the hold request is set accordingly; if omitted, the itemnumber
1083 is cleared.
1084
1085 B<FIXME:> Note that the forgoing can have the effect of causing
1086 item-level hold requests to turn into title-level requests.  This
1087 will be fixed once reserves has separate columns for requested
1088 itemnumber and supplying itemnumber.
1089
1090 =cut
1091
1092 sub ModReserve {
1093     #subroutine to update a reserve
1094     my ( $rank, $biblio, $borrower, $branch , $itemnumber, $suspend_until) = @_;
1095      return if $rank eq "W";
1096      return if $rank eq "n";
1097     my $dbh = C4::Context->dbh;
1098     if ( $rank eq "del" ) {
1099         my $query = qq/
1100             UPDATE reserves
1101             SET    cancellationdate=now()
1102             WHERE  biblionumber   = ?
1103              AND   borrowernumber = ?
1104         /;
1105         my $sth = $dbh->prepare($query);
1106         $sth->execute( $biblio, $borrower );
1107         $sth->finish;
1108         $query = qq/
1109             INSERT INTO old_reserves
1110             SELECT *
1111             FROM   reserves 
1112             WHERE  biblionumber   = ?
1113              AND   borrowernumber = ?
1114         /;
1115         $sth = $dbh->prepare($query);
1116         $sth->execute( $biblio, $borrower );
1117         $query = qq/
1118             DELETE FROM reserves 
1119             WHERE  biblionumber   = ?
1120              AND   borrowernumber = ?
1121         /;
1122         $sth = $dbh->prepare($query);
1123         $sth->execute( $biblio, $borrower );
1124         
1125     }
1126     elsif ($rank =~ /^\d+/ and $rank > 0) {
1127         my $query = "
1128             UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
1129             WHERE biblionumber   = ?
1130             AND borrowernumber = ?
1131         ";
1132         my $sth = $dbh->prepare($query);
1133         $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
1134         $sth->finish;
1135
1136         if ( defined( $suspend_until ) ) {
1137             if ( $suspend_until ) {
1138                 $suspend_until = C4::Dates->new( $suspend_until )->output("iso");
1139                 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $suspend_until, $biblio, $borrower ) );
1140             } else {
1141                 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $biblio, $borrower ) );
1142             }
1143         }
1144
1145         _FixPriority( $biblio, $borrower, $rank);
1146     }
1147 }
1148
1149 =head2 ModReserveFill
1150
1151   &ModReserveFill($reserve);
1152
1153 Fill a reserve. If I understand this correctly, this means that the
1154 reserved book has been found and given to the patron who reserved it.
1155
1156 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1157 whose keys are fields from the reserves table in the Koha database.
1158
1159 =cut
1160
1161 sub ModReserveFill {
1162     my ($res) = @_;
1163     my $dbh = C4::Context->dbh;
1164     # fill in a reserve record....
1165     my $biblionumber = $res->{'biblionumber'};
1166     my $borrowernumber    = $res->{'borrowernumber'};
1167     my $resdate = $res->{'reservedate'};
1168
1169     # get the priority on this record....
1170     my $priority;
1171     my $query = "SELECT priority
1172                  FROM   reserves
1173                  WHERE  biblionumber   = ?
1174                   AND   borrowernumber = ?
1175                   AND   reservedate    = ?";
1176     my $sth = $dbh->prepare($query);
1177     $sth->execute( $biblionumber, $borrowernumber, $resdate );
1178     ($priority) = $sth->fetchrow_array;
1179     $sth->finish;
1180
1181     # update the database...
1182     $query = "UPDATE reserves
1183                   SET    found            = 'F',
1184                          priority         = 0
1185                  WHERE  biblionumber     = ?
1186                     AND reservedate      = ?
1187                     AND borrowernumber   = ?
1188                 ";
1189     $sth = $dbh->prepare($query);
1190     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1191     $sth->finish;
1192
1193     # move to old_reserves
1194     $query = "INSERT INTO old_reserves
1195                  SELECT * FROM reserves
1196                  WHERE  biblionumber     = ?
1197                     AND reservedate      = ?
1198                     AND borrowernumber   = ?
1199                 ";
1200     $sth = $dbh->prepare($query);
1201     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1202     $query = "DELETE FROM reserves
1203                  WHERE  biblionumber     = ?
1204                     AND reservedate      = ?
1205                     AND borrowernumber   = ?
1206                 ";
1207     $sth = $dbh->prepare($query);
1208     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1209     
1210     # now fix the priority on the others (if the priority wasn't
1211     # already sorted!)....
1212     unless ( $priority == 0 ) {
1213         _FixPriority( $biblionumber, $borrowernumber );
1214     }
1215 }
1216
1217 =head2 ModReserveStatus
1218
1219   &ModReserveStatus($itemnumber, $newstatus);
1220
1221 Update the reserve status for the active (priority=0) reserve.
1222
1223 $itemnumber is the itemnumber the reserve is on
1224
1225 $newstatus is the new status.
1226
1227 =cut
1228
1229 sub ModReserveStatus {
1230
1231     #first : check if we have a reservation for this item .
1232     my ($itemnumber, $newstatus) = @_;
1233     my $dbh = C4::Context->dbh;
1234
1235     my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1236     my $sth_set = $dbh->prepare($query);
1237     $sth_set->execute( $newstatus, $itemnumber );
1238
1239     if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1240       CartToShelf( $itemnumber );
1241     }
1242 }
1243
1244 =head2 ModReserveAffect
1245
1246   &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1247
1248 This function affect an item and a status for a given reserve
1249 The itemnumber parameter is used to find the biblionumber.
1250 with the biblionumber & the borrowernumber, we can affect the itemnumber
1251 to the correct reserve.
1252
1253 if $transferToDo is not set, then the status is set to "Waiting" as well.
1254 otherwise, a transfer is on the way, and the end of the transfer will 
1255 take care of the waiting status
1256
1257 =cut
1258
1259 sub ModReserveAffect {
1260     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1261     my $dbh = C4::Context->dbh;
1262
1263     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1264     # attached to $itemnumber
1265     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1266     $sth->execute($itemnumber);
1267     my ($biblionumber) = $sth->fetchrow;
1268
1269     # get request - need to find out if item is already
1270     # waiting in order to not send duplicate hold filled notifications
1271     my $request = GetReserveInfo($borrowernumber, $biblionumber);
1272     my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1273
1274     # If we affect a reserve that has to be transfered, don't set to Waiting
1275     my $query;
1276     if ($transferToDo) {
1277     $query = "
1278         UPDATE reserves
1279         SET    priority = 0,
1280                itemnumber = ?,
1281                found = 'T'
1282         WHERE borrowernumber = ?
1283           AND biblionumber = ?
1284     ";
1285     }
1286     else {
1287     # affect the reserve to Waiting as well.
1288         $query = "
1289             UPDATE reserves
1290             SET     priority = 0,
1291                     found = 'W',
1292                     waitingdate = NOW(),
1293                     itemnumber = ?
1294             WHERE borrowernumber = ?
1295               AND biblionumber = ?
1296         ";
1297     }
1298     $sth = $dbh->prepare($query);
1299     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1300     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1301
1302     if ( C4::Context->preference("ReturnToShelvingCart") ) {
1303       CartToShelf( $itemnumber );
1304     }
1305
1306     return;
1307 }
1308
1309 =head2 ModReserveCancelAll
1310
1311   ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1312
1313 function to cancel reserv,check other reserves, and transfer document if it's necessary
1314
1315 =cut
1316
1317 sub ModReserveCancelAll {
1318     my $messages;
1319     my $nextreservinfo;
1320     my ( $itemnumber, $borrowernumber ) = @_;
1321
1322     #step 1 : cancel the reservation
1323     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1324
1325     #step 2 launch the subroutine of the others reserves
1326     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1327
1328     return ( $messages, $nextreservinfo );
1329 }
1330
1331 =head2 ModReserveMinusPriority
1332
1333   &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1334
1335 Reduce the values of queuded list     
1336
1337 =cut
1338
1339 sub ModReserveMinusPriority {
1340     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1341
1342     #first step update the value of the first person on reserv
1343     my $dbh   = C4::Context->dbh;
1344     my $query = "
1345         UPDATE reserves
1346         SET    priority = 0 , itemnumber = ? 
1347         WHERE  borrowernumber=?
1348           AND  biblionumber=?
1349     ";
1350     my $sth_upd = $dbh->prepare($query);
1351     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1352     # second step update all others reservs
1353     _FixPriority($biblionumber, $borrowernumber, '0');
1354 }
1355
1356 =head2 GetReserveInfo
1357
1358   &GetReserveInfo($borrowernumber,$biblionumber);
1359
1360 Get item and borrower details for a current hold.
1361 Current implementation this query should have a single result.
1362
1363 =cut
1364
1365 sub GetReserveInfo {
1366         my ( $borrowernumber, $biblionumber ) = @_;
1367     my $dbh = C4::Context->dbh;
1368         my $strsth="SELECT 
1369                        reservedate, 
1370                        reservenotes, 
1371                        reserves.borrowernumber,
1372                                    reserves.biblionumber, 
1373                                    reserves.branchcode,
1374                                    reserves.waitingdate,
1375                                    notificationdate, 
1376                                    reminderdate, 
1377                                    priority, 
1378                                    found,
1379                                    firstname, 
1380                                    surname, 
1381                                    phone, 
1382                                    email, 
1383                                    address, 
1384                                    address2,
1385                                    cardnumber, 
1386                                    city, 
1387                                    zipcode,
1388                                    biblio.title, 
1389                                    biblio.author,
1390                                    items.holdingbranch, 
1391                                    items.itemcallnumber, 
1392                                    items.itemnumber,
1393                                    items.location, 
1394                                    barcode, 
1395                                    notes
1396                         FROM reserves 
1397                          LEFT JOIN items USING(itemnumber) 
1398                      LEFT JOIN borrowers USING(borrowernumber)
1399                      LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber) 
1400                         WHERE 
1401                                 reserves.borrowernumber=?
1402                                 AND reserves.biblionumber=?";
1403         my $sth = $dbh->prepare($strsth); 
1404         $sth->execute($borrowernumber,$biblionumber);
1405
1406         my $data = $sth->fetchrow_hashref;
1407         return $data;
1408
1409 }
1410
1411 =head2 IsAvailableForItemLevelRequest
1412
1413   my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1414
1415 Checks whether a given item record is available for an
1416 item-level hold request.  An item is available if
1417
1418 * it is not lost AND 
1419 * it is not damaged AND 
1420 * it is not withdrawn AND 
1421 * does not have a not for loan value > 0
1422
1423 Whether or not the item is currently on loan is 
1424 also checked - if the AllowOnShelfHolds system preference
1425 is ON, an item can be requested even if it is currently
1426 on loan to somebody else.  If the system preference
1427 is OFF, an item that is currently checked out cannot
1428 be the target of an item-level hold request.
1429
1430 Note that IsAvailableForItemLevelRequest() does not
1431 check if the staff operator is authorized to place
1432 a request on the item - in particular,
1433 this routine does not check IndependantBranches
1434 and canreservefromotherbranches.
1435
1436 =cut
1437
1438 sub IsAvailableForItemLevelRequest {
1439     my $itemnumber = shift;
1440    
1441     my $item = GetItem($itemnumber);
1442
1443     # must check the notforloan setting of the itemtype
1444     # FIXME - a lot of places in the code do this
1445     #         or something similar - need to be
1446     #         consolidated
1447     my $dbh = C4::Context->dbh;
1448     my $notforloan_query;
1449     if (C4::Context->preference('item-level_itypes')) {
1450         $notforloan_query = "SELECT itemtypes.notforloan
1451                              FROM items
1452                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1453                              WHERE itemnumber = ?";
1454     } else {
1455         $notforloan_query = "SELECT itemtypes.notforloan
1456                              FROM items
1457                              JOIN biblioitems USING (biblioitemnumber)
1458                              JOIN itemtypes USING (itemtype)
1459                              WHERE itemnumber = ?";
1460     }
1461     my $sth = $dbh->prepare($notforloan_query);
1462     $sth->execute($itemnumber);
1463     my $notforloan_per_itemtype = 0;
1464     if (my ($notforloan) = $sth->fetchrow_array) {
1465         $notforloan_per_itemtype = 1 if $notforloan;
1466     }
1467
1468     my $available_per_item = 1;
1469     $available_per_item = 0 if $item->{itemlost} or
1470                                ( $item->{notforloan} > 0 ) or
1471                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1472                                $item->{wthdrawn} or
1473                                $notforloan_per_itemtype;
1474
1475
1476     if (C4::Context->preference('AllowOnShelfHolds')) {
1477         return $available_per_item;
1478     } else {
1479         return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "Waiting"));
1480     }
1481 }
1482
1483 =head2 AlterPriority
1484
1485   AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1486
1487 This function changes a reserve's priority up, down, to the top, or to the bottom.
1488 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1489
1490 =cut
1491
1492 sub AlterPriority {
1493     my ( $where, $borrowernumber, $biblionumber ) = @_;
1494
1495     my $dbh = C4::Context->dbh;
1496
1497     ## Find this reserve
1498     my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1499     $sth->execute( $biblionumber, $borrowernumber );
1500     my $reserve = $sth->fetchrow_hashref();
1501     $sth->finish();
1502
1503     if ( $where eq 'up' || $where eq 'down' ) {
1504     
1505       my $priority = $reserve->{'priority'};        
1506       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1507       _FixPriority( $biblionumber, $borrowernumber, $priority )
1508
1509     } elsif ( $where eq 'top' ) {
1510
1511       _FixPriority( $biblionumber, $borrowernumber, '1' )
1512
1513     } elsif ( $where eq 'bottom' ) {
1514
1515       _FixPriority( $biblionumber, $borrowernumber, '999999' )
1516
1517     }
1518 }
1519
1520 =head2 ToggleLowestPriority
1521
1522   ToggleLowestPriority( $borrowernumber, $biblionumber );
1523
1524 This function sets the lowestPriority field to true if is false, and false if it is true.
1525
1526 =cut
1527
1528 sub ToggleLowestPriority {
1529     my ( $borrowernumber, $biblionumber ) = @_;
1530
1531     my $dbh = C4::Context->dbh;
1532
1533     my $sth = $dbh->prepare(
1534         "UPDATE reserves SET lowestPriority = NOT lowestPriority
1535          WHERE biblionumber = ?
1536          AND borrowernumber = ?"
1537     );
1538     $sth->execute(
1539         $biblionumber,
1540         $borrowernumber,
1541     );
1542     $sth->finish;
1543     
1544     _FixPriority( $biblionumber, $borrowernumber, '999999' );
1545 }
1546
1547 =head2 ToggleSuspend
1548
1549   ToggleSuspend( $borrowernumber, $biblionumber );
1550
1551 This function sets the suspend field to true if is false, and false if it is true.
1552 If the reserve is currently suspended with a suspend_until date, that date will
1553 be cleared when it is unsuspended.
1554
1555 =cut
1556
1557 sub ToggleSuspend {
1558     my ( $borrowernumber, $biblionumber, $suspend_until ) = @_;
1559
1560     $suspend_until = output_pref( dt_from_string( $suspend_until ), 'iso' ) if ( $suspend_until );
1561
1562     my $do_until = ( $suspend_until ) ? '?' : 'NULL';
1563
1564     my $dbh = C4::Context->dbh;
1565
1566     my $sth = $dbh->prepare(
1567         "UPDATE reserves SET suspend = NOT suspend,
1568         suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
1569         WHERE biblionumber = ?
1570         AND borrowernumber = ?
1571     ");
1572
1573     my @params;
1574     push( @params, $suspend_until ) if ( $suspend_until );
1575     push( @params, $biblionumber );
1576     push( @params, $borrowernumber );
1577
1578     $sth->execute( @params );
1579     $sth->finish;
1580 }
1581
1582 =head2 SuspendAll
1583
1584   SuspendAll(
1585       borrowernumber   => $borrowernumber,
1586       [ biblionumber   => $biblionumber, ]
1587       [ suspend_until  => $suspend_until, ]
1588       [ suspend        => $suspend ]
1589   );
1590
1591   This function accepts a set of hash keys as its parameters.
1592   It requires either borrowernumber or biblionumber, or both.
1593
1594   suspend_until is wholly optional.
1595
1596 =cut
1597
1598 sub SuspendAll {
1599     my %params = @_;
1600
1601     my $borrowernumber = $params{'borrowernumber'} || undef;
1602     my $biblionumber   = $params{'biblionumber'}   || undef;
1603     my $suspend_until  = $params{'suspend_until'}  || undef;
1604     my $suspend        = defined( $params{'suspend'} ) ? $params{'suspend'} :  1;
1605
1606     $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1607
1608     return unless ( $borrowernumber || $biblionumber );
1609
1610     my ( $query, $sth, $dbh, @query_params );
1611
1612     $query = "UPDATE reserves SET suspend = ? ";
1613     push( @query_params, $suspend );
1614     if ( !$suspend ) {
1615         $query .= ", suspend_until = NULL ";
1616     } elsif ( $suspend_until ) {
1617         $query .= ", suspend_until = ? ";
1618         push( @query_params, $suspend_until );
1619     }
1620     $query .= " WHERE ";
1621     if ( $borrowernumber ) {
1622         $query .= " borrowernumber = ? ";
1623         push( @query_params, $borrowernumber );
1624     }
1625     $query .= " AND " if ( $borrowernumber && $biblionumber );
1626     if ( $biblionumber ) {
1627         $query .= " biblionumber = ? ";
1628         push( @query_params, $biblionumber );
1629     }
1630     $query .= " AND found IS NULL ";
1631
1632     $dbh = C4::Context->dbh;
1633     $sth = $dbh->prepare( $query );
1634     $sth->execute( @query_params );
1635     $sth->finish;
1636 }
1637
1638
1639 =head2 _FixPriority
1640
1641   &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1642
1643 Only used internally (so don't export it)
1644 Changed how this functions works #
1645 Now just gets an array of reserves in the rank order and updates them with
1646 the array index (+1 as array starts from 0)
1647 and if $rank is supplied will splice item from the array and splice it back in again
1648 in new priority rank
1649
1650 =cut 
1651
1652 sub _FixPriority {
1653     my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1654     my $dbh = C4::Context->dbh;
1655      if ( $rank eq "del" ) {
1656          CancelReserve( $biblio, undef, $borrowernumber );
1657      }
1658     if ( $rank eq "W" || $rank eq "0" ) {
1659
1660         # make sure priority for waiting or in-transit items is 0
1661         my $query = qq/
1662             UPDATE reserves
1663             SET    priority = 0
1664             WHERE biblionumber = ?
1665               AND borrowernumber = ?
1666               AND found IN ('W', 'T')
1667         /;
1668         my $sth = $dbh->prepare($query);
1669         $sth->execute( $biblio, $borrowernumber );
1670     }
1671     my @priority;
1672     my @reservedates;
1673
1674     # get whats left
1675 # FIXME adding a new security in returned elements for changing priority,
1676 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1677         # This is wrong a waiting reserve has W set
1678         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1679     my $query = qq/
1680         SELECT borrowernumber, reservedate, constrainttype
1681         FROM   reserves
1682         WHERE  biblionumber   = ?
1683           AND  ((found <> 'W' AND found <> 'T') or found is NULL)
1684         ORDER BY priority ASC
1685     /;
1686     my $sth = $dbh->prepare($query);
1687     $sth->execute($biblio);
1688     while ( my $line = $sth->fetchrow_hashref ) {
1689         push( @reservedates, $line );
1690         push( @priority,     $line );
1691     }
1692
1693     # To find the matching index
1694     my $i;
1695     my $key = -1;    # to allow for 0 to be a valid result
1696     for ( $i = 0 ; $i < @priority ; $i++ ) {
1697         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1698             $key = $i;    # save the index
1699             last;
1700         }
1701     }
1702
1703     # if index exists in array then move it to new position
1704     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1705         my $new_rank = $rank -
1706           1;    # $new_rank is what you want the new index to be in the array
1707         my $moving_item = splice( @priority, $key, 1 );
1708         splice( @priority, $new_rank, 0, $moving_item );
1709     }
1710
1711     # now fix the priority on those that are left....
1712     $query = "
1713             UPDATE reserves
1714             SET    priority = ?
1715                 WHERE  biblionumber = ?
1716                  AND borrowernumber   = ?
1717                  AND reservedate = ?
1718          AND found IS NULL
1719     ";
1720     $sth = $dbh->prepare($query);
1721     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1722         $sth->execute(
1723             $j + 1, $biblio,
1724             $priority[$j]->{'borrowernumber'},
1725             $priority[$j]->{'reservedate'}
1726         );
1727         $sth->finish;
1728     }
1729     
1730     $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1731     $sth->execute();
1732     
1733     unless ( $ignoreSetLowestRank ) {
1734       while ( my $res = $sth->fetchrow_hashref() ) {
1735         _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1736       }
1737     }
1738 }
1739
1740 =head2 _Findgroupreserve
1741
1742   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1743
1744 Looks for an item-specific match first, then for a title-level match, returning the
1745 first match found.  If neither, then we look for a 3rd kind of match based on
1746 reserve constraints.
1747
1748 TODO: add more explanation about reserve constraints
1749
1750 C<&_Findgroupreserve> returns :
1751 C<@results> is an array of references-to-hash whose keys are mostly
1752 fields from the reserves table of the Koha database, plus
1753 C<biblioitemnumber>.
1754
1755 =cut
1756
1757 sub _Findgroupreserve {
1758     my ( $bibitem, $biblio, $itemnumber ) = @_;
1759     my $dbh   = C4::Context->dbh;
1760
1761     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1762     # check for exact targetted match
1763     my $item_level_target_query = qq/
1764         SELECT reserves.biblionumber        AS biblionumber,
1765                reserves.borrowernumber      AS borrowernumber,
1766                reserves.reservedate         AS reservedate,
1767                reserves.branchcode          AS branchcode,
1768                reserves.cancellationdate    AS cancellationdate,
1769                reserves.found               AS found,
1770                reserves.reservenotes        AS reservenotes,
1771                reserves.priority            AS priority,
1772                reserves.timestamp           AS timestamp,
1773                biblioitems.biblioitemnumber AS biblioitemnumber,
1774                reserves.itemnumber          AS itemnumber
1775         FROM reserves
1776         JOIN biblioitems USING (biblionumber)
1777         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1778         WHERE found IS NULL
1779         AND priority > 0
1780         AND item_level_request = 1
1781         AND itemnumber = ?
1782         AND reservedate <= CURRENT_DATE()
1783         AND suspend = 0
1784     /;
1785     my $sth = $dbh->prepare($item_level_target_query);
1786     $sth->execute($itemnumber);
1787     my @results;
1788     if ( my $data = $sth->fetchrow_hashref ) {
1789         push( @results, $data );
1790     }
1791     return @results if @results;
1792     
1793     # check for title-level targetted match
1794     my $title_level_target_query = qq/
1795         SELECT reserves.biblionumber        AS biblionumber,
1796                reserves.borrowernumber      AS borrowernumber,
1797                reserves.reservedate         AS reservedate,
1798                reserves.branchcode          AS branchcode,
1799                reserves.cancellationdate    AS cancellationdate,
1800                reserves.found               AS found,
1801                reserves.reservenotes        AS reservenotes,
1802                reserves.priority            AS priority,
1803                reserves.timestamp           AS timestamp,
1804                biblioitems.biblioitemnumber AS biblioitemnumber,
1805                reserves.itemnumber          AS itemnumber
1806         FROM reserves
1807         JOIN biblioitems USING (biblionumber)
1808         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1809         WHERE found IS NULL
1810         AND priority > 0
1811         AND item_level_request = 0
1812         AND hold_fill_targets.itemnumber = ?
1813         AND reservedate <= CURRENT_DATE()
1814         AND suspend = 0
1815     /;
1816     $sth = $dbh->prepare($title_level_target_query);
1817     $sth->execute($itemnumber);
1818     @results = ();
1819     if ( my $data = $sth->fetchrow_hashref ) {
1820         push( @results, $data );
1821     }
1822     return @results if @results;
1823
1824     my $query = qq/
1825         SELECT reserves.biblionumber               AS biblionumber,
1826                reserves.borrowernumber             AS borrowernumber,
1827                reserves.reservedate                AS reservedate,
1828                reserves.waitingdate                AS waitingdate,
1829                reserves.branchcode                 AS branchcode,
1830                reserves.cancellationdate           AS cancellationdate,
1831                reserves.found                      AS found,
1832                reserves.reservenotes               AS reservenotes,
1833                reserves.priority                   AS priority,
1834                reserves.timestamp                  AS timestamp,
1835                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1836                reserves.itemnumber                 AS itemnumber
1837         FROM reserves
1838           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1839         WHERE reserves.biblionumber = ?
1840           AND ( ( reserveconstraints.biblioitemnumber = ?
1841           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1842           AND reserves.reservedate    = reserveconstraints.reservedate )
1843           OR  reserves.constrainttype='a' )
1844           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1845           AND reserves.reservedate <= CURRENT_DATE()
1846           AND suspend = 0
1847     /;
1848     $sth = $dbh->prepare($query);
1849     $sth->execute( $biblio, $bibitem, $itemnumber );
1850     @results = ();
1851     while ( my $data = $sth->fetchrow_hashref ) {
1852         push( @results, $data );
1853     }
1854     return @results;
1855 }
1856
1857 =head2 _koha_notify_reserve
1858
1859   _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1860
1861 Sends a notification to the patron that their hold has been filled (through
1862 ModReserveAffect, _not_ ModReserveFill)
1863
1864 =cut
1865
1866 sub _koha_notify_reserve {
1867     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1868
1869     my $dbh = C4::Context->dbh;
1870     my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1871     
1872     # Try to get the borrower's email address
1873     my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber);
1874     
1875     my $letter_code;
1876     my $print_mode = 0;
1877     my $messagingprefs;
1878     if ( $to_address || $borrower->{'smsalertnumber'} ) {
1879         $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
1880     } else {
1881         $print_mode = 1;
1882     }
1883
1884     my $sth = $dbh->prepare("
1885         SELECT *
1886         FROM   reserves
1887         WHERE  borrowernumber = ?
1888             AND biblionumber = ?
1889     ");
1890     $sth->execute( $borrowernumber, $biblionumber );
1891     my $reserve = $sth->fetchrow_hashref;
1892     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1893
1894     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1895
1896     my %letter_params = (
1897         module => 'reserves',
1898         branchcode => $reserve->{branchcode},
1899         tables => {
1900             'branches'  => $branch_details,
1901             'borrowers' => $borrower,
1902             'biblio'    => $biblionumber,
1903             'reserves'  => $reserve,
1904             'items', $reserve->{'itemnumber'},
1905         },
1906         substitute => { today => C4::Dates->new()->output() },
1907     );
1908
1909
1910     if ( $print_mode ) {
1911         $letter_params{ 'letter_code' } = 'HOLD_PRINT';
1912         my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1913
1914         C4::Letters::EnqueueLetter( {
1915             letter => $letter,
1916             borrowernumber => $borrowernumber,
1917             message_transport_type => 'print',
1918         } );
1919         
1920         return;
1921     }
1922
1923     if ( $to_address && defined $messagingprefs->{transports}->{'email'} ) {
1924         $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'email'};
1925         my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1926
1927         C4::Letters::EnqueueLetter(
1928             {   letter                 => $letter,
1929                 borrowernumber         => $borrowernumber,
1930                 message_transport_type => 'email',
1931                 from_address           => $admin_email_address,
1932             }
1933         );
1934     }
1935
1936     if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports}->{'sms'} ) {
1937         $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'sms'};
1938         my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
1939
1940         C4::Letters::EnqueueLetter(
1941             {   letter                 => $letter,
1942                 borrowernumber         => $borrowernumber,
1943                 message_transport_type => 'sms',
1944             }
1945         );
1946     }
1947 }
1948
1949 =head2 _ShiftPriorityByDateAndPriority
1950
1951   $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1952
1953 This increments the priority of all reserves after the one
1954 with either the lowest date after C<$reservedate>
1955 or the lowest priority after C<$priority>.
1956
1957 It effectively makes room for a new reserve to be inserted with a certain
1958 priority, which is returned.
1959
1960 This is most useful when the reservedate can be set by the user.  It allows
1961 the new reserve to be placed before other reserves that have a later
1962 reservedate.  Since priority also is set by the form in reserves/request.pl
1963 the sub accounts for that too.
1964
1965 =cut
1966
1967 sub _ShiftPriorityByDateAndPriority {
1968     my ( $biblio, $resdate, $new_priority ) = @_;
1969
1970     my $dbh = C4::Context->dbh;
1971     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1972     my $sth = $dbh->prepare( $query );
1973     $sth->execute( $biblio, $resdate, $new_priority );
1974     my $min_priority = $sth->fetchrow;
1975     # if no such matches are found, $new_priority remains as original value
1976     $new_priority = $min_priority if ( $min_priority );
1977
1978     # Shift the priority up by one; works in conjunction with the next SQL statement
1979     $query = "UPDATE reserves
1980               SET priority = priority+1
1981               WHERE biblionumber = ?
1982               AND borrowernumber = ?
1983               AND reservedate = ?
1984               AND found IS NULL";
1985     my $sth_update = $dbh->prepare( $query );
1986
1987     # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1988     $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1989     $sth = $dbh->prepare( $query );
1990     $sth->execute( $new_priority, $biblio );
1991     while ( my $row = $sth->fetchrow_hashref ) {
1992         $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1993     }
1994
1995     return $new_priority;  # so the caller knows what priority they wind up receiving
1996 }
1997
1998 =head2 MoveReserve
1999
2000   MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
2001
2002 Use when checking out an item to handle reserves
2003 If $cancelreserve boolean is set to true, it will remove existing reserve
2004
2005 =cut
2006
2007 sub MoveReserve {
2008     my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
2009
2010     my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber );
2011     return unless $res;
2012
2013     my $biblionumber     =  $res->{biblionumber};
2014     my $biblioitemnumber = $res->{biblioitemnumber};
2015
2016     if ($res->{borrowernumber} == $borrowernumber) {
2017         ModReserveFill($res);
2018     }
2019     else {
2020         # warn "Reserved";
2021         # The item is reserved by someone else.
2022         # Find this item in the reserves
2023
2024         my $borr_res;
2025         foreach (@$all_reserves) {
2026             $_->{'borrowernumber'} == $borrowernumber or next;
2027             $_->{'biblionumber'}   == $biblionumber   or next;
2028
2029             $borr_res = $_;
2030             last;
2031         }
2032
2033         if ( $borr_res ) {
2034             # The item is reserved by the current patron
2035             ModReserveFill($borr_res);
2036         }
2037
2038         if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
2039             RevertWaitingStatus({ itemnumber => $itemnumber });
2040         }
2041         elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
2042             CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
2043             CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
2044         }
2045     }
2046 }
2047
2048 =head2 MergeHolds
2049
2050   MergeHolds($dbh,$to_biblio, $from_biblio);
2051
2052 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2053
2054 =cut
2055
2056 sub MergeHolds {
2057     my ( $dbh, $to_biblio, $from_biblio ) = @_;
2058     my $sth = $dbh->prepare(
2059         "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
2060     );
2061     $sth->execute($from_biblio);
2062     if ( my $data = $sth->fetchrow_hashref() ) {
2063
2064         # holds exist on old record, if not we don't need to do anything
2065         $sth = $dbh->prepare(
2066             "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2067         $sth->execute( $to_biblio, $from_biblio );
2068
2069         # Reorder by date
2070         # don't reorder those already waiting
2071
2072         $sth = $dbh->prepare(
2073 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2074         );
2075         my $upd_sth = $dbh->prepare(
2076 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2077         AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2078         );
2079         $sth->execute( $to_biblio, 'W', 'T' );
2080         my $priority = 1;
2081         while ( my $reserve = $sth->fetchrow_hashref() ) {
2082             $upd_sth->execute(
2083                 $priority,                    $to_biblio,
2084                 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2085                 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2086             );
2087             $priority++;
2088         }
2089     }
2090 }
2091
2092 =head2 RevertWaitingStatus
2093
2094   $success = RevertWaitingStatus({ itemnumber => $itemnumber });
2095
2096   Reverts a 'waiting' hold back to a regular hold with a priority of 1.
2097
2098   Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
2099           item level hold, even if it was only a bibliolevel hold to
2100           begin with. This is because we can no longer know if a hold
2101           was item-level or bib-level after a hold has been set to
2102           waiting status.
2103
2104 =cut
2105
2106 sub RevertWaitingStatus {
2107     my ( $params ) = @_;
2108     my $itemnumber = $params->{'itemnumber'};
2109
2110     return unless ( $itemnumber );
2111
2112     my $dbh = C4::Context->dbh;
2113
2114     ## Get the waiting reserve we want to revert
2115     my $query = "
2116         SELECT * FROM reserves
2117         WHERE itemnumber = ?
2118         AND found IS NOT NULL
2119     ";
2120     my $sth = $dbh->prepare( $query );
2121     $sth->execute( $itemnumber );
2122     my $reserve = $sth->fetchrow_hashref();
2123
2124     ## Increment the priority of all other non-waiting
2125     ## reserves for this bib record
2126     $query = "
2127         UPDATE reserves
2128         SET
2129           priority = priority + 1
2130         WHERE
2131           biblionumber =  ?
2132         AND
2133           priority > 0
2134     ";
2135     $sth = $dbh->prepare( $query );
2136     $sth->execute( $reserve->{'biblionumber'} );
2137
2138     ## Fix up the currently waiting reserve
2139     $query = "
2140     UPDATE reserves
2141     SET
2142       priority = 1,
2143       found = NULL,
2144       waitingdate = NULL
2145     WHERE
2146       reserve_id = ?
2147     ";
2148     $sth = $dbh->prepare( $query );
2149     return $sth->execute( $reserve->{'reserve_id'} );
2150 }
2151
2152 =head2 ReserveSlip
2153
2154   ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2155
2156   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2157
2158 =cut
2159
2160 sub ReserveSlip {
2161     my ($branch, $borrowernumber, $biblionumber) = @_;
2162
2163 #   return unless ( C4::Context->boolean_preference('printreserveslips') );
2164
2165     my $reserve = GetReserveInfo($borrowernumber,$biblionumber )
2166       or return;
2167
2168     return  C4::Letters::GetPreparedLetter (
2169         module => 'circulation',
2170         letter_code => 'RESERVESLIP',
2171         branchcode => $branch,
2172         tables => {
2173             'reserves'    => $reserve,
2174             'branches'    => $reserve->{branchcode},
2175             'borrowers'   => $reserve->{borrowernumber},
2176             'biblio'      => $reserve->{biblionumber},
2177             'items'       => $reserve->{itemnumber},
2178         },
2179     );
2180 }
2181
2182 =head1 AUTHOR
2183
2184 Koha Development Team <http://koha-community.org/>
2185
2186 =cut
2187
2188 1;