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