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