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