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