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