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