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