Merge commit 'kc/master'
[koha.git] / C4 / Reserves.pm
index d8f3b0b..bcb27bb 100644 (file)
@@ -15,13 +15,13 @@ package C4::Reserves;
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 
 use strict;
-# use warnings;  # FIXME: someday
+#use warnings; FIXME - Bug 2505
 use C4::Context;
 use C4::Biblio;
 use C4::Members;
@@ -50,14 +50,15 @@ C4::Reserves - Koha functions for dealing with reservation.
 
 =head1 DESCRIPTION
 
-  this modules provides somes functions to deal with reservations.
-  
+This modules provides somes functions to deal with reservations.
+
   Reserves are stored in reserves table.
   The following columns contains important values :
   - priority >0      : then the reserve is at 1st stage, and not yet affected to any item.
              =0      : then the reserve is being dealed
   - found : NULL       : means the patron requested the 1st available, and we haven't choosen the item
-            W(aiting)  : the reserve has an itemnumber affected, and is on the way
+            T(ransit)  : the reserve is linked to an item but is in transit to the pickup branch
+            W(aiting)  : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
             F(inished) : the reserve has been completed, and is done
   - itemnumber : empty : the reserve is still unaffected to an item
                  filled: the reserve is attached to an item
@@ -67,20 +68,18 @@ C4::Reserves - Koha functions for dealing with reservation.
   a library having it run "transfertodo", and clic on the list    
          if there is no transfer to do, the reserve waiting
          patron can pick it up                                    P =0, F=W,    I=filled 
-         if there is a transfer to do, write in branchtransfer    P =0, F=NULL, I=filled
+         if there is a transfer to do, write in branchtransfer    P =0, F=T,    I=filled
            The pickup library recieve the book, it check in       P =0, F=W,    I=filled
   The patron borrow the book                                      P =0, F=F,    I=filled
   
   ==== 2nd use case ====
   patron requests a document, a given item,
     If pickup is holding branch                                   P =0, F=W,   I=filled
-    If transfer needed, write in branchtransfer                   P =0, F=NULL, I=filled
-        The pickup library recieve the book, it checks it in      P =0, F=W,    I=filled
+    If transfer needed, write in branchtransfer                   P =0, F=T,    I=filled
+        The pickup library receive the book, it checks it in      P =0, F=W,    I=filled
   The patron borrow the book                                      P =0, F=F,    I=filled
-  
-=head1 FUNCTIONS
 
-=over 2
+=head1 FUNCTIONS
 
 =cut
 
@@ -100,7 +99,8 @@ BEGIN {
         &GetReserveCount
         &GetReserveFee
                &GetReserveInfo
-    
+        &GetReserveStatus
+        
         &GetOtherReserves
         
         &ModReserveFill
@@ -123,7 +123,7 @@ BEGIN {
     );
 }    
 
-=item AddReserve
+=head2 AddReserve
 
     AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found)
 
