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