Bug 2394: Use syspref canreservefromotherbranches in CanItemBeReserved
[koha.git] / C4 / Reserves.pm
index c4b2c66..0291def 100644 (file)
@@ -36,6 +36,9 @@ use C4::Members qw();
 use C4::Letters;
 use C4::Branch qw( GetBranchDetail );
 use C4::Dates qw( format_date_in_iso );
+
+use Koha::DateUtils;
+
 use List::MoreUtils qw( firstidx );
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
@@ -85,12 +88,13 @@ This modules provides somes functions to deal with reservations.
 
 BEGIN {
     # set the version for version checking
-    $VERSION = 3.01;
+    $VERSION = 3.07.00.049;
     require Exporter;
     @ISA = qw(Exporter);
     @EXPORT = qw(
         &AddReserve
-  
+
+        &GetReserve
         &GetReservesFromItemnumber
         &GetReservesFromBiblionumber
         &GetReservesFromBorrowernumber
@@ -241,9 +245,26 @@ sub AddReserve {
     return;     # FIXME: why not have a useful return value?
 }
 
+=head2 GetReserve
+
+    $res = GetReserve( $reserve_id );
+
+=cut
+
+sub GetReserve {
+    my ($reserve_id) = @_;
+
+    my $dbh = C4::Context->dbh;
+    my $query = "SELECT * FROM reserves WHERE reserve_id = ?";
+    my $sth = $dbh->prepare( $query );
+    $sth->execute( $reserve_id );
+    my $res = $sth->fetchrow_hashref();
+    return $res;
+}
+
 =head2 GetReservesFromBiblionumber
 
-  ($count, $title_reserves) = &GetReserves($biblionumber);
+  ($count, $title_reserves) = GetReservesFromBiblionumber($biblionumber);
 
 This function gets the list of reservations for one C<$biblionumber>, returning a count
 of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
@@ -257,7 +278,8 @@ sub GetReservesFromBiblionumber {
 
     # Find the desired items in the reserves
     my $query = "
-        SELECT  branchcode,
+        SELECT  reserve_id,
+                branchcode,
                 timestamp AS rtimestamp,
                 priority,
                 biblionumber,
@@ -325,7 +347,7 @@ sub GetReservesFromBiblionumber {
 
 =head2 GetReservesFromItemnumber
 
- ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
+ ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = GetReservesFromItemnumber($itemnumber);
 
 TODO :: Description here
 
@@ -335,7 +357,7 @@ sub GetReservesFromItemnumber {
     my ( $itemnumber, $all_dates ) = @_;
     my $dbh   = C4::Context->dbh;
     my $query = "
-    SELECT reservedate,borrowernumber,branchcode
+    SELECT reservedate,borrowernumber,branchcode,reserve_id
     FROM   reserves
     WHERE  itemnumber=?
     ";
@@ -344,8 +366,8 @@ sub GetReservesFromItemnumber {
     }
     my $sth_res = $dbh->prepare($query);
     $sth_res->execute($itemnumber);
-    my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
-    return ( $reservedate, $borrowernumber, $branchcode );
+    my ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = $sth_res->fetchrow_array;
+    return ( $reservedate, $borrowernumber, $branchcode, $reserve_id );
 }
 
 =head2 GetReservesFromBorrowernumber
@@ -488,11 +510,22 @@ sub CanItemBeReserved{
     }
     
     # we check if it's ok or not
-    if( $reservecount < $allowedreserves ){
-        return 1;
-    }else{
+    if( $reservecount >= $allowedreserves ){
         return 0;
     }
+
+    # If reservecount is ok, we check item branch if IndependentBranches is ON
+    # and canreservefromotherbranches is OFF
+    if ( C4::Context->preference('IndependentBranches')
+        and !C4::Context->preference('canreservefromotherbranches') )
+    {
+        my $itembranch = $item->{homebranch};
+        if ($itembranch ne $borrower->{branchcode}) {
+            return 0;
+        }
+    }
+
+    return 1;
 }
 #--------------------------------------------------------------------------------
 =head2 GetReserveCount
@@ -508,11 +541,11 @@ sub GetReserveCount {
 
     my $dbh = C4::Context->dbh;
 
-    my $query = '
+    my $query = "
         SELECT COUNT(*) AS counter
         FROM reserves
-          WHERE borrowernumber = ?
-    ';
+        WHERE borrowernumber = ?
+    ";
     my $sth = $dbh->prepare($query);
     $sth->execute($borrowernumber);
     my $row = $sth->fetchrow_hashref;
@@ -539,8 +572,7 @@ sub GetOtherReserves {
             #minus priorities of others reservs
             ModReserveMinusPriority(
                 $itemnumber,
-                $checkreserves->{'borrowernumber'},
-                $iteminfo->{'biblionumber'}
+                $checkreserves->{'reserve_id'},
             );
 
             #launch the subroutine dotransfer
@@ -557,8 +589,7 @@ sub GetOtherReserves {
             $messages->{'waiting'} = 1;
             ModReserveMinusPriority(
                 $itemnumber,
-                $checkreserves->{'borrowernumber'},
-                $iteminfo->{'biblionumber'}
+                $checkreserves->{'reserve_id'},
             );
             ModReserveStatus($itemnumber,'W');
         }
@@ -684,7 +715,7 @@ sub GetReservesToBranch {
     my ( $frombranch ) = @_;
     my $dbh = C4::Context->dbh;
     my $sth = $dbh->prepare(
-        "SELECT borrowernumber,reservedate,itemnumber,timestamp
+        "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp
          FROM reserves 
          WHERE priority='0' 
            AND branchcode=?"
@@ -707,22 +738,24 @@ sub GetReservesToBranch {
 
 sub GetReservesForBranch {
     my ($frombranch) = @_;
-    my $dbh          = C4::Context->dbh;
-       my $query        = "SELECT borrowernumber,reservedate,itemnumber,waitingdate
+    my $dbh = C4::Context->dbh;
+
+    my $query = "
+        SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate
         FROM   reserves 
         WHERE   priority='0'
-            AND found='W' ";
-    if ($frombranch){
-        $query .= " AND branchcode=? ";
-       }
+        AND found='W'
+    ";
+    $query .= " AND branchcode=? " if ( $frombranch );
     $query .= "ORDER BY waitingdate" ;
+
     my $sth = $dbh->prepare($query);
     if ($frombranch){
-               $sth->execute($frombranch);
-       }
-    else {
-               $sth->execute();
-       }
+     $sth->execute($frombranch);
+    } else {
+        $sth->execute();
+    }
+
     my @transreserv;
     my $i = 0;
     while ( my $data = $sth->fetchrow_hashref ) {
@@ -732,16 +765,44 @@ sub GetReservesForBranch {
     return (@transreserv);
 }
 
+=head2 GetReserveStatus
+
+  $reservestatus = GetReserveStatus($itemnumber, $biblionumber);
+
+Take an itemnumber or a biblionumber and return the status of the reserve places on it.
+If several reserves exist, the reserve with the lower priority is given.
+
+=cut
+
+## FIXME: I don't think this does what it thinks it does.
+## It only ever checks the first reserve result, even though
+## multiple reserves for that bib can have the itemnumber set
+## the sub is only used once in the codebase.
 sub GetReserveStatus {
-    my ($itemnumber) = @_;
-    
+    my ($itemnumber, $biblionumber) = @_;
+
     my $dbh = C4::Context->dbh;
-    
-    my $itemstatus = $dbh->prepare("SELECT found FROM reserves WHERE itemnumber = ?");
-    
-    $itemstatus->execute($itemnumber);
-    my ($found) = $itemstatus->fetchrow_array;
-    return $found;
+
+    my ($sth, $found, $priority);
+    if ( $itemnumber ) {
+        $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE itemnumber = ? order by priority LIMIT 1");
+        $sth->execute($itemnumber);
+        ($found, $priority) = $sth->fetchrow_array;
+    }
+
+    if ( $biblionumber and not defined $found and not defined $priority ) {
+        $sth = $dbh->prepare("SELECT found, priority FROM reserves WHERE biblionumber = ? order by priority LIMIT 1");
+        $sth->execute($biblionumber);
+        ($found, $priority) = $sth->fetchrow_array;
+    }
+
+    if(defined $found) {
+        return 'Waiting'  if $found eq 'W' and $priority == 0;
+        return 'Finished' if $found eq 'F';
+        return 'Reserved' if $priority > 0;
+    }
+    return '';
+    #empty string here will remove need for checking undef, or less log lines
 }
 
 =head2 CheckReserves
@@ -877,7 +938,7 @@ sub CancelExpiredReserves {
     $sth->execute();
 
     while ( my $res = $sth->fetchrow_hashref() ) {
-        CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
+        CancelReserve({ reserve_id => $res->{'reserve_id'} });
     }
   
     # Cancel reserves that have been waiting too long
@@ -894,7 +955,7 @@ sub CancelExpiredReserves {
                 manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge);
             }
 
-            CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} );
+            CancelReserve({ reserve_id => $res->{'reserve_id'} });
         }
     }
 
@@ -920,109 +981,60 @@ sub AutoUnsuspendReserves {
 
 =head2 CancelReserve
 
-  &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
+  CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber ] });
 
 Cancels a reserve.
 
-Use either C<$biblionumber> or C<$itemnumber> to specify the item to
-cancel, but not both: if both are given, C<&CancelReserve> uses
-C<$itemnumber>.
+=cut
 
-C<$borrowernumber> is the borrower number of the patron on whose
-behalf the book was reserved.
+sub CancelReserve {
+    my ( $params ) = @_;
 
-If C<$biblionumber> was given, C<&CancelReserve> also adjusts the
-priorities of the other people who are waiting on the book.
+    my $reserve_id = $params->{'reserve_id'};
+    $reserve_id = GetReserveId( $params ) unless ( $reserve_id );
 
-=cut
+    return unless ( $reserve_id );
 
-sub CancelReserve {
-    my ( $biblio, $item, $borr ) = @_;
     my $dbh = C4::Context->dbh;
-        if ( $item and $borr ) {
-        # removing a waiting reserve record....
-        # update the database...
-        my $query = "
-            UPDATE reserves
-            SET    cancellationdate = now(),
-                   found            = Null,
-                   priority         = 0
-            WHERE  itemnumber       = ?
-             AND   borrowernumber   = ?
-        ";
-        my $sth = $dbh->prepare($query);
-        $sth->execute( $item, $borr );
-        $sth->finish;
-        $query = "
-            INSERT INTO old_reserves
-            SELECT * FROM reserves
-            WHERE  itemnumber       = ?
-             AND   borrowernumber   = ?
-        ";
-        $sth = $dbh->prepare($query);
-        $sth->execute( $item, $borr );
-        $query = "
-            DELETE FROM reserves
-            WHERE  itemnumber       = ?
-             AND   borrowernumber   = ?
-        ";
-        $sth = $dbh->prepare($query);
-        $sth->execute( $item, $borr );
-    }
-    else {
-        # removing a reserve record....
-        # get the prioritiy on this record....
-        my $priority;
-        my $query = qq/
-            SELECT priority FROM reserves
-            WHERE biblionumber   = ?
-              AND borrowernumber = ?
-              AND cancellationdate IS NULL
-              AND itemnumber IS NULL
-        /;
-        my $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borr );
-        ($priority) = $sth->fetchrow_array;
-        $sth->finish;
-        $query = qq/
-            UPDATE reserves
-            SET    cancellationdate = now(),
-                   found            = Null,
-                   priority         = 0
-            WHERE  biblionumber     = ?
-              AND  borrowernumber   = ?
-        /;
-
-        # update the database, removing the record...
-        $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borr );
-        $sth->finish;
 
-        $query = qq/
-            INSERT INTO old_reserves
-            SELECT * FROM reserves
-            WHERE  biblionumber     = ?
-              AND  borrowernumber   = ?
-        /;
-        $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borr );
+    my $query = "
+        UPDATE reserves
+        SET    cancellationdate = now(),
+               found            = Null,
+               priority         = 0
+        WHERE  reserve_id = ?
+    ";
+    my $sth = $dbh->prepare($query);
+    $sth->execute( $reserve_id );
+    $sth->finish;
 
-        $query = qq/
-            DELETE FROM reserves
-            WHERE  biblionumber     = ?
-              AND  borrowernumber   = ?
-        /;
-        $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borr );
+    $query = "
+        INSERT INTO old_reserves
+        SELECT * FROM reserves
+        WHERE  reserve_id = ?
+    ";
+    $sth = $dbh->prepare($query);
+    $sth->execute( $reserve_id );
 
-        # now fix the priority on the others....
-        _FixPriority( $biblio, $borr );
-    }
+    $query = "
+        DELETE FROM reserves
+        WHERE  reserve_id = ?
+    ";
+    $sth = $dbh->prepare($query);
+    $sth->execute( $reserve_id );
+
+    # now fix the priority on the others....
+    _FixPriority( $reserve_id );
 }
 
 =head2 ModReserve
 
-  ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
+  ModReserve({ rank => $rank,
+               reserve_id => $reserve_id,
+               branchcode => $branchcode
+               [, itemnumber => $itemnumber ]
+               [, biblionumber => $biblionumber, $borrowernumber => $borrowernumber ]
+              });
 
 Change a hold request's priority or cancel it.
 
@@ -1052,59 +1064,67 @@ itemnumber and supplying itemnumber.
 =cut
 
 sub ModReserve {
-    #subroutine to update a reserve
-    my ( $rank, $biblio, $borrower, $branch , $itemnumber, $suspend_until) = @_;
-     return if $rank eq "W";
-     return if $rank eq "n";
+    my ( $params ) = @_;
+
+    my $rank = $params->{'rank'};
+    my $reserve_id = $params->{'reserve_id'};
+    my $branchcode = $params->{'branchcode'};
+    my $itemnumber = $params->{'itemnumber'};
+    my $suspend_until = $params->{'suspend_until'};
+    my $borrowernumber = $params->{'borrowernumber'};
+    my $biblionumber = $params->{'biblionumber'};
+
+    return if $rank eq "W";
+    return if $rank eq "n";
+
+    return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
+    $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber }) unless ( $reserve_id );
+
     my $dbh = C4::Context->dbh;
     if ( $rank eq "del" ) {
-        my $query = qq/
+        my $query = "
             UPDATE reserves
             SET    cancellationdate=now()
-            WHERE  biblionumber   = ?
-             AND   borrowernumber = ?
-        /;
+            WHERE  reserve_id = ?
+        ";
         my $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borrower );
