X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FReserves.pm;h=1f89127eec38da87968c57ac6bce86f2aa09426f;hb=7e83c7ea3857a7dcca5ae610d63382c2674a1846;hp=d1cb88eb317b09d705da41489bbf8fe23678b55b;hpb=dc1d934c8f966dfbb824613f9ef034519acd2f05;p=koha.git diff --git a/C4/Reserves.pm b/C4/Reserves.pm index d1cb88eb31..1f89127eec 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -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 @@ -98,7 +102,7 @@ BEGIN { &GetReservesToBranch &GetReserveCount &GetReserveFee - &GetReserveInfo + &GetReserveInfo &GetReserveStatus &GetOtherReserves @@ -109,6 +113,7 @@ BEGIN { &ModReserveStatus &ModReserveCancelAll &ModReserveMinusPriority + &MoveReserve &CheckReserves &CanBookBeReserved @@ -116,10 +121,16 @@ BEGIN { &CancelReserve &CancelExpiredReserves + &AutoUnsuspendReserves + &IsAvailableForItemLevelRequest &AlterPriority &ToggleLowestPriority + + &ReserveSlip + &ToggleSuspend + &SuspendAll ); @EXPORT_OK = qw( MergeHolds ); } @@ -193,32 +204,31 @@ sub AddReserve { # 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 $branchcode = $borrower->{branchcode}; - my $branch_details = C4::Branch::GetBranchDetail($branchcode); - my $admin_email_address =$branch_details->{'branchemail'} || 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; + my $branch_details = C4::Branch::GetBranchDetail($borrower->{branchcode}); + if ( my $letter = C4::Letters::GetPreparedLetter ( + module => 'reserves', + letter_code => 'HOLDPLACED', + branchcode => $branch, + tables => { + 'branches' => $branch_details, + 'borrowers' => $borrower, + 'biblio' => $biblionumber, + }, + ) ) { + + my $admin_email_address =$branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); + + C4::Letters::EnqueueLetter( + { letter => $letter, + borrowernumber => $borrowernumber, + message_transport_type => 'email', + from_address => $admin_email_address, + to_address => $admin_email_address, + } + ); } - - 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/ @@ -235,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>. @@ -251,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, @@ -262,7 +290,9 @@ sub GetReservesFromBiblionumber { itemnumber, reservenotes, expirationdate, - lowestPriority + lowestPriority, + suspend, + suspend_until FROM reserves WHERE biblionumber = ? "; unless ( $all_dates ) { @@ -317,7 +347,7 @@ sub GetReservesFromBiblionumber { =head2 GetReservesFromItemnumber - ( $reservedate, $borrowernumber, $branchcode ) = GetReservesFromItemnumber($itemnumber); + ( $reservedate, $borrowernumber, $branchcode, $reserve_id ) = GetReservesFromItemnumber($itemnumber); TODO :: Description here @@ -327,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=? "; @@ -336,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 @@ -500,11 +530,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; @@ -523,7 +553,7 @@ sub GetOtherReserves { my ($itemnumber) = @_; my $messages; my $nextreservinfo; - my ( $restype, $checkreserves ) = CheckReserves($itemnumber); + my ( undef, $checkreserves, undef ) = CheckReserves($itemnumber); if ($checkreserves) { my $iteminfo = GetItem($itemnumber); if ( $iteminfo->{'holdingbranch'} ne $checkreserves->{'branchcode'} ) { @@ -531,8 +561,7 @@ sub GetOtherReserves { #minus priorities of others reservs ModReserveMinusPriority( $itemnumber, - $checkreserves->{'borrowernumber'}, - $iteminfo->{'biblionumber'} + $checkreserves->{'reserve_id'}, ); #launch the subroutine dotransfer @@ -549,8 +578,7 @@ sub GetOtherReserves { $messages->{'waiting'} = 1; ModReserveMinusPriority( $itemnumber, - $checkreserves->{'borrowernumber'}, - $iteminfo->{'biblionumber'} + $checkreserves->{'reserve_id'}, ); ModReserveStatus($itemnumber,'W'); } @@ -676,7 +704,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=?" @@ -699,22 +727,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 ) { @@ -724,22 +754,50 @@ 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 - ($status, $reserve) = &CheckReserves($itemnumber); - ($status, $reserve) = &CheckReserves(undef, $barcode); + ($status, $reserve, $all_reserves) = &CheckReserves($itemnumber); + ($status, $reserve, $all_reserves) = &CheckReserves(undef, $barcode); Find a book in the reserves. @@ -804,11 +862,11 @@ sub CheckReserves { # 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. + return ( '' ) unless $itemnumber; # bail if we got nothing. # if item is not for loan it cannot be reserved either..... # execpt where items.notforloan < 0 : This indicates the item is holdable. - return ( 0, 0 ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype; + return ( '' ) if ( $notforloan_per_item > 0 ) or $notforloan_per_itemtype; # Find this item in the reserves my @reserves = _Findgroupreserve( $bibitem, $biblio, $itemnumber ); @@ -822,11 +880,11 @@ sub CheckReserves { my $priority = 10000000; foreach my $res (@reserves) { if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) { - return ( "Waiting", $res ); # Found it + return ( "Waiting", $res, \@reserves ); # Found it } else { # See if this item is more important than what we've got so far if ( $res->{'priority'} && $res->{'priority'} < $priority ) { - my $borrowerinfo=C4::Members::GetMemberDetails($res->{'borrowernumber'}); + my $borrowerinfo=C4::Members::GetMember(borrowernumber => $res->{'borrowernumber'}); my $iteminfo=C4::Items::GetItem($itemnumber); my $branch=C4::Circulation::_GetCircControlBranch($iteminfo,$borrowerinfo); my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'}); @@ -843,11 +901,10 @@ sub CheckReserves { # We return the most important (i.e. next) reservation. if ($highest) { $highest->{'itemnumber'} = $item; - return ( "Reserved", $highest ); - } - else { - return ( 0, 0 ); + return ( "Reserved", $highest, \@reserves ); } + + return ( '' ); } =head2 CancelExpiredReserves @@ -860,124 +917,113 @@ Cancels all reserves with an expiration date from before today. sub CancelExpiredReserves { + # Cancel reserves that have passed their expiration date. my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( " SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) AND expirationdate IS NOT NULL + AND found IS NULL " ); $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 + if ( C4::Context->preference("ExpireReservesMaxPickUpDelay") ) { + my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay"); + my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge"); + + my $query = "SELECT * FROM reserves WHERE TO_DAYS( NOW() ) - TO_DAYS( waitingdate ) > ? AND found = 'W' AND priority = 0"; + $sth = $dbh->prepare( $query ); + $sth->execute( $max_pickup_delay ); + + while (my $res = $sth->fetchrow_hashref ) { + if ( $charge ) { + manualinvoice($res->{'borrowernumber'}, $res->{'itemnumber'}, 'Hold waiting too long', 'F', $charge); + } + + CancelReserve({ reserve_id => $res->{'reserve_id'} }); + } + } + } -=head2 CancelReserve +=head2 AutoUnsuspendReserves - &CancelReserve($biblionumber, $itemnumber, $borrowernumber); + AutoUnsuspendReserves(); -Cancels a reserve. +Unsuspends all suspended reserves with a suspend_until date from before today. + +=cut -Use either C<$biblionumber> or C<$itemnumber> to specify the item to -cancel, but not both: if both are given, C<&CancelReserve> does -nothing. +sub AutoUnsuspendReserves { -C<$borrowernumber> is the borrower number of the patron on whose -behalf the book was reserved. + my $dbh = C4::Context->dbh; + + my $query = "UPDATE reserves SET suspend = 0, suspend_until = NULL WHERE DATE( suspend_until ) < DATE( CURDATE() )"; + my $sth = $dbh->prepare( $query ); + $sth->execute(); + +} + +=head2 CancelReserve -If C<$biblionumber> was given, C<&CancelReserve> also adjusts the -priorities of the other people who are waiting on the book. + CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber ] }); + +Cancels a reserve. =cut sub CancelReserve { - my ( $biblio, $item, $borr ) = @_; + my ( $params ) = @_; + + my $reserve_id = $params->{'reserve_id'}; + $reserve_id = GetReserveId( $params ) unless ( $reserve_id ); + + return unless ( $reserve_id ); + 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. @@ -1007,49 +1053,67 @@ itemnumber and supplying itemnumber. =cut sub ModReserve { - #subroutine to update a reserve - my ( $rank, $biblio, $borrower, $branch , $itemnumber) = @_; - 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 = qq/ - UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL - WHERE biblionumber = ? - AND borrowernumber = ? - /; + my $query = " + UPDATE reserves SET priority = ? ,branchcode = ?, itemnumber = ?, found = NULL, waitingdate = NULL + WHERE reserve_id = ? + "; my $sth = $dbh->prepare($query); - $sth->execute( $rank, $branch,$itemnumber, $biblio, $borrower); + $sth->execute( $rank, $branchcode, $itemnumber, $reserve_id ); $sth->finish; - _FixPriority( $biblio, $borrower, $rank); + + 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 reserve_id = ?", undef, ( $suspend_until, $reserve_id ) ); + } else { + $dbh->do("UPDATE reserves SET suspend_until = NULL WHERE reserve_id = ?", undef, ( $reserve_id ) ); + } + } + + _FixPriority( $reserve_id, $rank ); } } @@ -1069,6 +1133,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'}; @@ -1117,7 +1182,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 ); } } @@ -1137,13 +1202,9 @@ sub ModReserveStatus { #first : check if we have a reservation for this item . my ($itemnumber, $newstatus) = @_; - my $dbh = C4::Context->dbh; - my $query = " UPDATE reserves - SET found=?,waitingdate = now() - WHERE itemnumber=? - AND found IS NULL - AND priority = 0 - "; + my $dbh = C4::Context->dbh; + + my $query = "UPDATE reserves SET found = ?, waitingdate = NOW() WHERE itemnumber = ? AND found IS NULL AND priority = 0"; my $sth_set = $dbh->prepare($query); $sth_set->execute( $newstatus, $itemnumber ); @@ -1196,15 +1257,15 @@ sub ModReserveAffect { } else { # affect the reserve to Waiting as well. - $query = " - UPDATE reserves - SET priority = 0, - found = 'W', - waitingdate=now(), - itemnumber = ? - WHERE borrowernumber = ? - AND biblionumber = ? - "; + $query = " + UPDATE reserves + SET priority = 0, + found = 'W', + waitingdate = NOW(), + itemnumber = ? + WHERE borrowernumber = ? + AND biblionumber = ? + "; } $sth = $dbh->prepare($query); $sth->execute( $itemnumber, $borrowernumber,$biblionumber); @@ -1231,7 +1292,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); @@ -1243,30 +1304,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. @@ -1274,49 +1334,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 @@ -1341,7 +1399,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 @@ -1387,13 +1445,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 @@ -1401,29 +1459,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' ) } } @@ -1437,27 +1496,110 @@ 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( $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 +be cleared when it is unsuspended. + +=cut + +sub ToggleSuspend { + 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 $do_until END + WHERE reserve_id = ? + "); + + my @params; + push( @params, $suspend_until ) if ( $suspend_until ); + push( @params, $reserve_id ); + + $sth->execute( @params ); + $sth->finish; } +=head2 SuspendAll + + SuspendAll( + borrowernumber => $borrowernumber, + [ biblionumber => $biblionumber, ] + [ suspend_until => $suspend_until, ] + [ suspend => $suspend ] + ); + + This function accepts a set of hash keys as its parameters. + It requires either borrowernumber or biblionumber, or both. + + suspend_until is wholly optional. + +=cut + +sub SuspendAll { + my %params = @_; + + my $borrowernumber = $params{'borrowernumber'} || undef; + my $biblionumber = $params{'biblionumber'} || undef; + my $suspend_until = $params{'suspend_until'} || undef; + my $suspend = defined( $params{'suspend'} ) ? $params{'suspend'} : 1; + + $suspend_until = C4::Dates->new( $suspend_until )->output("iso") if ( defined( $suspend_until ) ); + + return unless ( $borrowernumber || $biblionumber ); + + my ( $query, $sth, $dbh, @query_params ); + + $query = "UPDATE reserves SET suspend = ? "; + push( @query_params, $suspend ); + if ( !$suspend ) { + $query .= ", suspend_until = NULL "; + } elsif ( $suspend_until ) { + $query .= ", suspend_until = ? "; + push( @query_params, $suspend_until ); + } + $query .= " WHERE "; + if ( $borrowernumber ) { + $query .= " borrowernumber = ? "; + push( @query_params, $borrowernumber ); + } + $query .= " AND " if ( $borrowernumber && $biblionumber ); + if ( $biblionumber ) { + $query .= " biblionumber = ? "; + push( @query_params, $biblionumber ); + } + $query .= " AND found IS NULL "; + + $dbh = C4::Context->dbh; + $sth = $dbh->prepare( $query ); + $sth->execute( @query_params ); + $sth->finish; +} + + =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 # @@ -1469,43 +1611,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 ); } @@ -1513,7 +1651,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; } @@ -1529,29 +1667,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 ); } } } @@ -1590,7 +1724,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) @@ -1599,6 +1734,7 @@ sub _Findgroupreserve { AND item_level_request = 1 AND itemnumber = ? AND reservedate <= CURRENT_DATE() + AND suspend = 0 /; my $sth = $dbh->prepare($item_level_target_query); $sth->execute($itemnumber); @@ -1629,6 +1765,7 @@ sub _Findgroupreserve { AND item_level_request = 0 AND hold_fill_targets.itemnumber = ? AND reservedate <= CURRENT_DATE() + AND suspend = 0 /; $sth = $dbh->prepare($title_level_target_query); $sth->execute($itemnumber); @@ -1660,6 +1797,7 @@ sub _Findgroupreserve { OR reserves.constrainttype='a' ) AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?) AND reserves.reservedate <= CURRENT_DATE() + AND suspend = 0 /; $sth = $dbh->prepare($query); $sth->execute( $biblio, $bibitem, $itemnumber ); @@ -1686,25 +1824,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; } @@ -1720,23 +1847,24 @@ sub _koha_notify_reserve { my $admin_email_address = $branch_details->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); - 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', $borrowernumber ); - C4::Letters::parseletter( $letter, 'biblio', $biblionumber ); - C4::Letters::parseletter( $letter, 'reserves', $borrowernumber, $biblionumber ); + my %letter_params = ( + module => 'reserves', + branchcode => $reserve->{branchcode}, + tables => { + 'branches' => $branch_details, + 'borrowers' => $borrower, + 'biblio' => $biblionumber, + 'reserves' => $reserve, + 'items', $reserve->{'itemnumber'}, + }, + substitute => { today => C4::Dates->new()->output() }, + ); - 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 ( $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, @@ -1746,8 +1874,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, @@ -1757,7 +1887,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, @@ -1816,6 +1949,59 @@ sub _ShiftPriorityByDateAndPriority { return $new_priority; # so the caller knows what priority they wind up receiving } +=head2 MoveReserve + + MoveReserve( $itemnumber, $borrowernumber, $cancelreserve ) + +Use when checking out an item to handle reserves +If $cancelreserve boolean is set to true, it will remove existing reserve + +=cut + +sub MoveReserve { + my ( $itemnumber, $borrowernumber, $cancelreserve ) = @_; + + my ( $restype, $res, $all_reserves ) = CheckReserves( $itemnumber ); + return unless $res; + + my $biblionumber = $res->{biblionumber}; + my $biblioitemnumber = $res->{biblioitemnumber}; + + if ($res->{borrowernumber} == $borrowernumber) { + ModReserveFill($res); + } + else { + # warn "Reserved"; + # The item is reserved by someone else. + # Find this item in the reserves + + my $borr_res; + foreach (@$all_reserves) { + $_->{'borrowernumber'} == $borrowernumber or next; + $_->{'biblionumber'} == $biblionumber or next; + + $borr_res = $_; + last; + } + + if ( $borr_res ) { + # The item is reserved by the current patron + ModReserveFill($borr_res); + } + + 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'} + }); + } + } +} + =head2 MergeHolds MergeHolds($dbh,$to_biblio, $from_biblio); @@ -1827,7 +2013,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() ) { @@ -1860,6 +2046,130 @@ 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) + + Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef + +=cut + +sub ReserveSlip { + my ($branch, $borrowernumber, $biblionumber) = @_; + +# return unless ( C4::Context->boolean_preference('printreserveslips') ); + + my $reserve = GetReserveInfo($borrowernumber,$biblionumber ) + or return; + + return C4::Letters::GetPreparedLetter ( + module => 'circulation', + letter_code => 'RESERVESLIP', + branchcode => $branch, + tables => { + 'reserves' => $reserve, + 'branches' => $reserve->{branchcode}, + 'borrowers' => $reserve->{borrowernumber}, + 'biblio' => $reserve->{biblionumber}, + 'items' => $reserve->{itemnumber}, + }, + ); +} =head1 AUTHOR