@@ -142,7 +142,11 @@ sub AddReserve {
     my $const   = lc substr( $constraint, 0, 1 );
     $resdate = format_date_in_iso( $resdate ) if ( $resdate );
     $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
-    $expdate = format_date_in_iso( $expdate ) if ( $expdate );
+    if ($expdate) {
+        $expdate = format_date_in_iso( $expdate );
+    } else {
+        undef $expdate; # make reserves.expirationdate default to null rather than '0000-00-00'
+    }
     if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
        # Make room in reserves for this before those of a later reserve date
        $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
@@ -228,9 +232,9 @@ sub AddReserve {
     return;     # FIXME: why not have a useful return value?
 }
 
-=item GetReservesFromBiblionumber
+=head2 GetReservesFromBiblionumber
 
-($count, $title_reserves) = &GetReserves($biblionumber);
+  ($count, $title_reserves) = &GetReserves($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>.
@@ -308,11 +312,11 @@ sub GetReservesFromBiblionumber {
     return ( $#results + 1, \@results );
 }
 
-=item GetReservesFromItemnumber
+=head2 GetReservesFromItemnumber
 
  ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber);
 
-   TODO :: Description here
+TODO :: Description here
 
 =cut
 
@@ -333,12 +337,12 @@ sub GetReservesFromItemnumber {
     return ( $reservedate, $borrowernumber, $branchcode );
 }
 
-=item GetReservesFromBorrowernumber
+=head2 GetReservesFromBorrowernumber
 
     $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus);
-    
-    TODO :: Descritpion
-    
+
+TODO :: Descritpion
+
 =cut
 
 sub GetReservesFromBorrowernumber {
@@ -367,9 +371,9 @@ sub GetReservesFromBorrowernumber {
     return @$data;
 }
 #-------------------------------------------------------------------------------------
-=item CanBookBeReserved
+=head2 CanBookBeReserved
 
-$error = &CanBookBeReserved($borrowernumber, $biblionumber)
+  $error = &CanBookBeReserved($borrowernumber, $biblionumber)
 
 =cut
 
@@ -448,11 +452,11 @@ sub CanBookBeReserved{
     
 }
 
-=item CanItemBeReserved
+=head2 CanItemBeReserved
 
-$error = &CanItemBeReserved($borrowernumber, $itemnumber)
+  $error = &CanItemBeReserved($borrowernumber, $itemnumber)
 
-this function return 1 if an item can be issued by this borrower.
+This function return 1 if an item can be issued by this borrower.
 
 =cut
 
@@ -539,9 +543,9 @@ sub CanItemBeReserved{
     }
 }
 #--------------------------------------------------------------------------------
-=item GetReserveCount
+=head2 GetReserveCount
 
-$number = &GetReserveCount($borrowernumber);
+  $number = &GetReserveCount($borrowernumber);
 
 this function returns the number of reservation for a borrower given on input arg.
 
@@ -563,9 +567,9 @@ sub GetReserveCount {
     return $row->{counter};
 }
 
-=item GetOtherReserves
+=head2 GetOtherReserves
 
-($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
+  ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber);
 
 Check queued list of this document and check if this document must be  transfered
 
@@ -613,9 +617,9 @@ sub GetOtherReserves {
     return ( $messages, $nextreservinfo );
 }
 
-=item GetReserveFee
+=head2 GetReserveFee
 
-$fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
+  $fee = GetReserveFee($borrowernumber,$biblionumber,$constraint,$biblionumber);
 
 Calculate the fee for a reserve
 
@@ -716,9 +720,9 @@ sub GetReserveFee {
     return $fee;
 }
 
-=item GetReservesToBranch
+=head2 GetReservesToBranch
 
-@transreserv = GetReservesToBranch( $frombranch );
+  @transreserv = GetReservesToBranch( $frombranch );
 
 Get reserve list for a given branch
 
@@ -743,9 +747,9 @@ sub GetReservesToBranch {
     return (@transreserv);
 }
 
-=item GetReservesForBranch
+=head2 GetReservesForBranch
 
-@transreserv = GetReservesForBranch($frombranch);
+  @transreserv = GetReservesForBranch($frombranch);
 
 =cut
 
@@ -776,7 +780,19 @@ sub GetReservesForBranch {
     return (@transreserv);
 }
 
-=item CheckReserves
+sub GetReserveStatus {
+    my ($itemnumber) = @_;
+    
+    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;
+}
+
+=head2 CheckReserves
 
   ($status, $reserve) = &CheckReserves($itemnumber);
   ($status, $reserve) = &CheckReserves(undef, $barcode);
@@ -869,12 +885,12 @@ sub CheckReserves {
     }
 }
 
-=item CancelExpiredReserves
+=head2 CancelExpiredReserves
 
   CancelExpiredReserves();
-  
-  Cancels all reserves with an expiration date from before today.
-  
+
+Cancels all reserves with an expiration date from before today.
+
 =cut
 
 sub CancelExpiredReserves {
@@ -892,7 +908,7 @@ sub CancelExpiredReserves {
   
 }
 
-=item CancelReserve
+=head2 CancelReserve
 
   &CancelReserve($biblionumber, $itemnumber, $borrowernumber);
 
@@ -990,17 +1006,13 @@ sub CancelReserve {
         $sth->execute( $biblio, $borr );
 
         # now fix the priority on the others....
-        _FixPriority( $priority, $biblio );
+        _FixPriority( $biblio, $borr );
     }
 }
 