+        $sth->execute( $reserve_id );
         $sth->finish;
-        $query = qq/
+        $query = "
             INSERT INTO old_reserves
             SELECT *
             FROM   reserves 
-            WHERE  biblionumber   = ?
-             AND   borrowernumber = ?
-        /;
+            WHERE  reserve_id = ?
+        ";
         $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borrower );
-        $query = qq/
+        $sth->execute( $reserve_id );
+        $query = "
             DELETE FROM reserves 
-            WHERE  biblionumber   = ?
-             AND   borrowernumber = ?
-        /;
+            WHERE  reserve_id = ?
+        ";
         $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borrower );
+        $sth->execute( $reserve_id );
         
     }
     elsif ($rank =~ /^\d+/ and $rank > 0) {
         my $query = "
             UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL
-            WHERE biblionumber   = ?
-            AND borrowernumber = ?
+            WHERE reserve_id = ?
         ";
         my $sth = $dbh->prepare($query);
-        $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower);
+        $sth->execute( $rank, $branchcode, $itemnumber, $reserve_id );
         $sth->finish;
 
         if ( defined( $suspend_until ) ) {
             if ( $suspend_until ) {
                 $suspend_until = C4::Dates->new( $suspend_until )->output("iso");
-                $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $suspend_until, $biblio, $borrower ) );
+                $dbh->do("UPDATE reserves SET suspend = 1, suspend_until = ? WHERE reserve_id = ?", undef, ( $suspend_until, $reserve_id ) );
             } else {
-                $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE biblionumber = ? AND borrowernumber = ?", undef, ( $biblio, $borrower ) );
+                $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE reserve_id = ?", undef, ( $reserve_id ) );
             }
         }
 
