X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FReserves.pm;h=974168f8a38cedbd870f90f368d00a4adf502041;hb=102b1ca4bf3b1721f496417473643ff951c13833;hp=9851181ab6be07e9d5e733279097298a868f16c8;hpb=7cf3c12f5bd2a4a4acb7162e7b3297dbe492350e;p=koha.git diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 9851181ab6..974168f8a3 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -36,6 +36,7 @@ use C4::Members qw(); use C4::Letters; use C4::Log; +use Koha::Biblios; use Koha::DateUtils; use Koha::Calendar; use Koha::Database; @@ -47,6 +48,8 @@ use Koha::IssuingRules; use Koha::Items; use Koha::ItemTypes; use Koha::Patrons; +use Koha::CirculationRules; +use Koha::Account::Lines; use List::MoreUtils qw( firstidx any ); use Carp; @@ -103,12 +106,6 @@ BEGIN { @EXPORT = qw( &AddReserve - &GetReserve - &GetReservesFromBorrowernumber - &GetReservesForBranch - &GetReservesToBranch - &GetReserveCount - &GetReserveInfo &GetReserveStatus &GetOtherReserves @@ -125,15 +122,12 @@ BEGIN { &CanBookBeReserved &CanItemBeReserved &CanReserveBeCanceledFromOpac - &CancelReserve &CancelExpiredReserves &AutoUnsuspendReserves &IsAvailableForItemLevelRequest - &OPACItemHoldsAllowed - &AlterPriority &ToggleLowestPriority @@ -179,6 +173,16 @@ sub AddReserve { $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' }); + # if we have an item selectionned, and the pickup branch is the same as the holdingbranch + # of the document, we force the value $priority and $found . + if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) { + $priority = 0; + my $item = Koha::Items->find( $checkitem ); # FIXME Prevent bad calls + if ( $item->holdingbranch eq $branch ) { + $found = 'W'; + } + } + if ( C4::Context->preference('AllowHoldDateInFuture') ) { # Make room in reserves for this before those of a later reserve date @@ -211,6 +215,7 @@ sub AddReserve { itemtype => $itemtype, } )->store(); + $hold->set_waiting() if $found eq 'W'; logaction( 'HOLDS', 'CREATE', $hold->id, Dumper($hold->unblessed) ) if C4::Context->preference('HoldsLog'); @@ -227,15 +232,16 @@ sub AddReserve { # Send e-mail to librarian if syspref is active if(C4::Context->preference("emailLibrarianWhenHoldIsPlaced")){ - my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); - my $library = Koha::Libraries->find($borrower->{branchcode})->unblessed; + my $patron = Koha::Patrons->find( $borrowernumber ); + my $library = $patron->library; if ( my $letter = C4::Letters::GetPreparedLetter ( module => 'reserves', letter_code => 'HOLDPLACED', branchcode => $branch, + lang => $patron->lang, tables => { - 'branches' => $library, - 'borrowers' => $borrower, + 'branches' => $library->unblessed, + 'borrowers' => $patron->unblessed, 'biblio' => $biblionumber, 'biblioitems' => $biblionumber, 'items' => $checkitem, @@ -243,7 +249,7 @@ sub AddReserve { }, ) ) { - my $admin_email_address = $library->{'branchemail'} || C4::Context->preference('KohaAdminEmailAddress'); + my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress'); C4::Letters::EnqueueLetter( { letter => $letter, @@ -259,62 +265,9 @@ sub AddReserve { return $reserve_id; } -=head2 GetReserve - - $res = GetReserve( $reserve_id ); - - Return the current reserve. - -=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 ); - return $sth->fetchrow_hashref(); -} - -=head2 GetReservesFromBorrowernumber - - $borrowerreserv = GetReservesFromBorrowernumber($borrowernumber,$tatus); - -TODO :: Descritpion - -=cut - -sub GetReservesFromBorrowernumber { - my ( $borrowernumber, $status ) = @_; - my $dbh = C4::Context->dbh; - my $sth; - if ($status) { - $sth = $dbh->prepare(" - SELECT * - FROM reserves - WHERE borrowernumber=? - AND found =? - ORDER BY reservedate - "); - $sth->execute($borrowernumber,$status); - } else { - $sth = $dbh->prepare(" - SELECT * - FROM reserves - WHERE borrowernumber=? - ORDER BY reservedate - "); - $sth->execute($borrowernumber); - } - my $data = $sth->fetchall_arrayref({}); - return @$data; -} - =head2 CanBookBeReserved - $canReserve = &CanBookBeReserved($borrowernumber, $biblionumber) + $canReserve = &CanBookBeReserved($borrowernumber, $biblionumber, $branchcode) if ($canReserve eq 'OK') { #We can reserve this Item! } See CanItemBeReserved() for possible return values. @@ -322,63 +275,68 @@ See CanItemBeReserved() for possible return values. =cut sub CanBookBeReserved{ - my ($borrowernumber, $biblionumber) = @_; + my ($borrowernumber, $biblionumber, $pickup_branchcode) = @_; - my $items = GetItemnumbersForBiblio($biblionumber); + my @itemnumbers = Koha::Items->search({ biblionumber => $biblionumber})->get_column("itemnumber"); #get items linked via host records my @hostitems = get_hostitemnumbers_of($biblionumber); if (@hostitems){ - push (@$items,@hostitems); + push (@itemnumbers, @hostitems); } - my $canReserve; - foreach my $item (@$items) { - $canReserve = CanItemBeReserved( $borrowernumber, $item ); - return 'OK' if $canReserve eq 'OK'; + my $canReserve = { status => '' }; + foreach my $itemnumber (@itemnumbers) { + $canReserve = CanItemBeReserved( $borrowernumber, $itemnumber, $pickup_branchcode ); + return { status => 'OK' } if $canReserve->{status} eq 'OK'; } return $canReserve; } =head2 CanItemBeReserved - $canReserve = &CanItemBeReserved($borrowernumber, $itemnumber) - if ($canReserve eq 'OK') { #We can reserve this Item! } + $canReserve = &CanItemBeReserved($borrowernumber, $itemnumber, $branchcode) + if ($canReserve->{status} eq 'OK') { #We can reserve this Item! } -@RETURNS OK, if the Item can be reserved. - ageRestricted, if the Item is age restricted for this borrower. - damaged, if the Item is damaged. - cannotReserveFromOtherBranches, if syspref 'canreservefromotherbranches' is OK. - tooManyReserves, if the borrower has exceeded his maximum reserve amount. - notReservable, if holds on this item are not allowed +@RETURNS { status => OK }, if the Item can be reserved. + { status => ageRestricted }, if the Item is age restricted for this borrower. + { status => damaged }, if the Item is damaged. + { status => cannotReserveFromOtherBranches }, if syspref 'canreservefromotherbranches' is OK. + { status => tooManyReserves, limit => $limit }, if the borrower has exceeded their maximum reserve amount. + { status => notReservable }, if holds on this item are not allowed + { status => libraryNotFound }, if given branchcode is not an existing library + { status => libraryNotPickupLocation }, if given branchcode is not configured to be a pickup location + { status => cannotBeTransferred }, if branch transfer limit applies on given item and branchcode =cut sub CanItemBeReserved { - my ( $borrowernumber, $itemnumber ) = @_; + my ( $borrowernumber, $itemnumber, $pickup_branchcode ) = @_; my $dbh = C4::Context->dbh; my $ruleitemtype; # itemtype of the matching issuing rule my $allowedreserves = 0; # Total number of holds allowed across all records my $holds_per_record = 1; # Total number of holds allowed for this one given record + my $holds_per_day; # Default to unlimited # we retrieve borrowers and items informations # # item->{itype} will come for biblioitems if necessery - my $item = GetItem($itemnumber); - my $biblioData = C4::Biblio::GetBiblioData( $item->{biblionumber} ); - my $borrower = C4::Members::GetMember( 'borrowernumber' => $borrowernumber ); + my $item = C4::Items::GetItem($itemnumber); + my $biblio = Koha::Biblios->find( $item->{biblionumber} ); + my $patron = Koha::Patrons->find( $borrowernumber ); + my $borrower = $patron->unblessed; # If an item is damaged and we don't allow holds on damaged items, we can stop right here - return 'damaged' + return { status =>'damaged' } if ( $item->{damaged} && !C4::Context->preference('AllowHoldsOnDamagedItems') ); # Check for the age restriction my ( $ageRestriction, $daysToAgeRestriction ) = - C4::Circulation::GetAgeRestriction( $biblioData->{agerestriction}, $borrower ); - return 'ageRestricted' if $daysToAgeRestriction && $daysToAgeRestriction > 0; + C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower ); + return { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0; # Check that the patron doesn't have an item level hold on this item already - return 'itemAlreadyOnHold' + return { status =>'itemAlreadyOnHold' } if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count(); my $controlbranch = C4::Context->preference('ReservesControlBranch'); @@ -409,6 +367,7 @@ sub CanItemBeReserved { $ruleitemtype = $rights->{itemtype}; $allowedreserves = $rights->{reservesallowed}; $holds_per_record = $rights->{holds_per_record}; + $holds_per_day = $rights->{holds_per_day}; } else { $ruleitemtype = '*'; @@ -423,7 +382,19 @@ sub CanItemBeReserved { } ); if ( $holds->count() >= $holds_per_record ) { - return "tooManyHoldsForThisRecord"; + return { status => "tooManyHoldsForThisRecord", limit => $holds_per_record }; + } + + my $today_holds = Koha::Holds->search({ + borrowernumber => $borrowernumber, + reservedate => dt_from_string->date + }); + + if ( defined $holds_per_day && + ( ( $holds_per_day > 0 && $today_holds->count() >= $holds_per_day ) + or ( $holds_per_day == 0 ) ) + ) { + return { status => 'tooManyReservesToday', limit => $holds_per_day }; } # we retrieve count @@ -454,7 +425,25 @@ sub CanItemBeReserved { # we check if it's ok or not if ( $reservecount >= $allowedreserves ) { - return 'tooManyReserves'; + return { status => 'tooManyReserves', limit => $allowedreserves }; + } + + # Now we need to check hold limits by patron category + my $rule = Koha::CirculationRules->get_effective_rule( + { + categorycode => $borrower->{categorycode}, + branchcode => $branchcode, + rule_name => 'max_holds', + } + ); + if ( $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) { + my $total_holds_count = Koha::Holds->search( + { + borrowernumber => $borrower->{borrowernumber} + } + )->count(); + + return { status => 'tooManyReserves', limit => $rule->rule_value} if $total_holds_count >= $rule->rule_value; } my $circ_control_branch = @@ -463,13 +452,13 @@ sub CanItemBeReserved { C4::Circulation::GetBranchItemRule( $circ_control_branch, $item->itype ); if ( $branchitemrule->{holdallowed} == 0 ) { - return 'notReservable'; + return { status => 'notReservable' }; } if ( $branchitemrule->{holdallowed} == 1 && $borrower->{branchcode} ne $item->homebranch ) { - return 'cannotReserveFromOtherBranches'; + return { status => 'cannotReserveFromOtherBranches' }; } # If reservecount is ok, we check item branch if IndependentBranches is ON @@ -479,11 +468,27 @@ sub CanItemBeReserved { { my $itembranch = $item->homebranch; if ( $itembranch ne $borrower->{branchcode} ) { - return 'cannotReserveFromOtherBranches'; + return { status => 'cannotReserveFromOtherBranches' }; + } + } + + if ($pickup_branchcode) { + my $destination = Koha::Libraries->find({ + branchcode => $pickup_branchcode, + }); + + unless ($destination) { + return { status => 'libraryNotFound' }; + } + unless ($destination->pickup_location) { + return { status => 'libraryNotPickupLocation' }; + } + unless ($item->can_be_transferred({ to => $destination })) { + return 'cannotBeTransferred'; } } - return 'OK'; + return { status => 'OK' }; } =head2 CanReserveBeCanceledFromOpac @@ -500,39 +505,15 @@ sub CanReserveBeCanceledFromOpac { my ($reserve_id, $borrowernumber) = @_; return unless $reserve_id and $borrowernumber; - my $reserve = GetReserve($reserve_id); + my $reserve = Koha::Holds->find($reserve_id); - return 0 unless $reserve->{borrowernumber} == $borrowernumber; - return 0 if ( $reserve->{found} eq 'W' ) or ( $reserve->{found} eq 'T' ); + return 0 unless $reserve->borrowernumber == $borrowernumber; + return 0 if ( $reserve->found eq 'W' ) or ( $reserve->found eq 'T' ); return 1; } -=head2 GetReserveCount - - $number = &GetReserveCount($borrowernumber); - -this function returns the number of reservation for a borrower given on input arg. - -=cut - -sub GetReserveCount { - my ($borrowernumber) = @_; - - my $dbh = C4::Context->dbh; - - my $query = " - SELECT COUNT(*) AS counter - FROM reserves - WHERE borrowernumber = ? - "; - my $sth = $dbh->prepare($query); - $sth->execute($borrowernumber); - my $row = $sth->fetchrow_hashref; - return $row->{counter}; -} - =head2 GetOtherReserves ($messages,$nextreservinfo)=$GetOtherReserves(itemnumber); @@ -591,13 +572,24 @@ sub GetOtherReserves { sub ChargeReserveFee { my ( $borrowernumber, $fee, $title ) = @_; - return if !$fee || $fee==0; # the last test is needed to include 0.00 - my $accquery = qq{ -INSERT INTO accountlines ( borrowernumber, accountno, date, amount, description, accounttype, amountoutstanding ) VALUES (?, ?, NOW(), ?, ?, 'Res', ?) - }; - my $dbh = C4::Context->dbh; - my $nextacctno = &getnextacctno( $borrowernumber ); - $dbh->do( $accquery, undef, ( $borrowernumber, $nextacctno, $fee, "Reserve Charge - $title", $fee ) ); + + return if !$fee || $fee == 0; # the last test is needed to include 0.00 + + my $branchcode = C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef; + my $nextacctno = C4::Accounts::getnextacctno($borrowernumber); + + Koha::Account::Line->new( + { + borrowernumber => $borrowernumber, + accountno => $nextacctno, + date => dt_from_string(), + amount => $fee, + description => "Reserve Charge - $title", + accounttype => 'Res', + amountoutstanding => $fee, + branchcode => $branchcode + } + )->store(); } =head2 GetReserveFee @@ -642,68 +634,6 @@ SELECT COUNT(*) FROM reserves WHERE biblionumber=? AND borrowernumber<>? return $fee; } -=head2 GetReservesToBranch - - @transreserv = GetReservesToBranch( $frombranch ); - -Get reserve list for a given branch - -=cut - -sub GetReservesToBranch { - my ( $frombranch ) = @_; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare( - "SELECT reserve_id,borrowernumber,reservedate,itemnumber,timestamp - FROM reserves - WHERE priority='0' - AND branchcode=?" - ); - $sth->execute( $frombranch ); - my @transreserv; - my $i = 0; - while ( my $data = $sth->fetchrow_hashref ) { - $transreserv[$i] = $data; - $i++; - } - return (@transreserv); -} - -=head2 GetReservesForBranch - - @transreserv = GetReservesForBranch($frombranch); - -=cut - -sub GetReservesForBranch { - my ($frombranch) = @_; - my $dbh = C4::Context->dbh; - - my $query = " - SELECT reserve_id,borrowernumber,reservedate,itemnumber,waitingdate - FROM reserves - WHERE priority='0' - 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(); - } - - my @transreserv; - my $i = 0; - while ( my $data = $sth->fetchrow_hashref ) { - $transreserv[$i] = $data; - $i++; - } - return (@transreserv); -} - =head2 GetReserveStatus $reservestatus = GetReserveStatus($itemnumber); @@ -839,14 +769,18 @@ sub CheckReserves { my $priority = 10000000; foreach my $res (@reserves) { if ( $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) { - return ( "Waiting", $res, \@reserves ); # Found it + if ($res->{'found'} eq 'W') { + return ( "Waiting", $res, \@reserves ); # Found it, it is waiting + } else { + return ( "Reserved", $res, \@reserves ); # Found determinated hold, e. g. the tranferred one + } } else { - my $borrowerinfo; + my $patron; my $iteminfo; my $local_hold_match; if ($LocalHoldsPriority) { - $borrowerinfo = C4::Members::GetMember( borrowernumber => $res->{'borrowernumber'} ); + $patron = Koha::Patrons->find( $res->{borrowernumber} ); $iteminfo = C4::Items::GetItem($itemnumber); my $local_holds_priority_item_branchcode = @@ -855,7 +789,7 @@ sub CheckReserves { ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' ) ? $res->{branchcode} : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' ) - ? $borrowerinfo->{branchcode} + ? $patron->branchcode : undef; $local_hold_match = $local_holds_priority_item_branchcode eq @@ -866,11 +800,11 @@ sub CheckReserves { if ( ( $res->{'priority'} && $res->{'priority'} < $priority ) || $local_hold_match ) { $iteminfo ||= C4::Items::GetItem($itemnumber); next if $res->{itemtype} && $res->{itemtype} ne _get_itype( $iteminfo ); - $borrowerinfo ||= C4::Members::GetMember( borrowernumber => $res->{'borrowernumber'} ); - my $branch = GetReservesControlBranch( $iteminfo, $borrowerinfo ); + $patron ||= Koha::Patrons->find( $res->{borrowernumber} ); + my $branch = GetReservesControlBranch( $iteminfo, $patron->unblessed ); my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$iteminfo->{'itype'}); next if ($branchitemrule->{'holdallowed'} == 0); - next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $borrowerinfo->{'branchcode'})); + next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $patron->branchcode)); next if ( ($branchitemrule->{hold_fulfillment_policy} ne 'any') && ($res->{branchcode} ne $iteminfo->{ $branchitemrule->{hold_fulfillment_policy} }) ); $priority = $res->{'priority'}; $highest = $res; @@ -901,25 +835,25 @@ Cancels all reserves with an expiration date from before today. sub CancelExpiredReserves { my $today = dt_from_string(); my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays'); + my $expireWaiting = C4::Context->preference('ExpireReservesMaxPickUpDelay'); - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare( " - SELECT * FROM reserves WHERE DATE(expirationdate) < DATE( CURDATE() ) - AND expirationdate IS NOT NULL - " ); - $sth->execute(); + my $dtf = Koha::Database->new->schema->storage->datetime_parser; + my $params = { expirationdate => { '<', $dtf->format_date($today) } }; + $params->{found} = undef unless $expireWaiting; + + # FIXME To move to Koha::Holds->search_expired (?) + my $holds = Koha::Holds->search( $params ); - while ( my $res = $sth->fetchrow_hashref() ) { - my $calendar = Koha::Calendar->new( branchcode => $res->{'branchcode'} ); - my $cancel_params = { reserve_id => $res->{'reserve_id'} }; + while ( my $hold = $holds->next ) { + my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode ); next if !$cancel_on_holidays && $calendar->is_holiday( $today ); - if ( $res->{found} eq 'W' ) { + my $cancel_params = {}; + if ( $hold->found eq 'W' ) { $cancel_params->{charge_cancel_fee} = 1; } - - CancelReserve($cancel_params); + $hold->cancel( $cancel_params ); } } @@ -934,75 +868,11 @@ Unsuspends all suspended reserves with a suspend_until date from before today. sub AutoUnsuspendReserves { my $today = dt_from_string(); - my @holds = Koha::Holds->search( { suspend_until => { '<' => $today->ymd() } } ); + my @holds = Koha::Holds->search( { suspend_until => { '<=' => $today->ymd() } } ); map { $_->suspend(0)->suspend_until(undef)->store() } @holds; } -=head2 CancelReserve - - CancelReserve({ reserve_id => $reserve_id, [ biblionumber => $biblionumber, borrowernumber => $borrrowernumber, itemnumber => $itemnumber, ] [ charge_cancel_fee => 1 ] }); - -Cancels a reserve. If C is passed and the C syspref is set, charge that fee to the patron's account. - -=cut - -sub CancelReserve { - my ( $params ) = @_; - - my $reserve_id = $params->{'reserve_id'}; - # Filter out only the desired keys; this will insert undefined values for elements missing in - # \%params, but GetReserveId filters them out anyway. - $reserve_id = GetReserveId( { biblionumber => $params->{'biblionumber'}, borrowernumber => $params->{'borrowernumber'}, itemnumber => $params->{'itemnumber'} } ) unless ( $reserve_id ); - - return unless ( $reserve_id ); - - my $dbh = C4::Context->dbh; - - my $reserve = GetReserve( $reserve_id ); - if ($reserve) { - - my $hold = Koha::Holds->find( $reserve_id ); - logaction( 'HOLDS', 'CANCEL', $hold->reserve_id, Dumper($hold->unblessed) ) - if C4::Context->preference('HoldsLog'); - - my $query = " - UPDATE reserves - SET cancellationdate = now(), - priority = 0 - WHERE reserve_id = ? - "; - my $sth = $dbh->prepare($query); - $sth->execute( $reserve_id ); - - $query = " - INSERT INTO old_reserves - SELECT * FROM reserves - WHERE reserve_id = ? - "; - $sth = $dbh->prepare($query); - $sth->execute( $reserve_id ); - - $query = " - DELETE FROM reserves - WHERE reserve_id = ? - "; - $sth = $dbh->prepare($query); - $sth->execute( $reserve_id ); - - # now fix the priority on the others.... - _FixPriority({ biblionumber => $reserve->{biblionumber} }); - - # and, if desired, charge a cancel fee - my $charge = C4::Context->preference("ExpireReservesMaxPickUpDelayCharge"); - if ( $charge && $params->{'charge_cancel_fee'} ) { - manualinvoice($reserve->{'borrowernumber'}, $reserve->{'itemnumber'}, '', 'HE', $charge); - } - } - - return $reserve; -} - =head2 ModReserve ModReserve({ rank => $rank, @@ -1054,13 +924,21 @@ sub ModReserve { 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 $hold; + unless ( $reserve_id ) { + my $holds = Koha::Holds->search({ biblionumber => $biblionumber, borrowernumber => $borrowernumber, itemnumber => $itemnumber }); + return unless $holds->count; # FIXME Should raise an exception + $hold = $holds->next; + $reserve_id = $hold->reserve_id; + } + + $hold ||= Koha::Holds->find($reserve_id); if ( $rank eq "del" ) { - CancelReserve({ reserve_id => $reserve_id }); + $hold->cancel; } elsif ($rank =~ /^\d+/ and $rank > 0) { - my $hold = Koha::Holds->find($reserve_id); logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, Dumper($hold->unblessed) ) if C4::Context->preference('HoldsLog'); @@ -1118,6 +996,7 @@ sub ModReserveFill { } ); + # FIXME Must call Koha::Hold->cancel ? => No, should call ->filled and add the correct log Koha::Old::Hold->new( $hold->unblessed() )->store(); $hold->delete(); @@ -1230,7 +1109,9 @@ sub ModReserveCancelAll { my ( $itemnumber, $borrowernumber ) = @_; #step 1 : cancel the reservation - my $CancelReserve = CancelReserve({ itemnumber => $itemnumber, borrowernumber => $borrowernumber }); + my $holds = Koha::Holds->search({ itemnumber => $itemnumber, borrowernumber => $borrowernumber }); + return unless $holds->count; + $holds->next->cancel; #step 2 launch the subroutine of the others reserves ( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber); @@ -1262,59 +1143,6 @@ sub ModReserveMinusPriority { _FixPriority({ reserve_id => $reserve_id, rank => '0' }); } -=head2 GetReserveInfo - - &GetReserveInfo($reserve_id); - -Get item and borrower details for a current hold. -Current implementation this query should have a single result. - -=cut - -sub GetReserveInfo { - my ( $reserve_id ) = @_; - my $dbh = C4::Context->dbh; - 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 my $is_available = IsAvailableForItemLevelRequest($item_record,$borrower_record); @@ -1325,6 +1153,7 @@ item-level hold request. An item is available if * it is not lost AND * it is not damaged AND * it is not withdrawn AND +* a waiting or in transit reserve is placed on * does not have a not for loan value > 0 Need to check the issuingrules onshelfholds column, @@ -1347,10 +1176,12 @@ sub IsAvailableForItemLevelRequest { # FIXME - a lot of places in the code do this # or something similar - need to be # consolidated - my $itype = _get_itype($item); + my $patron = Koha::Patrons->find( $borrower->{borrowernumber} ); + my $item_object = Koha::Items->find( $item->{itemnumber } ); + my $itemtype = $item_object->effective_itemtype; my $notforloan_per_itemtype = $dbh->selectrow_array("SELECT notforloan FROM itemtypes WHERE itemtype = ?", - undef, $itype); + undef, $itemtype); return 0 if $notforloan_per_itemtype || @@ -1359,7 +1190,7 @@ sub IsAvailableForItemLevelRequest { $item->{withdrawn} || ($item->{damaged} && !C4::Context->preference('AllowHoldsOnDamagedItems')); - my $on_shelf_holds = _OnShelfHoldsAllowed($itype,$borrower->{categorycode},$item->{holdingbranch}); + my $on_shelf_holds = Koha::IssuingRules->get_onshelfholds_policy( { item => $item_object, patron => $patron } ); if ( $on_shelf_holds == 1 ) { return 1; @@ -1370,6 +1201,10 @@ sub IsAvailableForItemLevelRequest { my $any_available = 0; foreach my $i (@items) { + + my $circ_control_branch = C4::Circulation::_GetCircControlBranch( $i->unblessed(), $borrower ); + my $branchitemrule = C4::Circulation::GetBranchItemRule( $circ_control_branch, $i->itype ); + $any_available = 1 unless $i->itemlost || $i->notforloan > 0 @@ -1378,29 +1213,14 @@ sub IsAvailableForItemLevelRequest { || IsItemOnHoldAndFound( $i->id ) || ( $i->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') ) - || Koha::ItemTypes->find( $i->effective_itemtype() )->notforloan; + || Koha::ItemTypes->find( $i->effective_itemtype() )->notforloan + || $branchitemrule->{holdallowed} == 1 && $borrower->{branchcode} ne $i->homebranch; } return $any_available ? 0 : 1; + } else { # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved) + return $item->{onloan} || IsItemOnHoldAndFound( $item->{itemnumber} ); } - - return $item->{onloan} || GetReserveStatus($item->{itemnumber}) eq "Waiting"; -} - -=head2 OnShelfHoldsAllowed - - OnShelfHoldsAllowed($itemtype,$borrowercategory,$branchcode); - -Checks issuingrules, using the borrowers categorycode, the itemtype, and branchcode to see if onshelf -holds are allowed, returns true if so. - -=cut - -sub OnShelfHoldsAllowed { - my ($item, $borrower) = @_; - - my $itype = _get_itype($item); - return _OnShelfHoldsAllowed($itype,$borrower->{categorycode},$item->{holdingbranch}); } sub _get_itype { @@ -1430,16 +1250,9 @@ sub _get_itype { return $itype; } -sub _OnShelfHoldsAllowed { - my ($itype,$borrowercategory,$branchcode) = @_; - - my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $borrowercategory, itemtype => $itype, branchcode => $branchcode }); - return $issuing_rule ? $issuing_rule->onshelfholds : undef; -} - =head2 AlterPriority - AlterPriority( $where, $reserve_id ); + AlterPriority( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority ); 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 @@ -1447,30 +1260,29 @@ Input: $where is 'up', 'down', 'top' or 'bottom'. Biblionumber, Date reserve was =cut sub AlterPriority { - my ( $where, $reserve_id ) = @_; + my ( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority ) = @_; - my $reserve = GetReserve( $reserve_id ); + my $hold = Koha::Holds->find( $reserve_id ); + return unless $hold; - if ( $reserve->{cancellationdate} ) { - warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (".$reserve->{cancellationdate}.')'; + if ( $hold->cancellationdate ) { + warn "I cannot alter the priority for reserve_id $reserve_id, the reserve has been cancelled (" . $hold->cancellationdate . ')'; return; } - if ( $where eq 'up' || $where eq 'down' ) { - - my $priority = $reserve->{'priority'}; - $priority = $where eq 'up' ? $priority - 1 : $priority + 1; - _FixPriority({ reserve_id => $reserve_id, rank => $priority }) - + if ( $where eq 'up' ) { + return unless $prev_priority; + _FixPriority({ reserve_id => $reserve_id, rank => $prev_priority }) + } elsif ( $where eq 'down' ) { + return unless $next_priority; + _FixPriority({ reserve_id => $reserve_id, rank => $next_priority }) } elsif ( $where eq 'top' ) { - - _FixPriority({ reserve_id => $reserve_id, rank => '1' }) - + _FixPriority({ reserve_id => $reserve_id, rank => $first_priority }) } elsif ( $where eq 'bottom' ) { - - _FixPriority({ reserve_id => $reserve_id, rank => '999999' }); - + _FixPriority({ reserve_id => $reserve_id, rank => $last_priority }); } + + # FIXME Should return the new priority } =head2 ToggleLowestPriority @@ -1605,13 +1417,18 @@ sub _FixPriority { my $dbh = C4::Context->dbh; - unless ( $biblionumber ) { - my $res = GetReserve( $reserve_id ); - $biblionumber = $res->{biblionumber}; + my $hold; + if ( $reserve_id ) { + $hold = Koha::Holds->find( $reserve_id ); + return unless $hold; } - if ( $rank eq "del" ) { - CancelReserve({ reserve_id => $reserve_id }); + unless ( $biblionumber ) { # FIXME This is a very weird API + $biblionumber = $hold->biblionumber; + } + + if ( $rank eq "del" ) { # FIXME will crash if called without $hold + $hold->cancel; } elsif ( $rank eq "W" || $rank eq "0" ) { @@ -1841,10 +1658,10 @@ sub _koha_notify_reserve { my $hold = Koha::Holds->find($reserve_id); my $borrowernumber = $hold->borrowernumber; - my $borrower = C4::Members::GetMember(borrowernumber => $borrowernumber); + my $patron = Koha::Patrons->find( $borrowernumber ); # Try to get the borrower's email address - my $to_address = C4::Members::GetNoticeEmailAddress($borrowernumber); + my $to_address = $patron->notice_email_address; my $messagingprefs = C4::Members::Messaging::GetMessagingPreferences( { borrowernumber => $borrowernumber, @@ -1858,15 +1675,15 @@ sub _koha_notify_reserve { my %letter_params = ( module => 'reserves', branchcode => $hold->branchcode, + lang => $patron->lang, tables => { 'branches' => $library, - 'borrowers' => $borrower, + 'borrowers' => $patron->unblessed, 'biblio' => $hold->biblionumber, 'biblioitems' => $hold->biblionumber, 'reserves' => $hold->unblessed, 'items' => $hold->itemnumber, }, - substitute => { today => output_pref( { dt => dt_from_string, dateonly => 1 } ) }, ); my $notification_sent = 0; #Keeping track if a Hold_filled message is sent. If no message can be sent, then default to a print message. @@ -1892,7 +1709,7 @@ sub _koha_notify_reserve { while ( my ( $mtt, $letter_code ) = each %{ $messagingprefs->{transports} } ) { next if ( ( $mtt eq 'email' and not $to_address ) # No email address - or ( $mtt eq 'sms' and not $borrower->{smsalertnumber} ) # No SMS number + or ( $mtt eq 'sms' and not $patron->smsalertnumber ) # No SMS number or ( $mtt eq 'phone' and C4::Context->preference('TalkingTechItivaPhoneNotification') ) # Notice is handled by TalkingTech_itiva_outbound.pl ); @@ -1955,54 +1772,6 @@ sub _ShiftPriorityByDateAndPriority { return $new_priority; # so the caller knows what priority they wind up receiving } -=head2 OPACItemHoldsAllowed - - OPACItemHoldsAllowed($item_record,$borrower_record); - -Checks issuingrules, using the borrowers categorycode, the itemtype, and branchcode to see -if specific item holds are allowed, returns true if so. - -=cut - -sub OPACItemHoldsAllowed { - my ($item,$borrower) = @_; - - my $branchcode = $item->{homebranch} or die "No homebranch"; - my $itype; - my $dbh = C4::Context->dbh; - if (C4::Context->preference('item-level_itypes')) { - # We can't trust GetItem to honour the syspref, so safest to do it ourselves - # When GetItem is fixed, we can remove this - $itype = $item->{itype}; - } - else { - my $query = "SELECT itemtype FROM biblioitems WHERE biblioitemnumber = ? "; - my $sth = $dbh->prepare($query); - $sth->execute($item->{biblioitemnumber}); - if (my $data = $sth->fetchrow_hashref()){ - $itype = $data->{itemtype}; - } - } - - my $query = "SELECT opacitemholds,categorycode,itemtype,branchcode FROM issuingrules WHERE - (issuingrules.categorycode = ? OR issuingrules.categorycode = '*') - AND - (issuingrules.itemtype = ? OR issuingrules.itemtype = '*') - AND - (issuingrules.branchcode = ? OR issuingrules.branchcode = '*') - ORDER BY - issuingrules.categorycode desc, - issuingrules.itemtype desc, - issuingrules.branchcode desc - LIMIT 1"; - my $sth = $dbh->prepare($query); - $sth->execute($borrower->{categorycode},$itype,$branchcode); - my $data = $sth->fetchrow_hashref; - my $opacitemholds = uc substr ($data->{opacitemholds}, 0, 1); - return '' if $opacitemholds eq 'N'; - return $opacitemholds; -} - =head2 MoveReserve MoveReserve( $itemnumber, $borrowernumber, $cancelreserve ) @@ -2047,7 +1816,8 @@ sub MoveReserve { RevertWaitingStatus({ itemnumber => $itemnumber }); } elsif ( $cancelreserve eq 'cancel' || $cancelreserve ) { # cancel reserves on this item - CancelReserve( { reserve_id => $res->{'reserve_id'} } ); + my $hold = Koha::Holds->find( $res->{reserve_id} ); + $hold->cancel; } } } @@ -2157,33 +1927,17 @@ sub RevertWaitingStatus { _FixPriority( { biblionumber => $reserve->{biblionumber} } ); } -=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'} ); - - foreach my $key ( keys %$params ) { - delete $params->{$key} unless defined( $params->{$key} ); - } - - my $hold = Koha::Holds->search( $params )->next(); - - return unless $hold; - - return $hold->id(); -} - =head2 ReserveSlip - ReserveSlip($branchcode, $borrowernumber, $biblionumber) +ReserveSlip( + { + branchcode => $branchcode, + borrowernumber => $borrowernumber, + biblionumber => $biblionumber, + [ itemnumber => $itemnumber, ] + [ barcode => $barcode, ] + } + ) Returns letter hash ( see C4::Letters::GetPreparedLetter ) or undef @@ -2200,20 +1954,45 @@ available within the slip: =cut sub ReserveSlip { - my ($branch, $borrowernumber, $biblionumber) = @_; + my ($args) = @_; + my $branchcode = $args->{branchcode}; + my $borrowernumber = $args->{borrowernumber}; + my $biblionumber = $args->{biblionumber}; + my $itemnumber = $args->{itemnumber}; + my $barcode = $args->{barcode}; + + + my $patron = Koha::Patrons->find($borrowernumber); -# return unless ( C4::Context->boolean_preference('printreserveslips') ); + my $hold; + if ($itemnumber || $barcode ) { + $itemnumber ||= Koha::Items->find( { barcode => $barcode } )->itemnumber; + + $hold = Koha::Holds->search( + { + biblionumber => $biblionumber, + borrowernumber => $borrowernumber, + itemnumber => $itemnumber + } + )->next; + } + else { + $hold = Koha::Holds->search( + { + biblionumber => $biblionumber, + borrowernumber => $borrowernumber + } + )->next; + } - my $reserve_id = GetReserveId({ - biblionumber => $biblionumber, - borrowernumber => $borrowernumber - }) or return; - my $reserve = GetReserveInfo($reserve_id) or return; + return unless $hold; + my $reserve = $hold->unblessed; return C4::Letters::GetPreparedLetter ( module => 'circulation', letter_code => 'HOLD_SLIP', - branchcode => $branch, + branchcode => $branchcode, + lang => $patron->lang, tables => { 'reserves' => $reserve, 'branches' => $reserve->{branchcode}, @@ -2374,7 +2153,7 @@ sub GetHoldRule { my $sth = $dbh->prepare( q{ - SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record + SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record, holds_per_day FROM issuingrules WHERE (categorycode in (?,'*') ) AND (itemtype IN (?,'*'))