-=item ModReserve
+=head2 ModReserve
 
-=over 4
-
-ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
-
-=back
+  ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber])
 
 Change a hold request's priority or cancel it.
 
@@ -1022,7 +1034,7 @@ C<$rank> is a non-zero integer; if supplied, the itemnumber
 of the hold request is set accordingly; if omitted, the itemnumber
 is cleared.
 
-FIXME: Note that the forgoing can have the effect of causing
+B<FIXME:> Note that the forgoing can have the effect of causing
 item-level hold requests to turn into title-level requests.  This
 will be fixed once reserves has separate columns for requested
 itemnumber and supplying itemnumber.
@@ -1076,7 +1088,7 @@ sub ModReserve {
     }
 }
 
-=item ModReserveFill
+=head2 ModReserveFill
 
   &ModReserveFill($reserve);
 
@@ -1140,13 +1152,13 @@ sub ModReserveFill {
     # now fix the priority on the others (if the priority wasn't
     # already sorted!)....
     unless ( $priority == 0 ) {
-        _FixPriority( $priority, $biblionumber );
+        _FixPriority( $biblionumber, $borrowernumber );
     }
 }
 
-=item ModReserveStatus
+=head2 ModReserveStatus
 
-&ModReserveStatus($itemnumber, $newstatus);
+  &ModReserveStatus($itemnumber, $newstatus);
 
 Update the reserve status for the active (priority=0) reserve.
 
@@ -1175,9 +1187,9 @@ sub ModReserveStatus {
     }
 }
 
-=item ModReserveAffect
+=head2 ModReserveAffect
 
-&ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
+  &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend);
 
 This function affect an item and a status for a given reserve
 The itemnumber parameter is used to find the biblionumber.
@@ -1187,6 +1199,7 @@ to the correct reserve.
 if $transferToDo is not set, then the status is set to "Waiting" as well.
 otherwise, a transfer is on the way, and the end of the transfer will 
 take care of the waiting status
