Bug 4210 Acq suggestions don't have LibraryName value passed
[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
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
23 use strict;
24 #use warnings; FIXME - Bug 2505
25 use C4::Context;
26 use C4::Biblio;
27 use C4::Members;
28 use C4::Items;
29 use C4::Search;
30 use C4::Circulation;
31 use C4::Accounts;
32
33 # for _koha_notify_reserve
34 use C4::Members::Messaging;
35 use C4::Members qw();
36 use C4::Letters;
37 use C4::Branch qw( GetBranchDetail );
38 use C4::Dates qw( format_date_in_iso );
39 use List::MoreUtils qw( firstidx );
40
41 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
42
43 =head1 NAME
44
45 C4::Reserves - Koha functions for dealing with reservation.
46
47 =head1 SYNOPSIS
48
49   use C4::Reserves;
50
51 =head1 DESCRIPTION
52
53   this modules provides somes functions to deal with reservations.
54   
55   Reserves are stored in reserves table.
56   The following columns contains important values :
57   - priority >0      : then the reserve is at 1st stage, and not yet affected to any item.
58              =0      : then the reserve is being dealed
59   - found : NULL       : means the patron requested the 1st available, and we haven't choosen the item
60             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 = C4::Members::GetMember(borrowernumber => $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 
1300                        reservedate, 
1301                        reservenotes, 
1302                        reserves.borrowernumber,
1303                                    reserves.biblionumber, 
1304                                    reserves.branchcode,
1305                                    reserves.waitingdate,
1306                                    notificationdate, 
1307                                    reminderdate, 
1308                                    priority, 
1309                                    found,
1310                                    firstname, 
1311                                    surname, 
1312                                    phone, 
1313                                    email, 
1314                                    address, 
1315                                    address2,
1316                                    cardnumber, 
1317                                    city, 
1318                                    zipcode,
1319                                    biblio.title, 
1320                                    biblio.author,
1321                                    items.holdingbranch, 
1322                                    items.itemcallnumber, 
1323                                    items.itemnumber,
1324                                    items.location, 
1325                                    barcode, 
1326                                    notes
1327                         FROM reserves 
1328                          LEFT JOIN items USING(itemnumber) 
1329                      LEFT JOIN borrowers USING(borrowernumber)
1330                      LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber) 
1331                         WHERE 
1332                                 reserves.borrowernumber=?
1333                                 AND reserves.biblionumber=?";
1334         my $sth = $dbh->prepare($strsth); 
1335         $sth->execute($borrowernumber,$biblionumber);
1336
1337         my $data = $sth->fetchrow_hashref;
1338         return $data;
1339
1340 }
1341
1342 =item IsAvailableForItemLevelRequest
1343
1344 =over 4
1345
1346 my $is_available = IsAvailableForItemLevelRequest($itemnumber);
1347
1348 =back
1349
1350 Checks whether a given item record is available for an
1351 item-level hold request.  An item is available if
1352
1353 * it is not lost AND 
1354 * it is not damaged AND 
1355 * it is not withdrawn AND 
1356 * does not have a not for loan value > 0
1357
1358 Whether or not the item is currently on loan is 
1359 also checked - if the AllowOnShelfHolds system preference
1360 is ON, an item can be requested even if it is currently
1361 on loan to somebody else.  If the system preference
1362 is OFF, an item that is currently checked out cannot
1363 be the target of an item-level hold request.
1364
1365 Note that IsAvailableForItemLevelRequest() does not
1366 check if the staff operator is authorized to place
1367 a request on the item - in particular,
1368 this routine does not check IndependantBranches
1369 and canreservefromotherbranches.
1370
1371 =cut
1372
1373 sub IsAvailableForItemLevelRequest {
1374     my $itemnumber = shift;
1375    
1376     my $item = GetItem($itemnumber);
1377
1378     # must check the notforloan setting of the itemtype
1379     # FIXME - a lot of places in the code do this
1380     #         or something similar - need to be
1381     #         consolidated
1382     my $dbh = C4::Context->dbh;
1383     my $notforloan_query;
1384     if (C4::Context->preference('item-level_itypes')) {
1385         $notforloan_query = "SELECT itemtypes.notforloan
1386                              FROM items
1387                              JOIN itemtypes ON (itemtypes.itemtype = items.itype)
1388                              WHERE itemnumber = ?";
1389     } else {
1390         $notforloan_query = "SELECT itemtypes.notforloan
1391                              FROM items
1392                              JOIN biblioitems USING (biblioitemnumber)
1393                              JOIN itemtypes USING (itemtype)
1394                              WHERE itemnumber = ?";
1395     }
1396     my $sth = $dbh->prepare($notforloan_query);
1397     $sth->execute($itemnumber);
1398     my $notforloan_per_itemtype = 0;
1399     if (my ($notforloan) = $sth->fetchrow_array) {
1400         $notforloan_per_itemtype = 1 if $notforloan;
1401     }
1402
1403     my $available_per_item = 1;
1404     $available_per_item = 0 if $item->{itemlost} or
1405                                ( $item->{notforloan} > 0 ) or
1406                                ($item->{damaged} and not C4::Context->preference('AllowHoldsOnDamagedItems')) or
1407                                $item->{wthdrawn} or
1408                                $notforloan_per_itemtype;
1409
1410
1411     if (C4::Context->preference('AllowOnShelfHolds')) {
1412         return $available_per_item;
1413     } else {
1414         return ($available_per_item and $item->{onloan}); 
1415     }
1416 }
1417
1418 =item AlterPriority
1419 AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
1420
1421 This function changes a reserve's priority up, down, to the top, or to the bottom.
1422 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
1423
1424 =cut
1425 sub AlterPriority {
1426     my ( $where, $borrowernumber, $biblionumber ) = @_;
1427
1428     my $dbh = C4::Context->dbh;
1429
1430     ## Find this reserve
1431     my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
1432     $sth->execute( $biblionumber, $borrowernumber );
1433     my $reserve = $sth->fetchrow_hashref();
1434     $sth->finish();
1435
1436     if ( $where eq 'up' || $where eq 'down' ) {
1437     
1438       my $priority = $reserve->{'priority'};        
1439       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
1440       _FixPriority( $biblionumber, $borrowernumber, $priority )
1441
1442     } elsif ( $where eq 'top' ) {
1443
1444       _FixPriority( $biblionumber, $borrowernumber, '1' )
1445
1446     } elsif ( $where eq 'bottom' ) {
1447
1448       _FixPriority( $biblionumber, $borrowernumber, '999999' )
1449
1450     }
1451 }
1452
1453 =item ToggleLowestPriority
1454 ToggleLowestPriority( $borrowernumber, $biblionumber );
1455
1456 This function sets the lowestPriority field to true if is false, and false if it is true.
1457 =cut
1458
1459 sub ToggleLowestPriority {
1460     my ( $borrowernumber, $biblionumber ) = @_;
1461
1462     my $dbh = C4::Context->dbh;
1463
1464     my $sth = $dbh->prepare(
1465         "UPDATE reserves SET lowestPriority = NOT lowestPriority
1466          WHERE biblionumber = ?
1467          AND borrowernumber = ?"
1468     );
1469     $sth->execute(
1470         $biblionumber,
1471         $borrowernumber,
1472     );
1473     $sth->finish;
1474     
1475     _FixPriority( $biblionumber, $borrowernumber, '999999' );
1476 }
1477
1478 =item _FixPriority
1479
1480 &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
1481
1482  Only used internally (so don't export it)
1483  Changed how this functions works #
1484  Now just gets an array of reserves in the rank order and updates them with
1485  the array index (+1 as array starts from 0)
1486  and if $rank is supplied will splice item from the array and splice it back in again
1487  in new priority rank
1488
1489 =cut 
1490
1491 sub _FixPriority {
1492     my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
1493     my $dbh = C4::Context->dbh;
1494      if ( $rank eq "del" ) {
1495          CancelReserve( $biblio, undef, $borrowernumber );
1496      }
1497     if ( $rank eq "W" || $rank eq "0" ) {
1498
1499         # make sure priority for waiting items is 0
1500         my $query = qq/
1501             UPDATE reserves
1502             SET    priority = 0
1503             WHERE biblionumber = ?
1504               AND borrowernumber = ?
1505               AND found ='W'
1506         /;
1507         my $sth = $dbh->prepare($query);
1508         $sth->execute( $biblio, $borrowernumber );
1509     }
1510     my @priority;
1511     my @reservedates;
1512
1513     # get whats left
1514 # FIXME adding a new security in returned elements for changing priority,
1515 # now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
1516         # This is wrong a waiting reserve has W set
1517         # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
1518     my $query = qq/
1519         SELECT borrowernumber, reservedate, constrainttype
1520         FROM   reserves
1521         WHERE  biblionumber   = ?
1522           AND  ((found <> 'W') or found is NULL)
1523         ORDER BY priority ASC
1524     /;
1525     my $sth = $dbh->prepare($query);
1526     $sth->execute($biblio);
1527     while ( my $line = $sth->fetchrow_hashref ) {
1528         push( @reservedates, $line );
1529         push( @priority,     $line );
1530     }
1531
1532     # To find the matching index
1533     my $i;
1534     my $key = -1;    # to allow for 0 to be a valid result
1535     for ( $i = 0 ; $i < @priority ; $i++ ) {
1536         if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
1537             $key = $i;    # save the index
1538             last;
1539         }
1540     }
1541
1542     # if index exists in array then move it to new position
1543     if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
1544         my $new_rank = $rank -
1545           1;    # $new_rank is what you want the new index to be in the array
1546         my $moving_item = splice( @priority, $key, 1 );
1547         splice( @priority, $new_rank, 0, $moving_item );
1548     }
1549
1550     # now fix the priority on those that are left....
1551     $query = "
1552             UPDATE reserves
1553             SET    priority = ?
1554                 WHERE  biblionumber = ?
1555                  AND borrowernumber   = ?
1556                  AND reservedate = ?
1557          AND found IS NULL
1558     ";
1559     $sth = $dbh->prepare($query);
1560     for ( my $j = 0 ; $j < @priority ; $j++ ) {
1561         $sth->execute(
1562             $j + 1, $biblio,
1563             $priority[$j]->{'borrowernumber'},
1564             $priority[$j]->{'reservedate'}
1565         );
1566         $sth->finish;
1567     }
1568     
1569     $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
1570     $sth->execute();
1571     
1572     unless ( $ignoreSetLowestRank ) {
1573       while ( my $res = $sth->fetchrow_hashref() ) {
1574         _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
1575       }
1576     }
1577 }
1578
1579 =item _Findgroupreserve
1580
1581   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
1582
1583 Looks for an item-specific match first, then for a title-level match, returning the
1584 first match found.  If neither, then we look for a 3rd kind of match based on
1585 reserve constraints.
1586
1587 TODO: add more explanation about reserve constraints
1588
1589 C<&_Findgroupreserve> returns :
1590 C<@results> is an array of references-to-hash whose keys are mostly
1591 fields from the reserves table of the Koha database, plus
1592 C<biblioitemnumber>.
1593
1594 =cut
1595
1596 sub _Findgroupreserve {
1597     my ( $bibitem, $biblio, $itemnumber ) = @_;
1598     my $dbh   = C4::Context->dbh;
1599
1600     # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var.
1601     # check for exact targetted match
1602     my $item_level_target_query = qq/
1603         SELECT reserves.biblionumber        AS biblionumber,
1604                reserves.borrowernumber      AS borrowernumber,
1605                reserves.reservedate         AS reservedate,
1606                reserves.branchcode          AS branchcode,
1607                reserves.cancellationdate    AS cancellationdate,
1608                reserves.found               AS found,
1609                reserves.reservenotes        AS reservenotes,
1610                reserves.priority            AS priority,
1611                reserves.timestamp           AS timestamp,
1612                biblioitems.biblioitemnumber AS biblioitemnumber,
1613                reserves.itemnumber          AS itemnumber
1614         FROM reserves
1615         JOIN biblioitems USING (biblionumber)
1616         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
1617         WHERE found IS NULL
1618         AND priority > 0
1619         AND item_level_request = 1
1620         AND itemnumber = ?
1621         AND reservedate <= CURRENT_DATE()
1622     /;
1623     my $sth = $dbh->prepare($item_level_target_query);
1624     $sth->execute($itemnumber);
1625     my @results;
1626     if ( my $data = $sth->fetchrow_hashref ) {
1627         push( @results, $data );
1628     }
1629     return @results if @results;
1630     
1631     # check for title-level targetted match
1632     my $title_level_target_query = qq/
1633         SELECT reserves.biblionumber        AS biblionumber,
1634                reserves.borrowernumber      AS borrowernumber,
1635                reserves.reservedate         AS reservedate,
1636                reserves.branchcode          AS branchcode,
1637                reserves.cancellationdate    AS cancellationdate,
1638                reserves.found               AS found,
1639                reserves.reservenotes        AS reservenotes,
1640                reserves.priority            AS priority,
1641                reserves.timestamp           AS timestamp,
1642                biblioitems.biblioitemnumber AS biblioitemnumber,
1643                reserves.itemnumber          AS itemnumber
1644         FROM reserves
1645         JOIN biblioitems USING (biblionumber)
1646         JOIN hold_fill_targets USING (biblionumber, borrowernumber)
1647         WHERE found IS NULL
1648         AND priority > 0
1649         AND item_level_request = 0
1650         AND hold_fill_targets.itemnumber = ?
1651         AND reservedate <= CURRENT_DATE()
1652     /;
1653     $sth = $dbh->prepare($title_level_target_query);
1654     $sth->execute($itemnumber);
1655     @results = ();
1656     if ( my $data = $sth->fetchrow_hashref ) {
1657         push( @results, $data );
1658     }
1659     return @results if @results;
1660
1661     my $query = qq/
1662         SELECT reserves.biblionumber               AS biblionumber,
1663                reserves.borrowernumber             AS borrowernumber,
1664                reserves.reservedate                AS reservedate,
1665                reserves.branchcode                 AS branchcode,
1666                reserves.cancellationdate           AS cancellationdate,
1667                reserves.found                      AS found,
1668                reserves.reservenotes               AS reservenotes,
1669                reserves.priority                   AS priority,
1670                reserves.timestamp                  AS timestamp,
1671                reserveconstraints.biblioitemnumber AS biblioitemnumber,
1672                reserves.itemnumber                 AS itemnumber
1673         FROM reserves
1674           LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber
1675         WHERE reserves.biblionumber = ?
1676           AND ( ( reserveconstraints.biblioitemnumber = ?
1677           AND reserves.borrowernumber = reserveconstraints.borrowernumber
1678           AND reserves.reservedate    = reserveconstraints.reservedate )
1679           OR  reserves.constrainttype='a' )
1680           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
1681           AND reserves.reservedate <= CURRENT_DATE()
1682     /;
1683     $sth = $dbh->prepare($query);
1684     $sth->execute( $biblio, $bibitem, $itemnumber );
1685     @results = ();
1686     while ( my $data = $sth->fetchrow_hashref ) {
1687         push( @results, $data );
1688     }
1689     return @results;
1690 }
1691
1692 =item _koha_notify_reserve
1693
1694 =over 4
1695
1696 _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
1697
1698 =back
1699
1700 Sends a notification to the patron that their hold has been filled (through
1701 ModReserveAffect, _not_ ModReserveFill)
1702
1703 =cut
1704
1705 sub _koha_notify_reserve {
1706     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
1707
1708     my $dbh = C4::Context->dbh;
1709     my $borrower = C4::Members::GetMember( $borrowernumber );
1710     my $letter_code;
1711     my $print_mode = 0;
1712     my $messagingprefs;
1713     if ( $borrower->{'email'} || $borrower->{'smsalertnumber'} ) {
1714         $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } );
1715
1716         return if ( !defined( $messagingprefs->{'letter_code'} ) );
1717         $letter_code = $messagingprefs->{'letter_code'};
1718     } else {
1719         $letter_code = 'HOLD_PRINT';
1720         $print_mode = 1;
1721     }
1722
1723     my $sth = $dbh->prepare("
1724         SELECT *
1725         FROM   reserves
1726         WHERE  borrowernumber = ?
1727             AND biblionumber = ?
1728     ");
1729     $sth->execute( $borrowernumber, $biblionumber );
1730     my $reserve = $sth->fetchrow_hashref;
1731     my $branch_details = GetBranchDetail( $reserve->{'branchcode'} );
1732
1733     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
1734
1735     my $letter = getletter( 'reserves', $letter_code );
1736     die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter );
1737
1738     C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} );
1739     C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber );
1740     C4::Letters::parseletter( $letter, 'biblio', $biblionumber );
1741     C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber );
1742
1743     if ( $reserve->{'itemnumber'} ) {
1744         C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} );
1745     }
1746     my $today = C4::Dates->new()->output();
1747     $letter->{'title'} =~ s/<<today>>/$today/g;
1748     $letter->{'content'} =~ s/<<today>>/$today/g;
1749     $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers
1750
1751     if ( $print_mode ) {
1752         C4::Letters::EnqueueLetter( {
1753             letter => $letter,
1754             borrowernumber => $borrowernumber,
1755             message_transport_type => 'print',
1756         } );
1757         
1758         return;
1759     }
1760
1761     if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
1762         # aka, 'email' in ->{'transports'}
1763         C4::Letters::EnqueueLetter(
1764             {   letter                 => $letter,
1765                 borrowernumber         => $borrowernumber,
1766                 message_transport_type => 'email',
1767                 from_address           => $admin_email_address,
1768             }
1769         );
1770     }
1771
1772     if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
1773         C4::Letters::EnqueueLetter(
1774             {   letter                 => $letter,
1775                 borrowernumber         => $borrowernumber,
1776                 message_transport_type => 'sms',
1777             }
1778         );
1779     }
1780 }
1781
1782 =item _ShiftPriorityByDateAndPriority
1783
1784 =over 4
1785
1786 $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
1787
1788 =back
1789
1790 This increments the priority of all reserves after the one
1791  with either the lowest date after C<$reservedate>
1792  or the lowest priority after C<$priority>.
1793
1794 It effectively makes room for a new reserve to be inserted with a certain
1795  priority, which is returned.
1796
1797 This is most useful when the reservedate can be set by the user.  It allows
1798  the new reserve to be placed before other reserves that have a later
1799  reservedate.  Since priority also is set by the form in reserves/request.pl
1800  the sub accounts for that too.
1801
1802 =cut
1803
1804 sub _ShiftPriorityByDateAndPriority {
1805     my ( $biblio, $resdate, $new_priority ) = @_;
1806
1807     my $dbh = C4::Context->dbh;
1808     my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC";
1809     my $sth = $dbh->prepare( $query );
1810     $sth->execute( $biblio, $resdate, $new_priority );
1811     my ( $min_priority ) = $sth->fetchrow;
1812     $sth->finish;  # $sth might have more data.
1813     $new_priority = $min_priority if ( $min_priority );
1814     my $updated_priority = $new_priority + 1;
1815
1816     $query = "
1817  UPDATE reserves
1818     SET priority = ?
1819   WHERE biblionumber = ?
1820     AND borrowernumber = ?
1821     AND reservedate = ?
1822     AND found IS NULL";
1823     my $sth_update = $dbh->prepare( $query );
1824
1825     $query = "SELECT * FROM reserves WHERE priority >= ?";
1826     $sth = $dbh->prepare( $query );
1827     $sth->execute( $new_priority );
1828     while ( my $row = $sth->fetchrow_hashref ) {
1829         $sth_update->execute( $updated_priority, $biblio, $row->{borrowernumber}, $row->{reservedate} );
1830         $updated_priority++;
1831     }
1832
1833     return $new_priority;  # so the caller knows what priority they end up at
1834 }
1835
1836 =back
1837
1838 =head1 AUTHOR
1839
1840 Koha Developement team <info@koha.org>
1841
1842 =cut
1843
1844 1;