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