Modifying Members : Add Mod and GetMember
[koha.git] / C4 / Reserves.pm
index fca00f6..39e0768 100644 (file)
@@ -34,6 +34,7 @@ use C4::Members::Messaging;
 use C4::Members qw( GetMember );
 use C4::Letters;
 use C4::Branch qw( GetBranchDetail );
+use C4::Dates qw( format_date_in_iso );
 use List::MoreUtils qw( firstidx );
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
@@ -124,7 +125,7 @@ BEGIN {
 sub AddReserve {
     my (
         $branch,    $borrowernumber, $biblionumber,
-        $constraint, $bibitems,  $priority,       $notes,
+        $constraint, $bibitems,  $priority, $resdate,  $notes,
         $title,      $checkitem, $found
     ) = @_;
     my $fee =
@@ -132,9 +133,12 @@ sub AddReserve {
             $bibitems );
     my $dbh     = C4::Context->dbh;
     my $const   = lc substr( $constraint, 0, 1 );
-    my @datearr = localtime(time);
-    my $resdate =
-      ( 1900 + $datearr[5] ) . "-" . ( $datearr[4] + 1 ) . "-" . $datearr[3];
+    $resdate = format_date_in_iso( $resdate ) if ( $resdate );
+    $resdate = C4::Dates->today( 'iso' ) unless ( $resdate );
+    if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
+       # Make room in reserves for this before those of a later reserve date
+       $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
+    }
     my $waitingdate;
 
     # If the reserv had the waiting status, we had the value of the resdate
@@ -199,6 +203,7 @@ of the reserves and an arrayref pointing to the reserves for C<$biblionumber>.
 
 sub GetReservesFromBiblionumber {
     my ($biblionumber) = shift or return (0, []);
+    my ($all_dates) = shift;
     my $dbh   = C4::Context->dbh;
 
     # Find the desired items in the reserves
@@ -214,8 +219,11 @@ sub GetReservesFromBiblionumber {
                 itemnumber,
                 reservenotes
         FROM     reserves
-        WHERE biblionumber = ?
-        ORDER BY priority";
+        WHERE biblionumber = ? ";
+    unless ( $all_dates ) {
+        $query .= "AND reservedate <= CURRENT_DATE()";
+    }
+    $query .= "ORDER BY priority";
     my $sth = $dbh->prepare($query);
     $sth->execute($biblionumber);
     my @results;
@@ -271,13 +279,16 @@ sub GetReservesFromBiblionumber {
 =cut
 
 sub GetReservesFromItemnumber {
-    my ( $itemnumber ) = @_;
+    my ( $itemnumber, $all_dates ) = @_;
     my $dbh   = C4::Context->dbh;
     my $query = "
     SELECT reservedate,borrowernumber,branchcode
     FROM   reserves
     WHERE  itemnumber=?
     ";
+    unless ( $all_dates ) {
+       $query .= " AND reservedate <= CURRENT_DATE()";
+    }
     my $sth_res = $dbh->prepare($query);
     $sth_res->execute($itemnumber);
     my ( $reservedate, $borrowernumber,$branchcode ) = $sth_res->fetchrow_array;
@@ -318,7 +329,180 @@ sub GetReservesFromBorrowernumber {
     return @$data;
 }
 #-------------------------------------------------------------------------------------
+=item CanBookBeReserved
+
+$error = &CanBookBeReserved($borrowernumber, $biblionumber)
+
+=cut
+
+sub CanBookBeReserved{
+    my ($borrowernumber, $biblionumber) = @_;
+
+    my $dbh           = C4::Context->dbh;
+    my $biblio        = GetBiblioData($biblionumber);
+    my $borrower      = C4::Members::GetMember(borrowernumber=>$borrowernumber);
+    my $controlbranch = C4::Context->preference('ReservesControlBranch');
+    my $itype         = C4::Context->preference('item-level_itypes');
+    my $reservesrights= 0;
+    my $reservescount = 0;
+    
+    # we retrieve the user rights
+    my @args;
+    my $rightsquery = "SELECT categorycode, itemtype, branchcode, reservesallowed 
+                       FROM issuingrules 
+                       WHERE categorycode = ?";
+    push @args,$borrower->{categorycode};
+
+    if($controlbranch eq "ItemHomeLibrary"){
+        $rightsquery .= " AND branchcode = '*'";
+    }elsif($controlbranch eq "PatronLibrary"){
+        $rightsquery .= " AND branchcode IN (?,'*')";
+        push @args, $borrower->{branchcode};
+    }
+    
+    if(not $itype){
+        $rightsquery .= " AND itemtype IN (?,'*')";
+        push @args, $biblio->{itemtype};
+    }else{
+        $rightsquery .= " AND itemtype = '*'";
+    }
+    
+    $rightsquery .= " ORDER BY categorycode DESC, itemtype DESC, branchcode DESC";
+    
+    my $sthrights = $dbh->prepare($rightsquery);
+    $sthrights->execute(@args);
+    
+    if(my $row = $sthrights->fetchrow_hashref()){
+       $reservesrights = $row->{reservesallowed};
+    }
+    
+    @args = ();
+    # we count how many reserves the borrower have
+    my $countquery = "SELECT count(*) as count
+                      FROM reserves
+                      LEFT JOIN items USING (itemnumber)
+                      LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
+                      LEFT JOIN borrowers USING (borrowernumber)
+                      WHERE borrowernumber = ?
+                    ";
+    push @args, $borrowernumber;
+    
+    if(not $itype){
+           $countquery .= "AND itemtype = ?";
+           push @args, $biblio->{itemtype};
+    }
+    
+    if($controlbranch eq "PatronLibrary"){
+        $countquery .= " AND borrowers.branchcode = ? ";
+        push @args, $borrower->{branchcode};
+    }
+    
+    my $sthcount = $dbh->prepare($countquery);
+    $sthcount->execute(@args);
+    
+    if(my $row = $sthcount->fetchrow_hashref()){
+       $reservescount = $row->{count};
+    }
+    
+    if($reservescount < $reservesrights){
+        return 1;
+    }else{
+        return 0;
+    }
+    
+}
+
+=item CanItemBeReserved
+
+$error = &CanItemBeReserved($borrowernumber, $itemnumber)
+
+this function return 1 if an item can be issued by this borrower.
+
+=cut
 
+sub CanItemBeReserved{
+    my ($borrowernumber, $itemnumber) = @_;
+    
+    my $dbh             = C4::Context->dbh;
+    my $allowedreserves = 0;
+            
+    my $controlbranch = C4::Context->preference('ReservesControlBranch');
+    my $itype         = C4::Context->preference('item-level_itypes') ? "itype" : "itemtype";
+
+    # we retrieve borrowers and items informations #
+    my $item     = GetItem($itemnumber);
+    my $borrower = C4::Members::GetMember('borrowernumber'=>$borrowernumber);     
+    
+    # we retrieve user rights on this itemtype and branchcode
+    my $sth = $dbh->prepare("SELECT categorycode, itemtype, branchcode, reservesallowed 
+                             FROM issuingrules 
+                             WHERE (categorycode in (?,'*') ) 
+                             AND (itemtype IN (?,'*')) 
+                             AND (branchcode IN (?,'*')) 
+                             ORDER BY 
+                               categorycode DESC, 
+                               itemtype     DESC, 
+                               branchcode   DESC;"
+                           );
+                           
+    my $querycount ="SELECT 
+                            count(*) as count
+                            FROM reserves
+                                LEFT JOIN items USING (itemnumber)
+                                LEFT JOIN biblioitems ON (reserves.biblionumber=biblioitems.biblionumber)
+                                LEFT JOIN borrowers USING (borrowernumber)
+                            WHERE borrowernumber = ?
+                                ";
+    
+    
+    my $itemtype     = $item->{$itype};
+    my $categorycode = $borrower->{categorycode};
+    my $branchcode   = "";
+    my $branchfield  = "reserves.branchcode";
+    
+    if( $controlbranch eq "ItemHomeLibrary" ){
+        $branchfield = "items.homebranch";
+        $branchcode = $item->{homebranch};
+    }elsif( $controlbranch eq "PatronLibrary" ){
+        $branchfield = "borrowers.branchcode";
+        $branchcode = $borrower->{branchcode};
+    }
+    
+    # we retrieve rights 
+    $sth->execute($categorycode, $itemtype, $branchcode);
+    if(my $rights = $sth->fetchrow_hashref()){
+        $itemtype        = $rights->{itemtype};
+        $allowedreserves = $rights->{reservesallowed}; 
+    }else{
+        $itemtype = '*';
+    }
+    
+    # we retrieve count
+    
+    $querycount .= "AND $branchfield = ?";
+    
+    $querycount .= " AND $itype = ?" if ($itemtype ne "*");
+    my $sthcount = $dbh->prepare($querycount);
+    
+    if($itemtype eq "*"){
+        $sthcount->execute($borrowernumber, $branchcode);
+    }else{
+        $sthcount->execute($borrowernumber, $branchcode, $itemtype);
+    }
+    
+    my $reservecount = "0";
+    if(my $rowcount = $sthcount->fetchrow_hashref()){
+        $reservecount = $rowcount->{count};
+    }
+    
+    # we check if it's ok or not
+    if( $reservecount < $allowedreserves ){
+        return 1;
+    }else{
+        return 0;
+    }
+}
+#--------------------------------------------------------------------------------
 =item GetReserveCount
 
 $number = &GetReserveCount($borrowernumber);
@@ -559,6 +743,7 @@ sub GetReservesForBranch {
 =item CheckReserves
 
   ($status, $reserve) = &CheckReserves($itemnumber);
+  ($status, $reserve) = &CheckReserves(undef, $barcode);
 
 Find a book in the reserves.
 
@@ -586,65 +771,50 @@ sub CheckReserves {
     my ( $item, $barcode ) = @_;
     my $dbh = C4::Context->dbh;
     my $sth;
+    my $select = "
+    SELECT items.biblionumber,
+           items.biblioitemnumber,
+           itemtypes.notforloan,
+           items.notforloan AS itemnotforloan,
+           items.itemnumber
+    FROM   items
+    LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
+    LEFT JOIN itemtypes   ON biblioitems.itemtype   = itemtypes.itemtype
+    ";
+
     if ($item) {
-        my $qitem = $dbh->quote($item);
-        # Look up the item by itemnumber
-        my $query = "
-            SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan, items.notforloan AS itemnotforloan
-            FROM   items
-            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
-            LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
-            WHERE  itemnumber=$qitem
-        ";
-        $sth = $dbh->prepare($query);
+        $sth = $dbh->prepare("$select WHERE itemnumber = ?");
+        $sth->execute($item);
     }
     else {
-        my $qbc = $dbh->quote($barcode);
-        # Look up the item by barcode
-        my $query = "
-            SELECT items.biblionumber, items.biblioitemnumber, itemtypes.notforloan, items.notforloan AS itemnotforloan
-            FROM   items
-            LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
-            LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
-            WHERE  items.biblioitemnumber = biblioitems.biblioitemnumber
-              AND biblioitems.itemtype = itemtypes.itemtype
-              AND barcode=$qbc
-        ";
-        $sth = $dbh->prepare($query);
-
-        # FIXME - This function uses $item later on. Ought to set it here.
+        $sth = $dbh->prepare("$select WHERE barcode = ?");
+        $sth->execute($barcode);
     }
-    $sth->execute;
-    my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item ) = $sth->fetchrow_array;
-    $sth->finish;
+    # note: we get the itemnumber because we might have started w/ just the barcode.  Now we know for sure we have it.
+    my ( $biblio, $bibitem, $notforloan_per_itemtype, $notforloan_per_item, $itemnumber ) = $sth->fetchrow_array;
+
+    return ( 0, 0 ) unless $itemnumber; # bail if we got nothing.
+
     # if item is not for loan it cannot be reserved either.....
-    #    execption to notforloan is where items.notforloan < 0 :  This indicates the item is holdable. 
+    #    execpt where items.notforloan < 0 :  This indicates the item is holdable. 
     return ( 0, 0 ) if  ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype;
 
-    # get the reserves...
     # Find this item in the reserves
-    my @reserves = _Findgroupreserve( $bibitem, $biblio, $item );
-    my $count    = scalar @reserves;
+    my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber );
 
     # $priority and $highest are used to find the most important item
     # in the list returned by &_Findgroupreserve. (The lower $priority,
     # the more important the item.)
     # $highest is the most important item we've seen so far.
-    my $priority = 10000000;
     my $highest;
-    if ($count) {
+    if (scalar @reserves) {
+        my $priority = 10000000;
         foreach my $res (@reserves) {
-            # FIXME - $item might be undefined or empty: the caller
-            # might be searching by barcode.
-            if ( $res->{'itemnumber'} == $item && $res->{'priority'} == 0) {
-                # Found it
-                return ( "Waiting", $res );
-            }
-            else {
-                # See if this item is more important than what we've got
-                # so far.
-                if ( $res->{'priority'} != 0 && $res->{'priority'} < $priority )
-                {
+            if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
+                return ( "Waiting", $res ); # Found it
+            } else {
+                # See if this item is more important than what we've got so far
+                if ( $res->{'priority'} && $res->{'priority'} < $priority ) {
                     $priority = $res->{'priority'};
                     $highest  = $res;
                 }
@@ -652,10 +822,9 @@ sub CheckReserves {
         }
     }
 
-    # If we get this far, then no exact match was found. Print the
-    # most important item on the list. I think this tells us who's
-    # next in line to get this book.
-    if ($highest) {    # FIXME - $highest might be undefined
+    # If we get this far, then no exact match was found.
+    # We return the most important (i.e. next) reservation.
+    if ($highest) {
         $highest->{'itemnumber'} = $item;
         return ( "Reserved", $highest );
     }
@@ -941,6 +1110,10 @@ sub ModReserveStatus {
     ";
     my $sth_set = $dbh->prepare($query);
     $sth_set->execute( $newstatus, $itemnumber );
+
+    if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) {
+      CartToShelf( $itemnumber );
+    }
 }
 
 =item ModReserveAffect
@@ -999,6 +1172,10 @@ sub ModReserveAffect {
     $sth->execute( $itemnumber, $borrowernumber,$biblionumber);
     _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf );
 
+    if ( C4::Context->preference("ReturnToShelvingCart") ) {
+      CartToShelf( $itemnumber );
+    }
+
     return;
 }
 
@@ -1295,6 +1472,7 @@ sub _Findgroupreserve {
         AND priority > 0
         AND item_level_request = 1
         AND itemnumber = ?
+        AND reservedate <= CURRENT_DATE()
     /;
     my $sth = $dbh->prepare($item_level_target_query);
     $sth->execute($itemnumber);
@@ -1324,6 +1502,7 @@ sub _Findgroupreserve {
         AND priority > 0
         AND item_level_request = 0
         AND hold_fill_targets.itemnumber = ?
+        AND reservedate <= CURRENT_DATE()
     /;
     $sth = $dbh->prepare($title_level_target_query);
     $sth->execute($itemnumber);
@@ -1353,6 +1532,7 @@ sub _Findgroupreserve {
           AND reserves.reservedate    = reserveconstraints.reservedate )
           OR  reserves.constrainttype='a' )
           AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
+          AND reserves.reservedate <= CURRENT_DATE()
     /;
     $sth = $dbh->prepare($query);
     $sth->execute( $biblio, $bibitem, $itemnumber );
@@ -1429,6 +1609,60 @@ sub _koha_notify_reserve {
     }
 }
 
+=item _ShiftPriorityByDateAndPriority
+
+=over 4
+
+$new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
+
+=back
+
+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>.
+
+It effectively makes room for a new reserve to be inserted with a certain
+ 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.
+
+=cut
+
+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 $sth = $dbh->prepare( $query );
+    $sth->execute( $biblio, $resdate, $new_priority );
+    my ( $min_priority ) = $sth->fetchrow;
+    $sth->finish;  # $sth might have more data.
+    $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";
+    my $sth_update = $dbh->prepare( $query );
+
+    $query = "SELECT * FROM reserves WHERE priority >= ?";
+    $sth = $dbh->prepare( $query );
+    $sth->execute( $new_priority );
+    while ( my $row = $sth->fetchrow_hashref ) {
+       $sth_update->execute( $updated_priority, $biblio, $row->{borrowernumber}, $row->{reservedate} );
+       $updated_priority++;
+    }
+
+    return $new_priority;  # so the caller knows what priority they end up at
+}
+
 =back
 
 =head1 AUTHOR