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