[SIGNED-OFF]Bug 2780 - Capitalize strings consistently (reports)
[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                 $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $suspend_until, $biblio, $borrower ) );
1102             } else {
1103                 $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $biblio, $borrower ) );
1104             }
1105         }
1106
1107         _FixPriority( $biblio, $borrower, $rank);
1108     }
1109 }
1110
1111 =head2 ModReserveFill
1112
1113   &ModReserveFill($reserve);
1114
1115 Fill a reserve. If I understand this correctly, this means that the
1116 reserved book has been found and given to the patron who reserved it.
1117
1118 C<$reserve> specifies the reserve to fill. It is a reference-to-hash
1119 whose keys are fields from the reserves table in the Koha database.
1120
1121 =cut
1122
1123 sub ModReserveFill {
1124     my ($res) = @_;
1125     my $dbh = C4::Context->dbh;
1126     # fill in a reserve record....
1127     my $biblionumber = $res->{'biblionumber'};
1128     my $borrowernumber    = $res->{'borrowernumber'};
1129     my $resdate = $res->{'reservedate'};
1130
1131     # get the priority on this record....
1132     my $priority;
1133     my $query = "SELECT priority
1134                  FROM   reserves
1135                  WHERE  biblionumber   = ?
1136                   AND   borrowernumber = ?
1137                   AND   reservedate    = ?";
1138     my $sth = $dbh->prepare($query);
1139     $sth->execute( $biblionumber, $borrowernumber, $resdate );
1140     ($priority) = $sth->fetchrow_array;
1141     $sth->finish;
1142
1143     # update the database...
1144     $query = "UPDATE reserves
1145                   SET    found            = 'F',
1146                          priority         = 0
1147                  WHERE  biblionumber     = ?
1148                     AND reservedate      = ?
1149                     AND borrowernumber   = ?
1150                 ";
1151     $sth = $dbh->prepare($query);
1152     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1153     $sth->finish;
1154
1155     # move to old_reserves
1156     $query = "INSERT INTO old_reserves
1157                  SELECT * FROM reserves
1158                  WHERE  biblionumber     = ?
1159                     AND reservedate      = ?
1160                     AND borrowernumber   = ?
1161                 ";
1162     $sth = $dbh->prepare($query);
1163     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1164     $query = "DELETE FROM reserves
1165                  WHERE  biblionumber     = ?
1166                     AND reservedate      = ?
1167                     AND borrowernumber   = ?
1168                 ";
1169     $sth = $dbh->prepare($query);
1170     $sth->execute( $biblionumber, $resdate, $borrowernumber );
1171     
1172     # now fix the priority on the others (if the priority wasn't
1173     # already sorted!)....
1174     unless ( $priority == 0 ) {
1175         _FixPriority( $biblionumber, $borrowernumber );
1176     }
1177 }
1178
1179 =head2 ModReserveStatus
1180
1181   &ModReserveStatus($itemnumber, $newstatus);
1182
1183 Update the reserve status for the active (priority=0) reserve.
1184
1185 $itemnumber is the itemnumber the reserve is on
1186
1187 $newstatus is the new status.
1188
1189 =cut
1190
1191 sub ModReserveStatus {
1192
1193     #first : check if we have a reservation for this item .
1194     my ($itemnumber, $newstatus) = @_;
1195     my $dbh = C4::Context->dbh;
1196
1197     my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0";
1198     my $sth_set = $dbh->prepare($query);
1199     $sth_set->execute( $newstatus, $itemnumber );
1200
1201     if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
1202       CartToShelf( $itemnumber );
1203     }
1204 }
1205
1206 =head2 ModReserveAffect
1207
1208   &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
1209
1210 This function affect an item and a status for a given reserve
1211 The itemnumber parameter is used to find the biblionumber.
1212 with the biblionumber & the borrowernumber, we can affect the itemnumber
1213 to the correct reserve.
1214
1215 if $transferToDo is not set, then the status is set to "Waiting" as well.
1216 otherwise, a transfer is on the way, and the end of the transfer will 
1217 take care of the waiting status
1218
1219 =cut
1220
1221 sub ModReserveAffect {
1222     my ( $itemnumber, $borrowernumber,$transferToDo ) = @_;
1223     my $dbh = C4::Context->dbh;
1224
1225     # we want to attach $itemnumber to $borrowernumber, find the biblionumber
1226     # attached to $itemnumber
1227     my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?");
1228     $sth->execute($itemnumber);
1229     my ($biblionumber) = $sth->fetchrow;
1230
1231     # get request - need to find out if item is already
1232     # waiting in order to not send duplicate hold filled notifications
1233     my $request = GetReserveInfo($borrowernumber, $biblionumber);
1234     my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0;
1235
1236     # If we affect a reserve that has to be transfered, don't set to Waiting
1237     my $query;
1238     if ($transferToDo) {
1239     $query = "
1240         UPDATE reserves
1241         SET    priority = 0,
1242                itemnumber = ?,
1243                found = 'T'
1244         WHERE borrowernumber = ?
1245           AND biblionumber = ?
1246     ";
1247     }
1248     else {
1249     # affect the reserve to Waiting as well.
1250         $query = "
1251             UPDATE reserves
1252             SET     priority = 0,
1253                     found = 'W',
1254                     waitingdate = NOW(),
1255                     itemnumber = ?
1256             WHERE borrowernumber = ?
1257               AND biblionumber = ?
1258         ";
1259     }
1260     $sth = $dbh->prepare($query);
1261     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
1262     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
1263
1264     if ( C4::Context->preference("ReturnToShelvingCart") ) {
1265       CartToShelf( $itemnumber );
1266     }
1267
1268     return;
1269 }
1270
1271 =head2 ModReserveCancelAll
1272
1273   ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
1274
1275 function to cancel reserv,check other reserves, and transfer document if it's necessary
1276
1277 =cut
1278
1279 sub ModReserveCancelAll {
1280     my $messages;
1281     my $nextreservinfo;
1282     my ( $itemnumber, $borrowernumber ) = @_;
1283
1284     #step 1 : cancel the reservation
1285     my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
1286
1287     #step 2 launch the subroutine of the others reserves
1288     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
1289
1290     return ( $messages, $nextreservinfo );
1291 }
1292
1293 =head2 ModReserveMinusPriority
1294
1295   &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
1296
1297 Reduce the values of queuded list     
1298
1299 =cut
1300
1301 sub ModReserveMinusPriority {
1302     my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
1303
1304     #first step update the value of the first person on reserv
1305     my $dbh   = C4::Context->dbh;
1306     my $query = "
1307         UPDATE reserves
1308         SET    priority = 0 , itemnumber = ? 
1309         WHERE  borrowernumber=?
1310           AND  biblionumber=?
1311     ";
1312     my $sth_upd = $dbh->prepare($query);
1313     $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
1314     # second step update all others reservs
1315     _FixPriority($biblionumber, $borrowernumber, '0');
1316 }
1317
1318 =head2 GetReserveInfo
1319
1320   &GetReserveInfo($borrowernumber,$biblionumber);
1321
1322 Get item and borrower details for a current hold.
1323 Current implementation this query should have a single result.
1324
1325 =cut
1326
1327 sub GetReserveInfo {
1328         my ( $borrowernumber, $biblionumber ) = @_;
1329     my $dbh = C4::Context->dbh;
1330         my $strsth="SELECT 
1331                        reservedate, 
1332                        reservenotes, 
1333                        reserves.borrowernumber,
1334                                    reserves.biblionumber, 
1335                                    reserves.branchcode,
1336                                    reserves.waitingdate,
1337                                    notificationdate, 
1338                                    reminderdate, 
1339                                    priority, 
1340                                    found,
1341                                    firstname, 
1342                                    surname, 
1343                                    phone, 
1344                                    email, 
1345                                    address, 
1346                                    address2,
1347                                    cardnumber, 
1348                                    city, 
1349                                    zipcode,
1350                                    biblio.title, 
1351                                    biblio.author,
1352                                    items.holdingbranch, 
1353                                    items.itemcallnumber, 
1354                                    items.itemnumber,
1355                                    items.location, 
1356                                    barcode, 
1357                                    notes
1358                         FROM reserves 
1359                          LEFT JOIN items USING(itemnumber) 
1360                      LEFT JOIN borrowers USING(borrowernumber)
1361                      LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber) 
1362                         WHERE 
1363                                 reserves.borrowernumber=?
1364                                 AND reserves.biblionumber=?";
1365         my $sth = $dbh->prepare($strsth); 
1366         $sth->execute($borrowernumber,$biblionumber);
1367
1368         my $data = $sth->fetchrow_hashref;
1369         return $data;
1370
1371 }
1372
1373 =head2 IsAvailableForItemLevelRequest
1374
1375   my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1376
1377 Checks whether a given item record is available for an
1378 item-level hold request.  An item is available if
1379
1380 * it is not lost AND 
1381 * it is not damaged AND 
1382 * it is not withdrawn AND 
1383 * does not have a not for loan value > 0
1384
1385 Whether or not the item is currently on loan is 
1386 also checked - if the AllowOnShelfHolds system preference
1387 is ON, an item can be requested even if it is currently
1388 on loan to somebody else.  If the system preference
1389 is OFF, an item that is currently checked out cannot
1390 be the target of an item-level hold request.
1391
1392 Note that IsAvailableForItemLevelRequest() does not
1393 check if the staff operator is authorized to place
1394 a request on the item - in particular,
1395 this routine does not check IndependantBranches
1396 and canreservefromotherbranches.
1397
1398 =cut
1399
1400 sub IsAvailableForItemLevelRequest {
1401     my $itemnumber = shift;
1402    
1403     my $item = GetItem($itemnumber);
1404
1405     # must check the notforloan setting of the itemtype
1406     # FIXME - a lot of places in the code do this
1407     #         or something similar - need to be
1408     #         consolidated
1409     my $dbh = C4::Context->dbh;
1410     my $notforloan_query;
1411     if (C4::Context->preference('item-level_itypes')) {
1412         $notforloan_query = "SELECT itemtypes.notforloan
1413                              FROM items
1414                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1415                              WHERE itemnumber = ?";
1416     } else {
1417         $notforloan_query = "SELECT itemtypes.notforloan
1418                              FROM items
1419                              JOIN biblioitems USING (biblioitemnumber)
1420                              JOIN itemtypes USING (itemtype)
1421                              WHERE itemnumber = ?";
1422     }
1423     my $sth = $dbh->prepare($notforloan_query);
1424     $sth->execute($itemnumber);
1425     my $notforloan_per_itemtype = 0;
1426     if (my ($notforloan) = $sth->fetchrow_array) {
1427         $notforloan_per_itemtype = 1 if $notforloan;
1428     }
1429
1430     my $available_per_item = 1;
1431     $available_per_item = 0 if $item->{itemlost} or
1432                                ( $item->{notforloan} > 0 ) or
1433                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1434                                $item->{wthdrawn} or
1435                                $notforloan_per_itemtype;
1436
1437
1438     if (C4::Context->preference('AllowOnShelfHolds')) {
1439         return $available_per_item;
1440     } else {
1441         return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); 
1442     }
1443 }
1444
1445 =head2 AlterPriority
1446
1447   AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1448
1449 This function changes a reserve's priority up, down, to the top, or to the bottom.
1450 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1451
1452 =cut
1453
1454 sub AlterPriority {
1455     my ( $where, $borrowernumber, $biblionumber ) = @_;
1456
1457     my $dbh = C4::Context->dbh;
1458
1459     ## Find this reserve
1460     my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1461     $sth->execute( $biblionumber, $borrowernumber );
1462     my $reserve = $sth->fetchrow_hashref();
1463     $sth->finish();
1464
1465     if ( $where eq 'up' || $where eq 'down' ) {
1466     
1467       my $priority = $reserve->{'priority'};        
1468       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1469       _FixPriority( $biblionumber, $borrowernumber, $priority )
1470
1471     } elsif ( $where eq 'top' ) {
1472
1473       _FixPriority( $biblionumber, $borrowernumber, '1' )
1474
1475     } elsif ( $where eq 'bottom' ) {
1476
1477       _FixPriority( $biblionumber, $borrowernumber, '999999' )
1478
1479     }
1480 }
1481
1482 =head2 ToggleLowestPriority
1483
1484   ToggleLowestPriority( $borrowernumber, $biblionumber );
1485
1486 This function sets the lowestPriority field to true if is false, and false if it is true.
1487
1488 =cut
1489
1490 sub ToggleLowestPriority {
1491     my ( $borrowernumber, $biblionumber ) = @_;
1492
1493     my $dbh = C4::Context->dbh;
1494
1495     my $sth = $dbh->prepare(
1496         "UPDATE reserves SET lowestPriority = NOT lowestPriority
1497          WHERE biblionumber = ?
1498          AND borrowernumber = ?"
1499     );
1500     $sth->execute(
1501         $biblionumber,
1502         $borrowernumber,
1503     );
1504     $sth->finish;
1505     
1506     _FixPriority( $biblionumber, $borrowernumber, '999999' );
1507 }
1508
1509 =head2 ToggleSuspend
1510
1511   ToggleSuspend( $borrowernumber, $biblionumber );
1512
1513 This function sets the suspend field to true if is false, and false if it is true.
1514 If the reserve is currently suspended with a suspend_until date, that date will
1515 be cleared when it is unsuspended.
1516
1517 =cut
1518
1519 sub ToggleSuspend {
1520     my ( $borrowernumber, $biblionumber ) = @_;
1521
1522     my $dbh = C4::Context->dbh;
1523
1524     my $sth = $dbh->prepare(
1525         "UPDATE reserves SET suspend = NOT suspend,
1526         suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE suspend_until END
1527         WHERE biblionumber = ?
1528         AND borrowernumber = ?
1529     ");
1530     $sth->execute(
1531         $biblionumber,
1532         $borrowernumber,
1533     );
1534     $sth->finish;
1535 }
1536
1537 =head2 SuspendAll
1538
1539   SuspendAll(
1540       borrowernumber   => $borrowernumber,
1541       [ biblionumber   => $biblionumber, ]
1542       [ suspend_until  => $suspend_until, ]
1543       [ suspend        => $suspend ]
1544   );
1545
1546   This function accepts a set of hash keys as its parameters.
1547   It requires either borrowernumber or biblionumber, or both.
1548
1549   suspend_until is wholly optional.
1550
1551 =cut
1552
1553 sub SuspendAll {
1554     my %params = @_;
1555
1556     my $borrowernumber = $params{'borrowernumber'} || undef;
1557     my $biblionumber   = $params{'biblionumber'}   || undef;
1558     my $suspend_until  = $params{'suspend_until'}  || undef;
1559     my $suspend        = defined( $params{'suspend'} ) ? $params{'suspend'} :  1;
1560
1561     $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) );
1562
1563     return unless ( $borrowernumber || $biblionumber );
1564
1565     my ( $query, $sth, $dbh, @query_params );
1566
1567     $query = "UPDATE reserves SET suspend = ? ";
1568     push( @query_params, $suspend );
1569     if ( !$suspend ) {
1570         $query .= ", suspend_until = NULL ";
1571     } elsif ( $suspend_until ) {
1572         $query .= ", suspend_until = ? ";
1573         push( @query_params, $suspend_until );
1574     }
1575     $query .= " WHERE ";
1576     if ( $borrowernumber ) {
1577         $query .= " borrowernumber = ? ";
1578         push( @query_params, $borrowernumber );
1579     }
1580     $query .= " AND " if ( $borrowernumber && $biblionumber );
1581     if ( $biblionumber ) {
1582         $query .= " biblionumber = ? ";
1583         push( @query_params, $biblionumber );
1584     }
1585     $query .= " AND found IS NULL ";
1586
1587     $dbh = C4::Context->dbh;
1588     $sth = $dbh->prepare( $query );
1589     $sth->execute( @query_params );
1590     $sth->finish;
1591 }
1592
1593
1594 =head2 _FixPriority
1595
1596   &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1597
1598 Only used internally (so don't export it)
1599 Changed how this functions works #
1600 Now just gets an array of reserves in the rank order and updates them with
1601 the array index (+1 as array starts from 0)
1602 and if $rank is supplied will splice item from the array and splice it back in again
1603 in new priority rank
1604
1605 =cut 
1606
1607 sub _FixPriority {
1608     my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1609     my $dbh = C4::Context->dbh;
1610      if ( $rank eq "del" ) {
1611          CancelReserve( $biblio, undef, $borrowernumber );
1612      }
1613     if ( $rank eq "W" || $rank eq "0" ) {
1614
1615         # make sure priority for waiting or in-transit items is 0
1616         my $query = qq/
1617             UPDATE reserves
1618             SET    priority = 0
1619             WHERE biblionumber = ?
1620               AND borrowernumber = ?
1621               AND found IN ('W', 'T')
1622         /;
1623         my $sth = $dbh->prepare($query);
1624         $sth->execute( $biblio, $borrowernumber );
1625     }
1626     my @priority;
1627     my @reservedates;
1628
1629     # get whats left
1630 # FIXME adding a new security in returned elements for changing priority,
1631 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1632         # This is wrong a waiting reserve has W set
1633         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1634     my $query = qq/
1635         SELECT borrowernumber, reservedate, constrainttype
1636         FROM   reserves
1637         WHERE  biblionumber   = ?
1638           AND  ((found <> 'W' AND found <> 'T') or found is NULL)
1639         ORDER BY priority ASC
1640     /;
1641     my $sth = $dbh->prepare($query);
1642     $sth->execute($biblio);
1643     while ( my $line = $sth->fetchrow_hashref ) {
1644         push( @reservedates, $line );
1645         push( @priority,     $line );
1646     }
1647
1648     # To find the matching index
1649     my $i;
1650     my $key = -1;    # to allow for 0 to be a valid result
1651     for ( $i = 0 ; $i < @priority ; $i++ ) {
1652         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1653             $key = $i;    # save the index
1654             last;
1655         }
1656     }
1657
1658     # if index exists in array then move it to new position
1659     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1660         my $new_rank = $rank -
1661           1;    # $new_rank is what you want the new index to be in the array
1662         my $moving_item = splice( @priority, $key, 1 );
1663         splice( @priority, $new_rank, 0, $moving_item );
1664     }
1665
1666     # now fix the priority on those that are left....
1667     $query = "
1668             UPDATE reserves
1669             SET    priority = ?
1670                 WHERE  biblionumber = ?
1671                  AND borrowernumber   = ?
1672                  AND reservedate = ?
1673          AND found IS NULL
1674     ";
1675     $sth = $dbh->prepare($query);
1676     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1677         $sth->execute(
1678             $j + 1, $biblio,
1679             $priority[$j]->{'borrowernumber'},
1680             $priority[$j]->{'reservedate'}
1681         );
1682         $sth->finish;
1683     }
1684     
1685     $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1686     $sth->execute();
1687     
1688     unless ( $ignoreSetLowestRank ) {
1689       while ( my $res = $sth->fetchrow_hashref() ) {
1690         _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1691       }
1692     }
1693 }
1694
1695 =head2 _Findgroupreserve
1696
1697   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1698
1699 Looks for an item-specific match first, then for a title-level match, returning the
1700 first match found.  If neither, then we look for a 3rd kind of match based on
1701 reserve constraints.
1702
1703 TODO: add more explanation about reserve constraints
1704
1705 C<&_Findgroupreserve> returns :
1706 C<@results> is an array of references-to-hash whose keys are mostly
1707 fields from the reserves table of the Koha database, plus
1708 C<biblioitemnumber>.
1709
1710 =cut
1711
1712 sub _Findgroupreserve {
1713     my ( $bibitem, $biblio, $itemnumber ) = @_;
1714     my $dbh   = C4::Context->dbh;
1715
1716     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1717     # check for exact targetted match
1718     my $item_level_target_query = qq/
1719         SELECT reserves.biblionumber        AS biblionumber,
1720                reserves.borrowernumber      AS borrowernumber,
1721                reserves.reservedate         AS reservedate,
1722                reserves.branchcode          AS branchcode,
1723                reserves.cancellationdate    AS cancellationdate,
1724                reserves.found               AS found,
1725                reserves.reservenotes        AS reservenotes,
1726                reserves.priority            AS priority,
1727                reserves.timestamp           AS timestamp,
1728                biblioitems.biblioitemnumber AS biblioitemnumber,
1729                reserves.itemnumber          AS itemnumber
1730         FROM reserves
1731         JOIN biblioitems USING (biblionumber)
1732         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1733         WHERE found IS NULL
1734         AND priority > 0
1735         AND item_level_request = 1
1736         AND itemnumber = ?
1737         AND reservedate <= CURRENT_DATE()
1738         AND suspend = 0
1739     /;
1740     my $sth = $dbh->prepare($item_level_target_query);
1741     $sth->execute($itemnumber);
1742     my @results;
1743     if ( my $data = $sth->fetchrow_hashref ) {
1744         push( @results, $data );
1745     }
1746     return @results if @results;
1747     
1748     # check for title-level targetted match
1749     my $title_level_target_query = qq/
1750         SELECT reserves.biblionumber        AS biblionumber,
1751                reserves.borrowernumber      AS borrowernumber,
1752                reserves.reservedate         AS reservedate,
1753                reserves.branchcode          AS branchcode,
1754                reserves.cancellationdate    AS cancellationdate,
1755                reserves.found               AS found,
1756                reserves.reservenotes        AS reservenotes,
1757                reserves.priority            AS priority,
1758                reserves.timestamp           AS timestamp,
1759                biblioitems.biblioitemnumber AS biblioitemnumber,
1760                reserves.itemnumber          AS itemnumber
1761         FROM reserves
1762         JOIN biblioitems USING (biblionumber)
1763         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1764         WHERE found IS NULL
1765         AND priority > 0
1766         AND item_level_request = 0
1767         AND hold_fill_targets.itemnumber = ?
1768         AND reservedate <= CURRENT_DATE()
1769         AND suspend = 0
1770     /;
1771     $sth = $dbh->prepare($title_level_target_query);
1772     $sth->execute($itemnumber);
1773     @results = ();
1774     if ( my $data = $sth->fetchrow_hashref ) {
1775         push( @results, $data );
1776     }
1777     return @results if @results;
1778
1779     my $query = qq/
1780         SELECT reserves.biblionumber               AS biblionumber,
1781                reserves.borrowernumber             AS borrowernumber,
1782                reserves.reservedate                AS reservedate,
1783                reserves.waitingdate                AS waitingdate,
1784                reserves.branchcode                 AS branchcode,
1785                reserves.cancellationdate           AS cancellationdate,
1786                reserves.found                      AS found,
1787                reserves.reservenotes               AS reservenotes,
1788                reserves.priority                   AS priority,
1789                reserves.timestamp                  AS timestamp,
1790                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1791                reserves.itemnumber                 AS itemnumber
1792         FROM reserves
1793           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1794         WHERE reserves.biblionumber = ?
1795           AND ( ( reserveconstraints.biblioitemnumber = ?
1796           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1797           AND reserves.reservedate    = reserveconstraints.reservedate )
1798           OR  reserves.constrainttype='a' )
1799           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1800           AND reserves.reservedate <= CURRENT_DATE()
1801           AND suspend = 0
1802     /;
1803     $sth = $dbh->prepare($query);
1804     $sth->execute( $biblio, $bibitem, $itemnumber );
1805     @results = ();
1806     while ( my $data = $sth->fetchrow_hashref ) {
1807         push( @results, $data );
1808     }
1809     return @results;
1810 }
1811
1812 =head2 _koha_notify_reserve
1813
1814   _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1815
1816 Sends a notification to the patron that their hold has been filled (through
1817 ModReserveAffect, _not_ ModReserveFill)
1818
1819 =cut
1820
1821 sub _koha_notify_reserve {
1822     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1823
1824     my $dbh = C4::Context->dbh;
1825     my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
1826     
1827     # Try to get the borrower's email address
1828     my $to_address;
1829     my $which_address = C4::Context->preference('AutoEmailPrimaryAddress');
1830     # If the system preference is set to 'first valid' (value == OFF), look up email address
1831     if ($which_address eq 'OFF') {
1832         $to_address = C4::Members::GetFirstValidEmailAddress( $borrowernumber );
1833     } else {
1834         $to_address = $borrower->{$which_address};
1835     }
1836     
1837     my $letter_code;
1838     my $print_mode = 0;
1839     my $messagingprefs;
1840     if ( $to_address || $borrower->{'smsalertnumber'} ) {
1841         $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
1842
1843         return if ( !defined( $messagingprefs->{'letter_code'} ) );
1844         $letter_code = $messagingprefs->{'letter_code'};
1845     } else {
1846         $letter_code = 'HOLD_PRINT';
1847         $print_mode = 1;
1848     }
1849
1850     my $sth = $dbh->prepare("
1851         SELECT *
1852         FROM   reserves
1853         WHERE  borrowernumber = ?
1854             AND biblionumber = ?
1855     ");
1856     $sth->execute( $borrowernumber, $biblionumber );
1857     my $reserve = $sth->fetchrow_hashref;
1858     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1859
1860     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1861
1862     my $letter =  C4::Letters::GetPreparedLetter (
1863         module => 'reserves',
1864         letter_code => $letter_code,
1865         branchcode => $reserve->{branchcode},
1866         tables => {
1867             'branches'  => $branch_details,
1868             'borrowers' => $borrower,
1869             'biblio'    => $biblionumber,
1870             'reserves'  => $reserve,
1871             'items', $reserve->{'itemnumber'},
1872         },
1873         substitute => { today => C4::Dates->new()->output() },
1874     ) or die "Could not find a letter called '$letter_code' in the 'reserves' module";
1875
1876
1877
1878     if ( $print_mode ) {
1879         C4::Letters::EnqueueLetter( {
1880             letter => $letter,
1881             borrowernumber => $borrowernumber,
1882             message_transport_type => 'print',
1883         } );
1884         
1885         return;
1886     }
1887
1888     if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1889         # aka, 'email' in ->{'transports'}
1890         C4::Letters::EnqueueLetter(
1891             {   letter                 => $letter,
1892                 borrowernumber         => $borrowernumber,
1893                 message_transport_type => 'email',
1894                 from_address           => $admin_email_address,
1895             }
1896         );
1897     }
1898
1899     if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1900         C4::Letters::EnqueueLetter(
1901             {   letter                 => $letter,
1902                 borrowernumber         => $borrowernumber,
1903                 message_transport_type => 'sms',
1904             }
1905         );
1906     }
1907 }
1908
1909 =head2 _ShiftPriorityByDateAndPriority
1910
1911   $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1912
1913 This increments the priority of all reserves after the one
1914 with either the lowest date after C<$reservedate>
1915 or the lowest priority after C<$priority>.
1916
1917 It effectively makes room for a new reserve to be inserted with a certain
1918 priority, which is returned.
1919
1920 This is most useful when the reservedate can be set by the user.  It allows
1921 the new reserve to be placed before other reserves that have a later
1922 reservedate.  Since priority also is set by the form in reserves/request.pl
1923 the sub accounts for that too.
1924
1925 =cut
1926
1927 sub _ShiftPriorityByDateAndPriority {
1928     my ( $biblio, $resdate, $new_priority ) = @_;
1929
1930     my $dbh = C4::Context->dbh;
1931     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
1932     my $sth = $dbh->prepare( $query );
1933     $sth->execute( $biblio, $resdate, $new_priority );
1934     my $min_priority = $sth->fetchrow;
1935     # if no such matches are found, $new_priority remains as original value
1936     $new_priority = $min_priority if ( $min_priority );
1937
1938     # Shift the priority up by one; works in conjunction with the next SQL statement
1939     $query = "UPDATE reserves
1940               SET priority = priority+1
1941               WHERE biblionumber = ?
1942               AND borrowernumber = ?
1943               AND reservedate = ?
1944               AND found IS NULL";
1945     my $sth_update = $dbh->prepare( $query );
1946
1947     # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
1948     $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
1949     $sth = $dbh->prepare( $query );
1950     $sth->execute( $new_priority, $biblio );
1951     while ( my $row = $sth->fetchrow_hashref ) {
1952         $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
1953     }
1954
1955     return $new_priority;  # so the caller knows what priority they wind up receiving
1956 }
1957
1958 =head2 MoveReserve
1959
1960   MoveReserve( $itemnumber, $borrowernumber, $cancelreserve )
1961
1962 Use when checking out an item to handle reserves
1963 If $cancelreserve boolean is set to true, it will remove existing reserve
1964
1965 =cut
1966
1967 sub MoveReserve {
1968     my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_;
1969
1970     my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber );
1971     return unless $res;
1972
1973     my $biblionumber     =  $res->{biblionumber};
1974     my $biblioitemnumber = $res->{biblioitemnumber};
1975
1976     if ($res->{borrowernumber} == $borrowernumber) {
1977         ModReserveFill($res);
1978     }
1979     else {
1980         # warn "Reserved";
1981         # The item is reserved by someone else.
1982         # Find this item in the reserves
1983
1984         my $borr_res;
1985         foreach (@$all_reserves) {
1986             $_->{'borrowernumber'} == $borrowernumber or next;
1987             $_->{'biblionumber'}   == $biblionumber   or next;
1988
1989             $borr_res = $_;
1990             last;
1991         }
1992
1993         if ( $borr_res ) {
1994             # The item is reserved by the current patron
1995             ModReserveFill($borr_res);
1996         }
1997
1998         if ($cancelreserve) { # cancel reserves on this item
1999             CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
2000             CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
2001         }
2002     }
2003 }
2004
2005 =head2 MergeHolds
2006
2007   MergeHolds($dbh,$to_biblio, $from_biblio);
2008
2009 This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by the date they were placed
2010
2011 =cut
2012
2013 sub MergeHolds {
2014     my ( $dbh, $to_biblio, $from_biblio ) = @_;
2015     my $sth = $dbh->prepare(
2016         "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
2017     );
2018     $sth->execute($from_biblio);
2019     if ( my $data = $sth->fetchrow_hashref() ) {
2020
2021         # holds exist on old record, if not we don't need to do anything
2022         $sth = $dbh->prepare(
2023             "UPDATE reserves SET biblionumber = ? WHERE biblionumber = ?");
2024         $sth->execute( $to_biblio, $from_biblio );
2025
2026         # Reorder by date
2027         # don't reorder those already waiting
2028
2029         $sth = $dbh->prepare(
2030 "SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
2031         );
2032         my $upd_sth = $dbh->prepare(
2033 "UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
2034         AND reservedate = ? AND constrainttype = ? AND (itemnumber = ? or itemnumber is NULL) "
2035         );
2036         $sth->execute( $to_biblio, 'W', 'T' );
2037         my $priority = 1;
2038         while ( my $reserve = $sth->fetchrow_hashref() ) {
2039             $upd_sth->execute(
2040                 $priority,                    $to_biblio,
2041                 $reserve->{'borrowernumber'}, $reserve->{'reservedate'},
2042                 $reserve->{'constrainttype'}, $reserve->{'itemnumber'}
2043             );
2044             $priority++;
2045         }
2046     }
2047 }
2048
2049 =head2 ReserveSlip
2050
2051   ReserveSlip($branchcode, $borrowernumber, $biblionumber)
2052
2053   Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef
2054
2055 =cut
2056
2057 sub ReserveSlip {
2058     my ($branch, $borrowernumber, $biblionumber) = @_;
2059
2060 #   return unless ( C4::Context->boolean_preference('printreserveslips') );
2061
2062     my $reserve = GetReserveInfo($borrowernumber,$biblionumber )
2063       or return;
2064
2065     return  C4::Letters::GetPreparedLetter (
2066         module => 'circulation',
2067         letter_code => 'RESERVESLIP',
2068         branchcode => $branch,
2069         tables => {
2070             'reserves'    => $reserve,
2071             'branches'    => $reserve->{branchcode},
2072             'borrowers'   => $reserve,
2073             'biblio'      => $reserve,
2074             'items'       => $reserve,
2075         },
2076     );
2077 }
2078
2079 =head1 AUTHOR
2080
2081 Koha Development Team <http://koha-community.org/>
2082
2083 =cut
2084
2085 1;