+
 =cut
 
 sub ModReserveAffect {
@@ -1210,7 +1223,8 @@ sub ModReserveAffect {
     $query = "
         UPDATE reserves
         SET    priority = 0,
-               itemnumber = ?
+               itemnumber = ?,
+               found = 'T'
         WHERE borrowernumber = ?
           AND biblionumber = ?
     ";
@@ -1238,11 +1252,11 @@ sub ModReserveAffect {
     return;
 }
 
-=item ModReserveCancelAll
+=head2 ModReserveCancelAll
 
-($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
+  ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
 
-    function to cancel reserv,check other reserves, and transfer document if it's necessary
+function to cancel reserv,check other reserves, and transfer document if it's necessary
 
 =cut
 
@@ -1260,9 +1274,9 @@ sub ModReserveCancelAll {
     return ( $messages, $nextreservinfo );
 }
 
-=item ModReserveMinusPriority
+=head2 ModReserveMinusPriority
 
-&ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
+  &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber)
 
 Reduce the values of queuded list     
 
@@ -1285,34 +1299,53 @@ sub ModReserveMinusPriority {
     _FixPriority($biblionumber, $borrowernumber, '0');
 }
 
-=item GetReserveInfo
+=head2 GetReserveInfo
 
-&GetReserveInfo($borrowernumber,$biblionumber);
+  &GetReserveInfo($borrowernumber,$biblionumber);
+
+Get item and borrower details for a current hold.
+Current implementation this query should have a single result.
 
- Get item and borrower details for a current hold.
- Current implementation this query should have a single result.
 =cut
 
 sub GetReserveInfo {
        my ( $borrowernumber, $biblionumber ) = @_;
     my $dbh = C4::Context->dbh;
-       my $strsth="SELECT reservedate, reservenotes, reserves.borrowernumber,
-                               reserves.biblionumber, reserves.branchcode,
-                               notificationdate, reminderdate, priority, found,
-                               firstname, surname, phone, 
-                               email, address, address2,
-                               cardnumber, city, zipcode,
-                               biblio.title, biblio.author,
-                               items.holdingbranch, items.itemcallnumber, items.itemnumber, 
-                               barcode, notes
-                       FROM reserves left join items 
-                               ON items.itemnumber=reserves.itemnumber , 
-                               borrowers, biblio 
+       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=?  &&
-                               reserves.biblionumber=? && 
-                               reserves.borrowernumber=borrowers.borrowernumber && 
-                               reserves.biblionumber=biblio.biblionumber ";
+                               reserves.borrowernumber=?
+                               AND reserves.biblionumber=?";
        my $sth = $dbh->prepare($strsth); 
        $sth->execute($borrowernumber,$biblionumber);
 
@@ -1321,13 +1354,9 @@ sub GetReserveInfo {
 
 }
 
-=item IsAvailableForItemLevelRequest
-
-=over 4
-
-my $is_available = IsAvailableForItemLevelRequest($itemnumber);
+=head2 IsAvailableForItemLevelRequest
 
-=back
+  my $is_available = IsAvailableForItemLevelRequest($itemnumber);
 
 Checks whether a given item record is available for an
 item-level hold request.  An item is available if
@@ -1393,17 +1422,19 @@ sub IsAvailableForItemLevelRequest {
     if (C4::Context->preference('AllowOnShelfHolds')) {
         return $available_per_item;
     } else {
-        return ($available_per_item and $item->{onloan}); 
+        return ($available_per_item and ($item->{onloan} or GetReserveStatus($itemnumber) eq "W")); 
     }
 }
 
-=item AlterPriority
-AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
+=head2 AlterPriority
+
+  AlterPriority( $where, $borrowernumber, $biblionumber, $reservedate );
 
 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
 
 =cut
+
 sub AlterPriority {
     my ( $where, $borrowernumber, $biblionumber ) = @_;
 
@@ -1432,10 +1463,12 @@ sub AlterPriority {
     }
 }
 
-=item ToggleLowestPriority
-ToggleLowestPriority( $borrowernumber, $biblionumber );
+=head2 ToggleLowestPriority
+
+  ToggleLowestPriority( $borrowernumber, $biblionumber );
 
 This function sets the lowestPriority field to true if is false, and false if it is true.
+
 =cut
 
 sub ToggleLowestPriority {
@@ -1457,16 +1490,16 @@ sub ToggleLowestPriority {
     _FixPriority( $biblionumber, $borrowernumber, '999999' );
 }
 
-=item _FixPriority
+=head2 _FixPriority
 
-&_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
+  &_FixPriority($biblio,$borrowernumber,$rank,$ignoreSetLowestRank);
 
- Only used internally (so don't export it)
- Changed how this functions works #
- Now just gets an array of reserves in the rank order and updates them with
- the array index (+1 as array starts from 0)
- and if $rank is supplied will splice item from the array and splice it back in again
- in new priority rank
+Only used internally (so don't export it)
+Changed how this functions works #
+Now just gets an array of reserves in the rank order and updates them with
+the array index (+1 as array starts from 0)
+and if $rank is supplied will splice item from the array and splice it back in again
+in new priority rank
 
 =cut 
 
@@ -1478,13 +1511,13 @@ sub _FixPriority {
      }
     if ( $rank eq "W" || $rank eq "0" ) {
 
-        # make sure priority for waiting items is 0
+        # make sure priority for waiting or in-transit items is 0
         my $query = qq/
             UPDATE reserves
             SET    priority = 0
             WHERE biblionumber = ?
               AND borrowernumber = ?
-              AND found ='W'
+              AND found IN ('W', 'T')
         /;
         my $sth = $dbh->prepare($query);
         $sth->execute( $biblio, $borrowernumber );
@@ -1501,7 +1534,7 @@ sub _FixPriority {
         SELECT borrowernumber, reservedate, constrainttype
         FROM   reserves
         WHERE  biblionumber   = ?
-          AND  ((found <> 'W') or found is NULL)
+          AND  ((found <> 'W' AND found <> 'T') or found is NULL)
         ORDER BY priority ASC
     /;
     my $sth = $dbh->prepare($query);
@@ -1558,7 +1591,7 @@ sub _FixPriority {
     }
 }
 
-=item _Findgroupreserve
+=head2 _Findgroupreserve
 
   @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber);
 
@@ -1671,13 +1704,9 @@ sub _Findgroupreserve {
     return @results;
 }
 
-=item _koha_notify_reserve
-
-=over 4
+=head2 _koha_notify_reserve
 
-_koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
-
-=back
+  _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber );
 
 Sends a notification to the patron that their hold has been filled (through
 ModReserveAffect, _not_ ModReserveFill)
@@ -1688,7 +1717,7 @@ sub _koha_notify_reserve {
     my ($itemnumber, $borrowernumber, $biblionumber) = @_;
 
     my $dbh = C4::Context->dbh;
-    my $borrower = C4::Members::GetMember( $borrowernumber );
+    my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber);
     my $letter_code;
     my $print_mode = 0;
     my $messagingprefs;
@@ -1761,25 +1790,21 @@ sub _koha_notify_reserve {
     }
 }
 
-=item _ShiftPriorityByDateAndPriority
-
-=over 4
+=head2 _ShiftPriorityByDateAndPriority
 
-$new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
-
-=back
+  $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
 
 This increments the priority of all reserves after the one
- with either the lowest date after C<$reservedate>
- or the lowest priority after C<$priority>.
+with either the lowest date after C<$reservedate>
+or the lowest priority after C<$priority>.
 
 It effectively makes room for a new reserve to be inserted with a certain
- priority, which is returned.
+priority, which is returned.
 
 This is most useful when the reservedate can be set by the user.  It allows
- the new reserve to be placed before other reserves that have a later
- reservedate.  Since priority also is set by the form in reserves/request.pl
- the sub accounts for that too.
+the new reserve to be placed before other reserves that have a later
+reservedate.  Since priority also is set by the form in reserves/request.pl
+the sub accounts for that too.
 
 =cut
 
@@ -1787,39 +1812,36 @@ sub _ShiftPriorityByDateAndPriority {
     my ( $biblio, $resdate, $new_priority ) = @_;
 
     my $dbh = C4::Context->dbh;
-    my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC";
+    my $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND ( reservedate > ? OR priority > ? ) ORDER BY priority ASC LIMIT 1";
     my $sth = $dbh->prepare( $query );
     $sth->execute( $biblio, $resdate, $new_priority );
-    my ( $min_priority ) = $sth->fetchrow;
-    $sth->finish;  # $sth might have more data.
+    my $min_priority = $sth->fetchrow;
+    # if no such matches are found, $new_priority remains as original value
     $new_priority = $min_priority if ( $min_priority );
-    my $updated_priority = $new_priority + 1;
 
-    $query = "
- UPDATE reserves
-    SET priority = ?
-  WHERE biblionumber = ?
-    AND borrowernumber = ?
-    AND reservedate = ?
-    AND found IS NULL";
+    # Shift the priority up by one; works in conjunction with the next SQL statement
   $query = "UPDATE reserves
+              SET priority = priority+1
+              WHERE biblionumber = ?
+              AND borrowernumber = ?
+              AND reservedate = ?
+              AND found IS NULL";
     my $sth_update = $dbh->prepare( $query );
 
-    $query = "SELECT * FROM reserves WHERE priority >= ?";
+    # Select all reserves for the biblio with priority greater than $new_priority, and order greatest to least
+    $query = "SELECT borrowernumber, reservedate FROM reserves WHERE priority >= ? AND biblionumber = ? ORDER BY priority DESC";
     $sth = $dbh->prepare( $query );
-    $sth->execute( $new_priority );
+    $sth->execute( $new_priority, $biblio );
     while ( my $row = $sth->fetchrow_hashref ) {
-       $sth_update->execute( $updated_priority, $biblio, $row->{borrowernumber}, $row->{reservedate} );
-       $updated_priority++;
+       $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} );
     }
 
-    return $new_priority;  # so the caller knows what priority they end up at
+    return $new_priority;  # so the caller knows what priority they wind up receiving
 }
 
-=back
-
 =head1 AUTHOR
 
-Koha Developement team <info@koha.org>
+Koha Development Team <info@koha.org>
 
 =cut