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