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