-        _FixPriority( $biblio, $borrower, $rank);
+        _FixPriority( $reserve_id, $rank );
     }
 }
 
@@ -1124,6 +1144,7 @@ sub ModReserveFill {
     my ($res) = @_;
     my $dbh = C4::Context->dbh;
     # fill in a reserve record....
+    my $reserve_id = $res->{'reserve_id'};
     my $biblionumber = $res->{'biblionumber'};
     my $borrowernumber    = $res->{'borrowernumber'};
     my $resdate = $res->{'reservedate'};
@@ -1172,7 +1193,7 @@ sub ModReserveFill {
     # now fix the priority on the others (if the priority wasn't
     # already sorted!)....
     unless ( $priority == 0 ) {
-        _FixPriority( $biblionumber, $borrowernumber );
+        _FixPriority( $reserve_id );
     }
 }
 
@@ -1282,7 +1303,7 @@ sub ModReserveCancelAll {
     my ( $itemnumber, $borrowernumber ) = @_;
 
     #step 1 : cancel the reservation
-    my $CancelReserve = CancelReserve( undef, $itemnumber, $borrowernumber );
+    my $CancelReserve = CancelReserve({ itemnumber => $itemnumber, borrowernumber => $borrowernumber });
 
     #step 2 launch the subroutine of the others reserves
     ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
@@ -1294,30 +1315,29 @@ sub ModReserveCancelAll {
 
   &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
 
-Reduce the values of queuded list     
+Reduce the values of queued list
 
 =cut
 
 sub ModReserveMinusPriority {
-    my ( $itemnumber, $borrowernumber, $biblionumber ) = @_;
+    my ( $itemnumber, $reserve_id ) = @_;
 
     #first step update the value of the first person on reserv
     my $dbh   = C4::Context->dbh;
     my $query = "
         UPDATE reserves
         SET    priority = 0 , itemnumber = ? 
-        WHERE  borrowernumber=?
-          AND  biblionumber=?
+        WHERE  reserve_id = ?
     ";
     my $sth_upd = $dbh->prepare($query);
-    $sth_upd->execute( $itemnumber, $borrowernumber, $biblionumber );
+    $sth_upd->execute( $itemnumber, $reserve_id );
     # second step update all others reservs
-    _FixPriority($biblionumber, $borrowernumber, '0');
+    _FixPriority( $reserve_id, '0');
 }
 
 =head2 GetReserveInfo
 
-  &GetReserveInfo($borrowernumber,$biblionumber);
+  &GetReserveInfo($reserve_id);
 
 Get item and borrower details for a current hold.
 Current implementation this query should have a single result.
@@ -1325,49 +1345,47 @@ Current implementation this query should have a single result.
 =cut
 
 sub GetReserveInfo {
-       my ( $borrowernumber, $biblionumber ) = @_;
+    my ( $reserve_id ) = @_;
     my $dbh = C4::Context->dbh;
-       my $strsth="SELECT 
-                      reservedate, 
-                      reservenotes, 
-                      reserves.borrowernumber,
-                                  reserves.biblionumber, 
-                                  reserves.branchcode,
-                                  reserves.waitingdate,
-                                  notificationdate, 
-                                  reminderdate, 
-                                  priority, 
-                                  found,
-                                  firstname, 
-                                  surname, 
-                                  phone, 
-                                  email, 
-                                  address, 
-                                  address2,
-                                  cardnumber, 
-                                  city, 
-                                  zipcode,
-                                  biblio.title, 
-                                  biblio.author,
-                                  items.holdingbranch, 
-                                  items.itemcallnumber, 
-                                  items.itemnumber,
-                                  items.location, 
-                                  barcode, 
-                                  notes
-                       FROM reserves 
-                        LEFT JOIN items USING(itemnumber) 
-                    LEFT JOIN borrowers USING(borrowernumber)
-                    LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber) 
-                       WHERE 
-                               reserves.borrowernumber=?
-                               AND reserves.biblionumber=?";
-       my $sth = $dbh->prepare($strsth); 
-       $sth->execute($borrowernumber,$biblionumber);
-
-       my $data = $sth->fetchrow_hashref;
-       return $data;
+    my $strsth="SELECT
+                   reserve_id,
+                   reservedate,
+                   reservenotes,
+                   reserves.borrowernumber,
+                   reserves.biblionumber,
+                   reserves.branchcode,
+                   reserves.waitingdate,
+                   notificationdate,
+                   reminderdate,
+                   priority,
+                   found,
+                   firstname,
+                   surname,
+                   phone,
+                   email,
+                   address,
+                   address2,
+                   cardnumber,
+                   city,
+                   zipcode,
+                   biblio.title,
+                   biblio.author,
+                   items.holdingbranch,
+                   items.itemcallnumber,
+                   items.itemnumber,
+                   items.location,
+                   barcode,
+                   notes
+                FROM reserves
+                LEFT JOIN items USING(itemnumber)
+                LEFT JOIN borrowers USING(borrowernumber)
+                LEFT JOIN biblio ON  (reserves.biblionumber=biblio.biblionumber)
+                WHERE reserves.reserve_id = ?";
+    my $sth = $dbh->prepare($strsth);
+    $sth->execute($reserve_id);
 
+    my $data = $sth->fetchrow_hashref;
+    return $data;
 }
 
 =head2 IsAvailableForItemLevelRequest
@@ -1392,7 +1410,7 @@ be the target of an item-level hold request.
 Note that IsAvailableForItemLevelRequest() does not
 check if the staff operator is authorized to place
 a request on the item - in particular,
-this routine does not check IndependantBranches
+this routine does not check IndependentBranches
 and canreservefromotherbranches.
 
 =cut
@@ -1438,13 +1456,13 @@ sub IsAvailableForItemLevelRequest {
     if (C4::Context->preference('AllowOnShelfHolds')) {
         return $available_per_item;
     } else {
-        return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); 
+        return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "Waiting"));
     }
 }
 
 =head2 AlterPriority
 
