X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FReserves.pm;h=4ca555f4f05d5b1bf8993a5101aac32aa95b1eb4;hb=34936223f385713bb87f12432e4adf2fe430264c;hp=3593dd8864fc6a56be87abfc70a6037b7763940d;hpb=af2c4340adcfe337004beaba90df33ca05f8f082;p=koha.git diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 3593dd8864..4ca555f4f0 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -1,6 +1,3 @@ -# -*- tab-width: 8 -*- -# NOTE: This file uses standard 8-character tabs - package C4::Reserves; # Copyright 2000-2002 Katipo Communications @@ -18,14 +15,16 @@ 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 - Bug 2505 use C4::Context; use C4::Biblio; +use C4::Members; use C4::Items; use C4::Search; use C4::Circulation; @@ -33,15 +32,14 @@ use C4::Accounts; # for _koha_notify_reserve use C4::Members::Messaging; -use C4::Members qw( GetMember ); +use C4::Members qw(); 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); -my $library_name = C4::Context->preference("LibraryName"); - =head1 NAME C4::Reserves - Koha functions for dealing with reservation. @@ -52,8 +50,8 @@ 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. @@ -79,10 +77,8 @@ C4::Reserves - Koha functions for dealing with reservation. 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 The patron borrow the book P =0, F=F, I=filled - -=head1 FUNCTIONS -=over 2 +=head1 FUNCTIONS =cut @@ -102,7 +98,8 @@ BEGIN { &GetReserveCount &GetReserveFee &GetReserveInfo - + &GetReserveStatus + &GetOtherReserves &ModReserveFill @@ -113,22 +110,28 @@ BEGIN { &ModReserveMinusPriority &CheckReserves + &CanBookBeReserved + &CanItemBeReserved &CancelReserve + &CancelExpiredReserves &IsAvailableForItemLevelRequest + + &AlterPriority + &ToggleLowestPriority ); } -=item AddReserve +=head2 AddReserve - AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$notes,$title,$checkitem,$found) + AddReserve($branch,$borrowernumber,$biblionumber,$constraint,$bibitems,$priority,$resdate,$expdate,$notes,$title,$checkitem,$found) =cut sub AddReserve { my ( $branch, $borrowernumber, $biblionumber, - $constraint, $bibitems, $priority, $notes, + $constraint, $bibitems, $priority, $resdate, $expdate, $notes, $title, $checkitem, $found ) = @_; my $fee = @@ -136,9 +139,17 @@ 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 ($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 ); + } my $waitingdate; # If the reserv had the waiting status, we had the value of the resdate @@ -165,18 +176,45 @@ sub AddReserve { my $query = qq/ INSERT INTO reserves (borrowernumber,biblionumber,reservedate,branchcode,constrainttype, - priority,reservenotes,itemnumber,found,waitingdate) + priority,reservenotes,itemnumber,found,waitingdate,expirationdate) VALUES (?,?,?,?,?, - ?,?,?,?,?) + ?,?,?,?,?,?) /; my $sth = $dbh->prepare($query); $sth->execute( $borrowernumber, $biblionumber, $resdate, $branch, $const, $priority, $notes, $checkitem, - $found, $waitingdate + $found, $waitingdate, $expdate ); + # Send e-mail to librarian if syspref is active + if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){ + my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); + my $biblio = GetBiblioData($biblionumber); + my $letter = C4::Letters::getletter( 'reserves', 'HOLDPLACED'); + my $admin_email_address = C4::Context->preference('KohaAdminEmailAddress'); + + my %keys = (%$borrower, %$biblio); + foreach my $key (keys %keys) { + my $replacefield = "<<$key>>"; + $letter->{content} =~ s/$replacefield/$keys{$key}/g; + $letter->{title} =~ s/$replacefield/$keys{$key}/g; + } + + C4::Letters::EnqueueLetter( + { letter => $letter, + borrowernumber => $borrowernumber, + message_transport_type => 'email', + from_address => $admin_email_address, + to_address => $admin_email_address, + } + ); + + + } + + #} ($const eq "o" || $const eq "e") or return; # FIXME: why not have a useful return value? $query = qq/ @@ -189,21 +227,22 @@ sub AddReserve { foreach (@$bibitems) { $sth->execute($borrowernumber, $biblionumber, $resdate, $_); } + return; # FIXME: why not have a useful return value? } -=item GetReservesFromBiblionumber +=head2 GetReservesFromBiblionumber -@borrowerreserv=&GetReserves($biblionumber,$itemnumber,$borrowernumber); + ($count, $title_reserves) = &GetReserves($biblionumber); -this function get the list of reservation for an C<$biblionumber>, C<$itemnumber> or C<$borrowernumber> -given on input arg. -Only 1 argument has to be passed. +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>. =cut sub GetReservesFromBiblionumber { - my ( $biblionumber, $itemnumber, $borrowernumber ) = @_; + my ($biblionumber) = shift or return (0, []); + my ($all_dates) = shift; my $dbh = C4::Context->dbh; # Find the desired items in the reserves @@ -217,10 +256,15 @@ sub GetReservesFromBiblionumber { constrainttype, found, itemnumber, - reservenotes + reservenotes, + expirationdate, + lowestPriority 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; @@ -237,9 +281,7 @@ sub GetReservesFromBiblionumber { AND reservedate = ? '; my $csth = $dbh->prepare($query); - $csth->execute( $data->{biblionumber}, $data->{borrowernumber}, - $data->{reservedate}, ); - + $csth->execute($data->{biblionumber}, $data->{borrowernumber}, $data->{reservedate}); my @bibitemno; while ( my $bibitemnos = $csth->fetchrow_array ) { push( @bibitemno, $bibitemnos ); # FIXME: inefficient: use fetchall_arrayref @@ -250,8 +292,8 @@ sub GetReservesFromBiblionumber { # reserved by same person on same day my $bdata; if ( $count > 1 ) { - $bdata = GetBiblioItemData( $bibitemno[$i] ); - $i++; + $bdata = GetBiblioItemData( $bibitemno[$i] ); # FIXME: This doesn't make sense. + $i++; # $i can increase each pass, but the next @bibitemno might be smaller? } else { # Look up the book we just found. @@ -269,34 +311,37 @@ sub GetReservesFromBiblionumber { return ( $#results + 1, \@results ); } -=item GetReservesFromItemnumber +=head2 GetReservesFromItemnumber ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber); - TODO :: Description here +TODO :: Description here =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; return ( $reservedate, $borrowernumber, $branchcode ); } -=item GetReservesFromBorrowernumber +=head2 GetReservesFromBorrowernumber $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus); - - TODO :: Descritpion - + +TODO :: Descritpion + =cut sub GetReservesFromBorrowernumber { @@ -325,10 +370,181 @@ sub GetReservesFromBorrowernumber { return @$data; } #------------------------------------------------------------------------------------- +=head2 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 IN (?, '*')"; + 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; + } + +} + +=head2 CanItemBeReserved + + $error = &CanItemBeReserved($borrowernumber, $itemnumber) -=item GetReserveCount +This function return 1 if an item can be issued by this borrower. -$number = &GetReserveCount($borrowernumber); +=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; + } +} +#-------------------------------------------------------------------------------- +=head2 GetReserveCount + + $number = &GetReserveCount($borrowernumber); this function returns the number of reservation for a borrower given on input arg. @@ -350,9 +566,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 @@ -400,9 +616,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 @@ -503,9 +719,9 @@ sub GetReserveFee { return $fee; } -=item GetReservesToBranch +=head2 GetReservesToBranch -@transreserv = GetReservesToBranch( $frombranch ); + @transreserv = GetReservesToBranch( $frombranch ); Get reserve list for a given branch @@ -530,9 +746,9 @@ sub GetReservesToBranch { return (@transreserv); } -=item GetReservesForBranch +=head2 GetReservesForBranch -@transreserv = GetReservesForBranch($frombranch); + @transreserv = GetReservesForBranch($frombranch); =cut @@ -563,9 +779,22 @@ 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); Find a book in the reserves. @@ -593,65 +822,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; } @@ -659,10 +873,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 ); } @@ -671,7 +884,30 @@ sub CheckReserves { } } -=item CancelReserve +=head2 CancelExpiredReserves + + CancelExpiredReserves(); + +Cancels all reserves with an expiration date from before today. + +=cut + +sub CancelExpiredReserves { + + my $dbh = C4::Context->dbh; + my $sth = $dbh->prepare( " + SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) + AND expirationdate IS NOT NULL + " ); + $sth->execute(); + + while ( my $res = $sth->fetchrow_hashref() ) { + CancelReserve( $res->{'biblionumber'}, '', $res->{'borrowernumber'} ); + } + +} + +=head2 CancelReserve &CancelReserve($biblionumber, $itemnumber, $borrowernumber); @@ -773,13 +1009,9 @@ sub CancelReserve { } } -=item ModReserve - -=over 4 +=head2 ModReserve -ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber]) - -=back + ModReserve($rank, $biblio, $borrower, $branch[, $itemnumber]) Change a hold request's priority or cancel it. @@ -801,7 +1033,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 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. @@ -855,7 +1087,7 @@ sub ModReserve { } } -=item ModReserveFill +=head2 ModReserveFill &ModReserveFill($reserve); @@ -923,9 +1155,9 @@ sub ModReserveFill { } } -=item ModReserveStatus +=head2 ModReserveStatus -&ModReserveStatus($itemnumber, $newstatus); + &ModReserveStatus($itemnumber, $newstatus); Update the reserve status for the active (priority=0) reserve. @@ -948,11 +1180,15 @@ sub ModReserveStatus { "; my $sth_set = $dbh->prepare($query); $sth_set->execute( $newstatus, $itemnumber ); + + if ( C4::Context->preference("ReturnToShelvingCart") && $newstatus ) { + CartToShelf( $itemnumber ); + } } -=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. @@ -962,6 +1198,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 { @@ -973,6 +1210,12 @@ sub ModReserveAffect { my $sth = $dbh->prepare("SELECT biblionumber FROM items WHERE itemnumber=?"); $sth->execute($itemnumber); my ($biblionumber) = $sth->fetchrow; + + # get request - need to find out if item is already + # waiting in order to not send duplicate hold filled notifications + my $request = GetReserveInfo($borrowernumber, $biblionumber); + my $already_on_shelf = ($request && $request->{found} eq 'W') ? 1 : 0; + # If we affect a reserve that has to be transfered, don't set to Waiting my $query; if ($transferToDo) { @@ -998,16 +1241,20 @@ sub ModReserveAffect { } $sth = $dbh->prepare($query); $sth->execute( $itemnumber, $borrowernumber,$biblionumber); - _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo ); + _koha_notify_reserve( $itemnumber, $borrowernumber, $biblionumber ) if ( !$transferToDo && !$already_on_shelf ); + + if ( C4::Context->preference("ReturnToShelvingCart") ) { + CartToShelf( $itemnumber ); + } 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 @@ -1025,9 +1272,9 @@ sub ModReserveCancelAll { return ( $messages, $nextreservinfo ); } -=item ModReserveMinusPriority +=head2 ModReserveMinusPriority -&ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber) + &ModReserveMinusPriority($itemnumber,$borrowernumber,$biblionumber) Reduce the values of queuded list @@ -1050,34 +1297,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); @@ -1086,13 +1352,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 @@ -1158,25 +1420,89 @@ 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")); + } +} + +=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 ) = @_; + + 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(); + + if ( $where eq 'up' || $where eq 'down' ) { + + my $priority = $reserve->{'priority'}; + $priority = $where eq 'up' ? $priority - 1 : $priority + 1; + _FixPriority( $biblionumber, $borrowernumber, $priority ) + + } elsif ( $where eq 'top' ) { + + _FixPriority( $biblionumber, $borrowernumber, '1' ) + + } elsif ( $where eq 'bottom' ) { + + _FixPriority( $biblionumber, $borrowernumber, '999999' ) + } } -=item _FixPriority +=head2 ToggleLowestPriority + + ToggleLowestPriority( $borrowernumber, $biblionumber ); + +This function sets the lowestPriority field to true if is false, and false if it is true. + +=cut -&_FixPriority($biblio,$borrowernumber,$rank); +sub ToggleLowestPriority { + my ( $borrowernumber, $biblionumber ) = @_; - 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 + my $dbh = C4::Context->dbh; + + my $sth = $dbh->prepare( + "UPDATE reserves SET lowestPriority = NOT lowestPriority + WHERE biblionumber = ? + AND borrowernumber = ?" + ); + $sth->execute( + $biblionumber, + $borrowernumber, + ); + $sth->finish; + + _FixPriority( $biblionumber, $borrowernumber, '999999' ); +} + +=head2 _FixPriority + + &_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 =cut sub _FixPriority { - my ( $biblio, $borrowernumber, $rank ) = @_; + my ( $biblio, $borrowernumber, $rank, $ignoreSetLowestRank ) = @_; my $dbh = C4::Context->dbh; if ( $rank eq "del" ) { CancelReserve( $biblio, undef, $borrowernumber ); @@ -1252,18 +1578,26 @@ sub _FixPriority { ); $sth->finish; } + + $sth = $dbh->prepare( "SELECT borrowernumber FROM reserves WHERE lowestPriority = 1 ORDER BY priority" ); + $sth->execute(); + + unless ( $ignoreSetLowestRank ) { + while ( my $res = $sth->fetchrow_hashref() ) { + _FixPriority( $biblio, $res->{'borrowernumber'}, '999999', 1 ); + } + } } -=item _Findgroupreserve +=head2 _Findgroupreserve @results = &_Findgroupreserve($biblioitemnumber, $biblionumber, $itemnumber); -****** FIXME ****** -I don't know what this does, because I don't understand how reserve -constraints work. I think the idea is that you reserve a particular -biblio, and the constraint allows you to restrict it to a given -biblioitem (e.g., if you want to borrow the audio book edition of "The -Prophet", rather than the first available publication). +Looks for an item-specific match first, then for a title-level match, returning the +first match found. If neither, then we look for a 3rd kind of match based on +reserve constraints. + +TODO: add more explanation about reserve constraints C<&_Findgroupreserve> returns : C<@results> is an array of references-to-hash whose keys are mostly @@ -1276,19 +1610,20 @@ sub _Findgroupreserve { my ( $bibitem, $biblio, $itemnumber ) = @_; my $dbh = C4::Context->dbh; + # TODO: consolidate at least the SELECT portion of the first 2 queries to a common $select var. # check for exact targetted match my $item_level_target_query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, biblioitems.biblioitemnumber AS biblioitemnumber, - reserves.itemnumber AS itemnumber + reserves.itemnumber AS itemnumber FROM reserves JOIN biblioitems USING (biblionumber) JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber) @@ -1296,6 +1631,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); @@ -1307,17 +1643,17 @@ sub _Findgroupreserve { # check for title-level targetted match my $title_level_target_query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, biblioitems.biblioitemnumber AS biblioitemnumber, - reserves.itemnumber AS itemnumber + reserves.itemnumber AS itemnumber FROM reserves JOIN biblioitems USING (biblionumber) JOIN hold_fill_targets USING (biblionumber, borrowernumber) @@ -1325,6 +1661,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); @@ -1335,25 +1672,26 @@ sub _Findgroupreserve { return @results if @results; my $query = qq/ - SELECT reserves.biblionumber AS biblionumber, - reserves.borrowernumber AS borrowernumber, - reserves.reservedate AS reservedate, - reserves.branchcode AS branchcode, - reserves.cancellationdate AS cancellationdate, - reserves.found AS found, - reserves.reservenotes AS reservenotes, - reserves.priority AS priority, - reserves.timestamp AS timestamp, + SELECT reserves.biblionumber AS biblionumber, + reserves.borrowernumber AS borrowernumber, + reserves.reservedate AS reservedate, + reserves.branchcode AS branchcode, + reserves.cancellationdate AS cancellationdate, + reserves.found AS found, + reserves.reservenotes AS reservenotes, + reserves.priority AS priority, + reserves.timestamp AS timestamp, reserveconstraints.biblioitemnumber AS biblioitemnumber, - reserves.itemnumber AS itemnumber + reserves.itemnumber AS itemnumber FROM reserves LEFT JOIN reserveconstraints ON reserves.biblionumber = reserveconstraints.biblionumber WHERE reserves.biblionumber = ? AND ( ( reserveconstraints.biblioitemnumber = ? AND reserves.borrowernumber = reserveconstraints.borrowernumber - AND reserves.reservedate =reserveconstraints.reservedate ) + 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 ); @@ -1364,13 +1702,9 @@ sub _Findgroupreserve { return @results; } -=item _koha_notify_reserve +=head2 _koha_notify_reserve -=over 4 - -_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) @@ -1381,9 +1715,19 @@ sub _koha_notify_reserve { my ($itemnumber, $borrowernumber, $biblionumber) = @_; my $dbh = C4::Context->dbh; - my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, message_name => 'Hold Filled' } ); - - return if ( !defined( $messagingprefs->{'letter_code'} ) ); + my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); + my $letter_code; + my $print_mode = 0; + my $messagingprefs; + if ( $borrower->{'email'} || $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; + } my $sth = $dbh->prepare(" SELECT * @@ -1397,19 +1741,33 @@ sub _koha_notify_reserve { my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); - my $letter = getletter( 'reserves', $messagingprefs->{'letter_code'} ); + my $letter = getletter( 'reserves', $letter_code ); + die "Could not find a letter called '$letter_code' in the 'reserves' module" unless( $letter ); C4::Letters::parseletter( $letter, 'branches', $reserve->{'branchcode'} ); - C4::Letters::parseletter( $letter, 'borrowers', $reserve->{'borrowernumber'} ); - C4::Letters::parseletter( $letter, 'biblio', $reserve->{'biblionumber'} ); - C4::Letters::parseletter( $letter, 'reserves', $reserve->{'borrowernumber'}, $reserve->{'biblionumber'} ); + C4::Letters::parseletter( $letter, 'borrowers', $borrowernumber ); + C4::Letters::parseletter( $letter, 'biblio', $biblionumber ); + C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber ); if ( $reserve->{'itemnumber'} ) { C4::Letters::parseletter( $letter, 'items', $reserve->{'itemnumber'} ); } + my $today = C4::Dates->new()->output(); + $letter->{'title'} =~ s/<>/$today/g; + $letter->{'content'} =~ s/<>/$today/g; $letter->{'content'} =~ s/<<[a-z0-9_]+\.[a-z0-9]+>>//g; #remove any stragglers - if ( -1 != firstidx { $_ eq 'email' } @{$messagingprefs->{transports}} ) { + if ( $print_mode ) { + C4::Letters::EnqueueLetter( { + letter => $letter, + borrowernumber => $borrowernumber, + message_transport_type => 'print', + } ); + + return; + } + + if ( grep { $_ eq 'email' } @{$messagingprefs->{transports}} ) { # aka, 'email' in ->{'transports'} C4::Letters::EnqueueLetter( { letter => $letter, @@ -1420,7 +1778,7 @@ sub _koha_notify_reserve { ); } - if ( -1 != firstidx { $_ eq 'sms' } @{$messagingprefs->{transports}} ) { + if ( grep { $_ eq 'sms' } @{$messagingprefs->{transports}} ) { C4::Letters::EnqueueLetter( { letter => $letter, borrowernumber => $borrowernumber, @@ -1430,11 +1788,58 @@ sub _koha_notify_reserve { } } -=back +=head2 _ShiftPriorityByDateAndPriority + + $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>. + +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 LIMIT 1"; + my $sth = $dbh->prepare( $query ); + $sth->execute( $biblio, $resdate, $new_priority ); + my $min_priority = $sth->fetchrow; + # if no such matches are found, $new_priority remains as original value + $new_priority = $min_priority if ( $min_priority ); + + # 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 ); + + # 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, $biblio ); + while ( my $row = $sth->fetchrow_hashref ) { + $sth_update->execute( $biblio, $row->{borrowernumber}, $row->{reservedate} ); + } + + return $new_priority; # so the caller knows what priority they wind up receiving +} =head1 AUTHOR -Koha Developement team +Koha Development Team =cut