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