-  AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
+  AlterPriority( $where, $reserve_id );
 
 This function changes a reserve's priority up, down, to the top, or to the bottom.
 Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was placed
@@ -1452,29 +1470,30 @@ Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was
 =cut
 
 sub AlterPriority {
-    my ( $where, $borrowernumber, $biblionumber ) = @_;
+    my ( $where, $reserve_id ) = @_;
 
     my $dbh = C4::Context->dbh;
 
-    ## Find this reserve
-    my $sth = $dbh->prepare('SELECT * FROM reserves WHERE biblionumber = ? AND borrowernumber = ? AND cancellationdate IS NULL');
-    $sth->execute( $biblionumber, $borrowernumber );
-    my $reserve = $sth->fetchrow_hashref();
-    $sth->finish();
+    my $reserve = GetReserve( $reserve_id );
+
+    if ( $reserve->{cancellationdate} ) {
+        warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (".$reserve->{cancellationdate}.')';
+        return;
+    }
 
     if ( $where eq 'up' || $where eq 'down' ) {
-    
-      my $priority = $reserve->{'priority'};        
+
+      my $priority = $reserve->{'priority'};
       $priority = $where eq 'up' ? $priority - 1 : $priority + 1;
-      _FixPriority( $biblionumber, $borrowernumber, $priority )
+      _FixPriority( $reserve_id, $priority )
 
     } elsif ( $where eq 'top' ) {
 
-      _FixPriority( $biblionumber, $borrowernumber, '1' )
+      _FixPriority( $reserve_id, '1' )
 
     } elsif ( $where eq 'bottom' ) {
 
-      _FixPriority( $biblionumber, $borrowernumber, '999999' )
+      _FixPriority( $reserve_id, '999999' )
 
     }
 }
@@ -1488,27 +1507,20 @@ This function sets the lowestPriority field to true if is false, and false if it
 =cut
 
 sub ToggleLowestPriority {
-    my ( $borrowernumber, $biblionumber ) = @_;
+    my ( $reserve_id ) = @_;
 
     my $dbh = C4::Context->dbh;
 
-    my $sth = $dbh->prepare(
-        "UPDATE reserves SET lowestPriority = NOT lowestPriority
-         WHERE biblionumber = ?
-         AND borrowernumber = ?"
-    );
-    $sth->execute(
-        $biblionumber,
-        $borrowernumber,
-    );
+    my $sth = $dbh->prepare( "UPDATE reserves SET lowestPriority = NOT lowestPriority WHERE reserve_id = ?");
+    $sth->execute( $reserve_id );
     $sth->finish;
     
-    _FixPriority( $biblionumber, $borrowernumber, '999999' );
+    _FixPriority( $reserve_id, '999999' );
 }
 
 =head2 ToggleSuspend
 
-  ToggleSuspend( $borrowernumber, $biblionumber );
+  ToggleSuspend( $reserve_id );
 
 This function sets the suspend field to true if is false, and false if it is true.
 If the reserve is currently suspended with a suspend_until date, that date will
@@ -1517,20 +1529,25 @@ be cleared when it is unsuspended.
 =cut
 
 sub ToggleSuspend {
-    my ( $borrowernumber, $biblionumber ) = @_;
+    my ( $reserve_id, $suspend_until ) = @_;
+
+    $suspend_until = output_pref( dt_from_string( $suspend_until ), 'iso' ) if ( $suspend_until );
+
+    my $do_until = ( $suspend_until ) ? '?' : 'NULL';
 
     my $dbh = C4::Context->dbh;
 
     my $sth = $dbh->prepare(
         "UPDATE reserves SET suspend = NOT suspend,
-        suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE suspend_until END
-        WHERE biblionumber = ?
-        AND borrowernumber = ?
+        suspend_until = CASE WHEN suspend = 0 THEN NULL ELSE $do_until END
+        WHERE reserve_id = ?
     ");
-    $sth->execute(
-        $biblionumber,
-        $borrowernumber,
-    );
+
+    my @params;
+    push( @params, $suspend_until ) if ( $suspend_until );
+    push( @params, $reserve_id );
+
+    $sth->execute( @params );
     $sth->finish;
 }
 
@@ -1593,7 +1610,7 @@ sub SuspendAll {
 
 =head2 _FixPriority
 
-  &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
+  &_FixPriority( $reserve_id, $rank, $ignoreSetLowestRank);
 
 Only used internally (so don't export it)
 Changed how this functions works #
@@ -1605,43 +1622,39 @@ in new priority rank
 =cut 
 
 sub _FixPriority {
-    my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_;
+    my ( $reserve_id, $rank, $ignoreSetLowestRank ) = @_;
     my $dbh = C4::Context->dbh;
-     if ( $rank eq "del" ) {
-         CancelReserve( $biblio, undef, $borrowernumber );
-     }
-    if ( $rank eq "W" || $rank eq "0" ) {
+
+    my $res = GetReserve( $reserve_id );
+
+    if ( $rank eq "del" ) {
+         CancelReserve({ reserve_id => $reserve_id });
+    }
+    elsif ( $rank eq "W" || $rank eq "0" ) {
 
         # make sure priority for waiting or in-transit items is 0
-        my $query = qq/
+        my $query = "
             UPDATE reserves
             SET    priority = 0
-            WHERE biblionumber = ?
-              AND borrowernumber = ?
-              AND found IN ('W', 'T')
-        /;
+            WHERE reserve_id = ?
+            AND found IN ('W', 'T')
+        ";
         my $sth = $dbh->prepare($query);
-        $sth->execute( $biblio, $borrowernumber );
+        $sth->execute( $reserve_id );
     }
     my @priority;
-    my @reservedates;
 
     # get whats left
-# FIXME adding a new security in returned elements for changing priority,
-# now, we don't care anymore any reservations with itemnumber linked (suppose a waiting reserve)
-       # This is wrong a waiting reserve has W set
-       # The assumption that having an itemnumber set means waiting is wrong and should be corrected any place it occurs
-    my $query = qq/
-        SELECT borrowernumber, reservedate, constrainttype
+    my $query = "
+        SELECT reserve_id, borrowernumber, reservedate, constrainttype
         FROM   reserves
         WHERE  biblionumber   = ?
-          AND  ((found <> 'W' AND found <> 'T') or found is NULL)
+          AND  ((found <> 'W' AND found <> 'T') OR found IS NULL)
         ORDER BY priority ASC
-    /;
+    ";
     my $sth = $dbh->prepare($query);
-    $sth->execute($biblio);
+    $sth->execute( $res->{'biblionumber'} );
     while ( my $line = $sth->fetchrow_hashref ) {
-        push( @reservedates, $line );
         push( @priority,     $line );
     }
 
@@ -1649,7 +1662,7 @@ sub _FixPriority {
     my $i;
     my $key = -1;    # to allow for 0 to be a valid result
     for ( $i = 0 ; $i < @priority ; $i++ ) {
-        if ( $borrowernumber == $priority[$i]->{'borrowernumber'} ) {
+        if ( $reserve_id == $priority[$i]->{'reserve_id'} ) {
             $key = $i;    # save the index
             last;
         }
@@ -1665,29 +1678,25 @@ sub _FixPriority {
 
     # now fix the priority on those that are left....
     $query = "
-            UPDATE reserves
-            SET    priority = ?
-                WHERE  biblionumber = ?
-                 AND borrowernumber   = ?
-                 AND reservedate = ?
-         AND found IS NULL
+        UPDATE reserves
+        SET    priority = ?
+        WHERE  reserve_id = ?
     ";
     $sth = $dbh->prepare($query);
     for ( my $j = 0 ; $j < @priority ; $j++ ) {
         $sth->execute(
-            $j + 1, $biblio,
-            $priority[$j]->{'borrowernumber'},
-            $priority[$j]->{'reservedate'}
+            $j + 1,
+            $priority[$j]->{'reserve_id'}
         );
         $sth->finish;
     }
     
-    $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
+    $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
     $sth->execute();
     
     unless ( $ignoreSetLowestRank ) {
       while ( my $res = $sth->fetchrow_hashref() ) {
-        _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 );
+        _FixPriority( $res->{'reserve_id'}, '999999', 1 );
       }
     }
 }
@@ -1726,7 +1735,8 @@ sub _Findgroupreserve {
                reserves.priority            AS priority,
                reserves.timestamp           AS timestamp,
                biblioitems.biblioitemnumber AS biblioitemnumber,
-               reserves.itemnumber          AS itemnumber
+               reserves.itemnumber          AS itemnumber,
+               reserves.reserve_id          AS reserve_id
         FROM reserves
         JOIN biblioitems USING (biblionumber)
         JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
@@ -1825,25 +1835,14 @@ sub _koha_notify_reserve {
     my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
     
     # Try to get the borrower's email address
-    my $to_address;
-    my $which_address = C4::Context->preference('AutoEmailPrimaryAddress');
-    # If the system preference is set to 'first valid' (value == OFF), look up email address
-    if ($which_address eq 'OFF') {
-        $to_address = C4::Members::GetFirstValidEmailAddress( $borrowernumber );
-    } else {
-        $to_address = $borrower->{$which_address};
-    }
+    my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber);
     
     my $letter_code;
     my $print_mode = 0;
     my $messagingprefs;
     if ( $to_address || $borrower->{'smsalertnumber'} ) {
         $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold_Filled' } );
-
-        return if ( !defined( $messagingprefs->{'letter_code'} ) );
-        $letter_code = $messagingprefs->{'letter_code'};
     } else {
-        $letter_code = 'HOLD_PRINT';
         $print_mode = 1;
     }
 
@@ -1859,9 +1858,8 @@ sub _koha_notify_reserve {
 
     my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress');
 
-    my $letter =  C4::Letters::GetPreparedLetter (
+    my %letter_params = (
         module => 'reserves',
-        letter_code => $letter_code,
         branchcode => $reserve->{branchcode},
         tables => {
             'branches'  => $branch_details,
@@ -1871,11 +1869,13 @@ sub _koha_notify_reserve {
             'items', $reserve->{'itemnumber'},
         },
         substitute => { today => C4::Dates->new()->output() },
-    ) or die "Could not find a letter called '$letter_code' in the 'reserves' module";
-
+    );
 
 
     if ( $print_mode ) {
+        $letter_params{ 'letter_code' } = 'HOLD_PRINT';
+        my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
+
         C4::Letters::EnqueueLetter( {
             letter => $letter,
             borrowernumber => $borrowernumber,
@@ -1885,8 +1885,10 @@ sub _koha_notify_reserve {
         return;
     }
 
-    if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) {
-        # aka, 'email' in ->{'transports'}
+    if ( $to_address && defined $messagingprefs->{transports}->{'email'} ) {
+        $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'email'};
+        my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
+
         C4::Letters::EnqueueLetter(
             {   letter                 => $letter,
                 borrowernumber         => $borrowernumber,
@@ -1896,7 +1898,10 @@ sub _koha_notify_reserve {
         );
     }
 
-    if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) {
+    if ( $borrower->{'smsalertnumber'} && defined $messagingprefs->{transports}->{'sms'} ) {
+        $letter_params{ 'letter_code' } = $messagingprefs->{transports}->{'sms'};
+        my $letter =  C4::Letters::GetPreparedLetter ( %letter_params ) or die "Could not find a letter called '$letter_params{'letter_code'}' in the 'reserves' module";
+
         C4::Letters::EnqueueLetter(
             {   letter                 => $letter,
                 borrowernumber         => $borrowernumber,
@@ -1995,9 +2000,15 @@ sub MoveReserve {
             ModReserveFill($borr_res);
         }
 
-        if ($cancelreserve) { # cancel reserves on this item
-            CancelReserve(0, $res->{'itemnumber'}, $res->{'borrowernumber'});
-            CancelReserve($res->{'biblionumber'}, 0, $res->{'borrowernumber'});
+        if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
+            RevertWaitingStatus({ itemnumber => $itemnumber });
+        }
+        elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item
+            CancelReserve({
+                biblionumber   => $res->{'biblionumber'},
+                itemnumber     => $res->{'itemnumber'},
+                borrowernumber => $res->{'borrowernumber'}
+            });
         }
     }
 }
@@ -2013,7 +2024,7 @@ This shifts the holds from C<$from_biblio> to C<$to_biblio> and reorders them by
 sub MergeHolds {
     my ( $dbh, $to_biblio, $from_biblio ) = @_;
     my $sth = $dbh->prepare(
-        "SELECT count(*) as reservenumber FROM reserves WHERE biblionumber = ?"
+        "SELECT count(*) as reserve_count FROM reserves WHERE biblionumber = ?"
     );
     $sth->execute($from_biblio);
     if ( my $data = $sth->fetchrow_hashref() ) {
@@ -2046,6 +2057,101 @@ sub MergeHolds {
     }
 }
 
+=head2 RevertWaitingStatus
+
+  $success = RevertWaitingStatus({ itemnumber => $itemnumber });
+
+  Reverts a 'waiting' hold back to a regular hold with a priority of 1.
+
+  Caveat: Any waiting hold fixed with RevertWaitingStatus will be an
+          item level hold, even if it was only a bibliolevel hold to
+          begin with. This is because we can no longer know if a hold
+          was item-level or bib-level after a hold has been set to
+          waiting status.
+
+=cut
+
+sub RevertWaitingStatus {
+    my ( $params ) = @_;
+    my $itemnumber = $params->{'itemnumber'};
+
+    return unless ( $itemnumber );
+
+    my $dbh = C4::Context->dbh;
+
+    ## Get the waiting reserve we want to revert
+    my $query = "
+        SELECT * FROM reserves
+        WHERE itemnumber = ?
+        AND found IS NOT NULL
+    ";
+    my $sth = $dbh->prepare( $query );
+    $sth->execute( $itemnumber );
+    my $reserve = $sth->fetchrow_hashref();
+
+    ## Increment the priority of all other non-waiting
+    ## reserves for this bib record
+    $query = "
+        UPDATE reserves
+        SET
+          priority = priority + 1
+        WHERE
+          biblionumber =  ?
+        AND
+          priority > 0
+    ";
+    $sth = $dbh->prepare( $query );
+    $sth->execute( $reserve->{'biblionumber'} );
+
+    ## Fix up the currently waiting reserve
+    $query = "
+    UPDATE reserves
+    SET
+      priority = 1,
+      found = NULL,
+      waitingdate = NULL
+    WHERE
+      reserve_id = ?
+    ";
+    $sth = $dbh->prepare( $query );
+    return $sth->execute( $reserve->{'reserve_id'} );
+}
+
+=head2 GetReserveId
+
+  $reserve_id = GetReserveId({ biblionumber => $biblionumber, borrowernumber => $borrowernumber [, itemnumber => $itemnumber ] });
+
+  Returnes the first reserve id that matches the given criteria
+
+=cut
+
+sub GetReserveId {
+    my ( $params ) = @_;
+
+    return unless ( ( $params->{'biblionumber'} || $params->{'itemnumber'} ) && $params->{'borrowernumber'} );
+
+    my $dbh = C4::Context->dbh();
+
+    my $sql = "SELECT reserve_id FROM reserves WHERE ";
+
+    my @params;
+    my @limits;
+    foreach my $key ( keys %$params ) {
+        if ( defined( $params->{$key} ) ) {
+            push( @limits, "$key = ?" );
+            push( @params, $params->{$key} );
+        }
+    }
+
+    $sql .= join( " AND ", @limits );
+
+    my $sth = $dbh->prepare( $sql );
+    $sth->execute( @params );
+    my $row = $sth->fetchrow_hashref();
+
+    return $row->{'reserve_id'};
+}
+
 =head2 ReserveSlip
 
   ReserveSlip($branchcode, $borrowernumber, $biblionumber)
@@ -2069,9 +2175,9 @@ sub ReserveSlip {
         tables => {
             'reserves'    => $reserve,
             'branches'    => $reserve->{branchcode},
-            'borrowers'   => $reserve,
-            'biblio'      => $reserve,
-            'items'       => $reserve,
+            'borrowers'   => $reserve->{borrowernumber},
+            'biblio'      => $reserve->{biblionumber},
+            'items'       => $reserve->{itemnumber},
         },
     );
 }