use Modern::Perl;
use C4::Accounts;
-use C4::Biblio;
-use C4::Circulation;
+use C4::Biblio qw( GetMarcFromKohaField );
+use C4::Circulation qw( CheckIfIssuedToPatron GetAgeRestriction GetBranchItemRule );
use C4::Context;
-use C4::Items;
+use C4::Items qw( CartToShelf get_hostitemnumbers_of );
use C4::Letters;
-use C4::Log;
+use C4::Log qw( logaction );
use C4::Members::Messaging;
use C4::Members;
use Koha::Account::Lines;
+use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
use Koha::Biblios;
use Koha::Calendar;
use Koha::CirculationRules;
use Koha::Database;
-use Koha::DateUtils;
-use Koha::Hold;
+use Koha::DateUtils qw( dt_from_string output_pref );
use Koha::Holds;
use Koha::ItemTypes;
use Koha::Items;
use Koha::Libraries;
-use Koha::Old::Hold;
+use Koha::Old::Holds;
use Koha::Patrons;
use Koha::Plugins;
-use Carp;
-use Data::Dumper;
-use List::MoreUtils qw( firstidx any );
-
-use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+use List::MoreUtils qw( any );
=head1 NAME
The following columns contains important values :
- priority >0 : then the reserve is at 1st stage, and not yet affected to any item.
=0 : then the reserve is being dealed
- - found : NULL : means the patron requested the 1st available, and we haven't chosen the item
- T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
- W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
- F(inished) : the reserve has been completed, and is done
+ - found : NULL : means the patron requested the 1st available, and we haven't chosen the item
+ T(ransit) : the reserve is linked to an item but is in transit to the pickup branch
+ W(aiting) : the reserve is linked to an item, is at the pickup branch, and is waiting on the hold shelf
+ F(inished) : the reserve has been completed, and is done
+ P(rocessing) : reserved item has been returned using self-check machine and reserve needs to be confirmed
+ by librarian before notice is send and status changed to waiting.
+ Applicable only if HoldsNeedProcessingSIP system preference is set.
- itemnumber : empty : the reserve is still unaffected to an item
filled: the reserve is attached to an item
The complete workflow is :
=cut
+our (@ISA, @EXPORT_OK);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
- @EXPORT = qw(
- &AddReserve
+ @EXPORT_OK = qw(
+ AddReserve
+
+ GetReserveStatus
+
+ GetOtherReserves
+ ChargeReserveFee
+ GetReserveFee
- &GetReserveStatus
+ ModReserveAffect
+ ModReserve
+ ModReserveStatus
+ ModReserveCancelAll
+ ModReserveMinusPriority
+ MoveReserve
- &GetOtherReserves
+ CheckReserves
+ CanBookBeReserved
+ CanItemBeReserved
+ CanReserveBeCanceledFromOpac
+ CancelExpiredReserves
- &ModReserveFill
- &ModReserveAffect
- &ModReserve
- &ModReserveStatus
- &ModReserveCancelAll
- &ModReserveMinusPriority
- &MoveReserve
+ AutoUnsuspendReserves
- &CheckReserves
- &CanBookBeReserved
- &CanItemBeReserved
- &CanReserveBeCanceledFromOpac
- &CancelExpiredReserves
+ IsAvailableForItemLevelRequest
+ ItemsAnyAvailableAndNotRestricted
- &AutoUnsuspendReserves
+ AlterPriority
+ ToggleLowestPriority
- &IsAvailableForItemLevelRequest
- ItemsAnyAvailableAndNotRestricted
+ ReserveSlip
+ ToggleSuspend
+ SuspendAll
- &AlterPriority
- &ToggleLowestPriority
+ GetReservesControlBranch
- &ReserveSlip
- &ToggleSuspend
- &SuspendAll
+ CalculatePriority
- &GetReservesControlBranch
+ IsItemOnHoldAndFound
- IsItemOnHoldAndFound
+ GetMaxPatronHoldsForRecord
- GetMaxPatronHoldsForRecord
+ MergeHolds
+
+ RevertWaitingStatus
);
- @EXPORT_OK = qw( MergeHolds );
}
=head2 AddReserve
my $biblionumber = $params->{biblionumber};
my $priority = $params->{priority};
my $resdate = $params->{reservation_date};
- my $expdate = $params->{expiration_date};
+ my $patron_expiration_date = $params->{expiration_date};
my $notes = $params->{notes};
my $title = $params->{title};
my $checkitem = $params->{itemnumber};
my $found = $params->{found};
my $itemtype = $params->{itemtype};
+ my $non_priority = $params->{non_priority};
- $resdate = output_pref( { str => dt_from_string( $resdate ), dateonly => 1, dateformat => 'iso' })
- or output_pref({ dt => dt_from_string, dateonly => 1, dateformat => 'iso' });
-
- $expdate = output_pref({ str => $expdate, dateonly => 1, dateformat => 'iso' });
+ $resdate ||= dt_from_string;
# 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 .
$found = 'W';
}
}
-
- if ( C4::Context->preference('AllowHoldDateInFuture') ) {
-
- # Make room in reserves for this before those of a later reserve date
- $priority = _ShiftPriorityByDateAndPriority( $biblionumber, $resdate, $priority );
+ if ( C4::Context->preference( 'AllowHoldDateInFuture' ) ) {
+ # Make room in reserves for this if passed a priority
+ $priority = _ShiftPriority( $biblionumber, $priority );
}
my $waitingdate;
itemnumber => $checkitem,
found => $found,
waitingdate => $waitingdate,
- expirationdate => $expdate,
+ patron_expiration_date => $patron_expiration_date,
itemtype => $itemtype,
item_level_hold => $checkitem ? 1 : 0,
+ non_priority => $non_priority ? 1 : 0,
}
)->store();
$hold->set_waiting() if $found && $found eq 'W';
- logaction( 'HOLDS', 'CREATE', $hold->id, Dumper($hold->unblessed) )
+ logaction( 'HOLDS', 'CREATE', $hold->id, $hold )
if C4::Context->preference('HoldsLog');
my $reserve_id = $hold->id();
}
Koha::Plugins->call('after_hold_create', $hold);
+ Koha::Plugins->call(
+ 'after_hold_action',
+ {
+ action => 'place',
+ payload => { hold => $hold->get_from_storage }
+ }
+ );
+
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $biblionumber ]
+ }
+ ) if C4::Context->preference('RealTimeHoldsQueue');
return $reserve_id;
}
sub CanBookBeReserved{
my ($borrowernumber, $biblionumber, $pickup_branchcode, $params) = @_;
- my @itemnumbers = Koha::Items->search({ biblionumber => $biblionumber})->get_column("itemnumber");
+ # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
+ if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
+ && C4::Circulation::CheckIfIssuedToPatron( $borrowernumber, $biblionumber ) ) {
+ return { status =>'alreadypossession' };
+ }
+
+ if ( $params->{itemtype} ) {
+
+ # biblio-level, item type-contrained
+ my $patron = Koha::Patrons->find($borrowernumber);
+ my $reservesallowed = Koha::CirculationRules->get_effective_rule(
+ {
+ itemtype => $params->{itemtype},
+ categorycode => $patron->categorycode,
+ branchcode => $pickup_branchcode,
+ rule_name => 'reservesallowed',
+ }
+ )->rule_value;
+
+ $reservesallowed = ( $reservesallowed eq '' ) ? undef : $reservesallowed;
+
+ my $count = $patron->holds->search(
+ {
+ '-or' => [
+ { 'me.itemtype' => $params->{itemtype} },
+ { 'item.itype' => $params->{itemtype} }
+ ]
+ },
+ {
+ join => ['item']
+ }
+ )->count;
+
+ return { status => '' }
+ if defined $reservesallowed and $reservesallowed < $count + 1;
+ }
+
+ my $items;
#get items linked via host records
- my @hostitems = get_hostitemnumbers_of($biblionumber);
- if (@hostitems){
- push (@itemnumbers, @hostitems);
+ my @hostitemnumbers = get_hostitemnumbers_of($biblionumber);
+ if (@hostitemnumbers){
+ $items = Koha::Items->search({
+ -or => [
+ biblionumber => $biblionumber,
+ itemnumber => { -in => @hostitemnumbers }
+ ]
+ });
+ } else {
+ $items = Koha::Items->search({ biblionumber => $biblionumber});
}
my $canReserve = { status => '' };
- foreach my $itemnumber (@itemnumbers) {
- $canReserve = CanItemBeReserved( $borrowernumber, $itemnumber, $pickup_branchcode, $params );
+ my $patron = Koha::Patrons->find( $borrowernumber );
+ while ( my $item = $items->next ) {
+ $canReserve = CanItemBeReserved( $patron, $item, $pickup_branchcode, $params );
return { status => 'OK' } if $canReserve->{status} eq 'OK';
}
return $canReserve;
=head2 CanItemBeReserved
- $canReserve = &CanItemBeReserved($borrowernumber, $itemnumber, $branchcode, $params)
+ $canReserve = &CanItemBeReserved($patron, $item, $branchcode, $params)
if ($canReserve->{status} eq 'OK') { #We can reserve this Item! }
- current params are 'ignore_found_holds' - if true holds that have been trapped are not counted
+ current params are:
+ 'ignore_found_holds' - if true holds that have been trapped are not counted
toward the patron limit, used by checkHighHolds to avoid counting the hold we will fill with the
current checkout against the high holds threshold
+ 'ignore_hold_counts' - we use this routine to check if an item can fill a hold - on this case we
+ should not check if there are too many holds as we only csre about reservability
@RETURNS { status => OK }, if the Item can be reserved.
{ status => ageRestricted }, if the Item is age restricted for this borrower.
{ 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
{ status => pickupNotInHoldGroup }, pickup location is not in hold group, and pickup locations are only allowed from hold groups.
+ { status => recall }, if the borrower has already placed a recall on this item
=cut
sub CanItemBeReserved {
- my ( $borrowernumber, $itemnumber, $pickup_branchcode, $params ) = @_;
+ my ( $patron, $item, $pickup_branchcode, $params ) = @_;
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
+ my $allowedreserves = 0; # Total number of holds allowed across all records, default to none
+
+ # We check item branch if IndependentBranches is ON
+ # and canreservefromotherbranches is OFF
+ if ( C4::Context->preference('IndependentBranches')
+ and !C4::Context->preference('canreservefromotherbranches') )
+ {
+ if ( $item->homebranch ne $patron->branchcode ) {
+ return { status => 'cannotReserveFromOtherBranches' };
+ }
+ }
# we retrieve borrowers and items informations #
# item->{itype} will come for biblioitems if necessery
- my $item = Koha::Items->find($itemnumber);
- my $biblio = $item->biblio;
- 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
if ( $item->damaged
&& !C4::Context->preference('AllowHoldsOnDamagedItems') );
- # Check for the age restriction
- my ( $ageRestriction, $daysToAgeRestriction ) =
- C4::Circulation::GetAgeRestriction( $biblio->biblioitem->agerestriction, $borrower );
- return { status => 'ageRestricted' } if $daysToAgeRestriction && $daysToAgeRestriction > 0;
+ if( GetMarcFromKohaField('biblioitems.agerestriction') ){
+ my $biblio = $item->biblio;
+ # Check for the age restriction
+ my ( $ageRestriction, $daysToAgeRestriction ) =
+ 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 { status =>'itemAlreadyOnHold' }
- if Koha::Holds->search( { borrowernumber => $borrowernumber, itemnumber => $itemnumber } )->count();
+ if ( !$params->{ignore_hold_counts} && Koha::Holds->search( { borrowernumber => $patron->borrowernumber, itemnumber => $item->itemnumber } )->count() );
- my $controlbranch = C4::Context->preference('ReservesControlBranch');
+ # Check that patron have not checked out this biblio (if AllowHoldsOnPatronsPossessions set)
+ if ( !C4::Context->preference('AllowHoldsOnPatronsPossessions')
+ && C4::Circulation::CheckIfIssuedToPatron( $patron->borrowernumber, $item->biblionumber ) ) {
+ return { status =>'alreadypossession' };
+ }
- my $querycount = q{
- 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 = ?
- };
+ # check if a recall exists on this item from this borrower
+ return { status => 'recall' }
+ if $patron->recalls->filter_by_current->search({ item_id => $item->itemnumber })->count;
+
+ my $controlbranch = C4::Context->preference('ReservesControlBranch');
- my $branchcode = "";
+ my $reserves_control_branch;
my $branchfield = "reserves.branchcode";
if ( $controlbranch eq "ItemHomeLibrary" ) {
$branchfield = "items.homebranch";
- $branchcode = $item->homebranch;
+ $reserves_control_branch = $item->homebranch;
}
elsif ( $controlbranch eq "PatronLibrary" ) {
$branchfield = "borrowers.branchcode";
- $branchcode = $borrower->{branchcode};
+ $reserves_control_branch = $borrower->{branchcode};
}
# we retrieve rights
- if ( my $rights = GetHoldRule( $borrower->{'categorycode'}, $item->effective_itemtype, $branchcode ) ) {
- $ruleitemtype = $rights->{itemtype};
- $allowedreserves = $rights->{reservesallowed} // $allowedreserves;
- $holds_per_record = $rights->{holds_per_record} // $holds_per_record;
- $holds_per_day = $rights->{holds_per_day};
+ if (
+ my $reservesallowed = Koha::CirculationRules->get_effective_rule({
+ itemtype => $item->effective_itemtype,
+ categorycode => $borrower->{categorycode},
+ branchcode => $reserves_control_branch,
+ rule_name => 'reservesallowed',
+ })
+ ) {
+ $ruleitemtype = $reservesallowed->itemtype;
+ $allowedreserves = $reservesallowed->rule_value // 0; #undefined is 0, blank is unlimited
}
else {
$ruleitemtype = undef;
}
- my $search_params = {
- borrowernumber => $borrowernumber,
- biblionumber => $item->biblionumber,
- };
- $search_params->{found} = undef if $params->{ignore_found_holds};
+ my $rights = Koha::CirculationRules->get_effective_rules({
+ categorycode => $borrower->{'categorycode'},
+ itemtype => $item->effective_itemtype,
+ branchcode => $reserves_control_branch,
+ rules => ['holds_per_record','holds_per_day']
+ });
+ my $holds_per_record = $rights->{holds_per_record} // 1;
+ my $holds_per_day = $rights->{holds_per_day};
- my $holds = Koha::Holds->search($search_params);
- if ( defined $holds_per_record && $holds_per_record ne ''
- && $holds->count() >= $holds_per_record ) {
- return { status => "tooManyHoldsForThisRecord", limit => $holds_per_record };
+ if ( defined $holds_per_record && $holds_per_record ne '' ){
+ if ( $holds_per_record == 0 ) {
+ return { status => "noReservesAllowed" };
+ }
+ if ( !$params->{ignore_hold_counts} ) {
+ my $search_params = {
+ borrowernumber => $patron->borrowernumber,
+ biblionumber => $item->biblionumber,
+ };
+ $search_params->{found} = undef if $params->{ignore_found_holds};
+ my $holds = Koha::Holds->search($search_params);
+ return { status => "tooManyHoldsForThisRecord", limit => $holds_per_record } if $holds->count() >= $holds_per_record;
+ }
}
- my $today_holds = Koha::Holds->search({
- borrowernumber => $borrowernumber,
- reservedate => dt_from_string->date
- });
-
- if ( defined $holds_per_day && $holds_per_day ne ''
- && $today_holds->count() >= $holds_per_day )
+ if (!$params->{ignore_hold_counts} && defined $holds_per_day && $holds_per_day ne '')
{
- return { status => 'tooManyReservesToday', limit => $holds_per_day };
+ my $today_holds = Koha::Holds->search({
+ borrowernumber => $patron->borrowernumber,
+ reservedate => dt_from_string->date
+ });
+ return { status => 'tooManyReservesToday', limit => $holds_per_day } if $today_holds->count() >= $holds_per_day;
}
- # we retrieve count
-
- $querycount .= "AND ( $branchfield = ? OR $branchfield IS NULL )";
-
- # If using item-level itypes, fall back to the record
- # level itemtype if the hold has no associated item
- $querycount .=
- C4::Context->preference('item-level_itypes')
- ? " AND COALESCE( items.itype, biblioitems.itemtype ) = ?"
- : " AND biblioitems.itemtype = ?"
- if defined $ruleitemtype;
+ # we check if it's ok or not
+ if ( defined $allowedreserves && $allowedreserves ne '' ){
+ if( $allowedreserves == 0 ){
+ return { status => 'noReservesAllowed' };
+ }
+ if ( !$params->{ignore_hold_counts} ) {
+ # we retrieve count
+ my $querycount = q{
+ 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 = ?
+ };
+ $querycount .= "AND ( $branchfield = ? OR $branchfield IS NULL )";
+
+ # If using item-level itypes, fall back to the record
+ # level itemtype if the hold has no associated item
+ if ( defined $ruleitemtype ) {
+ if ( C4::Context->preference('item-level_itypes') ) {
+ $querycount .= q{
+ AND ( COALESCE( items.itype, biblioitems.itemtype ) = ?
+ OR reserves.itemtype = ? )
+ };
+ }
+ else {
+ $querycount .= q{
+ AND ( biblioitems.itemtype = ?
+ OR reserves.itemtype = ? )
+ };
+ }
+ }
- my $sthcount = $dbh->prepare($querycount);
+ my $sthcount = $dbh->prepare($querycount);
- if ( defined $ruleitemtype ) {
- $sthcount->execute( $borrowernumber, $branchcode, $ruleitemtype );
- }
- else {
- $sthcount->execute( $borrowernumber, $branchcode );
- }
+ if ( defined $ruleitemtype ) {
+ $sthcount->execute( $patron->borrowernumber, $reserves_control_branch, $ruleitemtype, $ruleitemtype );
+ }
+ else {
+ $sthcount->execute( $patron->borrowernumber, $reserves_control_branch );
+ }
- my $reservecount = "0";
- if ( my $rowcount = $sthcount->fetchrow_hashref() ) {
- $reservecount = $rowcount->{count};
- }
+ my $reservecount = "0";
+ if ( my $rowcount = $sthcount->fetchrow_hashref() ) {
+ $reservecount = $rowcount->{count};
+ }
- # we check if it's ok or not
- if ( defined $allowedreserves && $allowedreserves ne ''
- && $reservecount >= $allowedreserves ) {
- return { status => 'tooManyReserves', limit => $allowedreserves };
+ return { status => 'tooManyReserves', limit => $allowedreserves } if $reservecount >= $allowedreserves;
+ }
}
# Now we need to check hold limits by patron category
my $rule = Koha::CirculationRules->get_effective_rule(
{
- categorycode => $borrower->{categorycode},
- branchcode => $branchcode,
+ categorycode => $patron->categorycode,
+ branchcode => $reserves_control_branch,
rule_name => 'max_holds',
}
);
- if ( $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
+ if (!$params->{ignore_hold_counts} && $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
my $total_holds_count = Koha::Holds->search(
{
- borrowernumber => $borrower->{borrowernumber}
+ borrowernumber => $patron->borrowernumber
}
)->count();
return { status => 'tooManyReserves', limit => $rule->rule_value} if $total_holds_count >= $rule->rule_value;
}
- my $reserves_control_branch =
- GetReservesControlBranch( $item->unblessed(), $borrower );
my $branchitemrule =
- C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype ); # FIXME Should not be item->effective_itemtype?
+ C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->effective_itemtype );
- if ( $branchitemrule->{holdallowed} == 0 ) {
+ if ( $branchitemrule->{holdallowed} eq 'not_allowed' ) {
return { status => 'notReservable' };
}
- if ( $branchitemrule->{holdallowed} == 1
+ if ( $branchitemrule->{holdallowed} eq 'from_home_library'
&& $borrower->{branchcode} ne $item->homebranch )
{
return { status => 'cannotReserveFromOtherBranches' };
}
my $item_library = Koha::Libraries->find( {branchcode => $item->homebranch} );
- if ( $branchitemrule->{holdallowed} == 3) {
- if($borrower->{branchcode} ne $item->homebranch && !$item_library->validate_hold_sibling( {branchcode => $borrower->{branchcode}} )) {
+ if ( $branchitemrule->{holdallowed} eq 'from_local_hold_group') {
+ if($patron->branchcode ne $item->homebranch && !$item_library->validate_hold_sibling( {branchcode => $patron->branchcode} )) {
return { status => 'branchNotInHoldGroup' };
}
}
- # If reservecount is ok, we check item branch if IndependentBranches is ON
- # and canreservefromotherbranches is OFF
- if ( C4::Context->preference('IndependentBranches')
- and !C4::Context->preference('canreservefromotherbranches') )
- {
- if ( $item->homebranch ne $borrower->{branchcode} ) {
- return { status => 'cannotReserveFromOtherBranches' };
- }
- }
-
if ($pickup_branchcode) {
my $destination = Koha::Libraries->find({
branchcode => $pickup_branchcode,
unless ($item->can_be_transferred({ to => $destination })) {
return { status => 'cannotBeTransferred' };
}
- unless ($branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $item_library->validate_hold_sibling( {branchcode => $pickup_branchcode} )) {
+ if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup' && !$item_library->validate_hold_sibling( {branchcode => $pickup_branchcode} )) {
return { status => 'pickupNotInHoldGroup' };
}
- unless ($branchitemrule->{hold_fulfillment_policy} ne 'patrongroup' || Koha::Libraries->find({branchcode => $borrower->{branchcode}})->validate_hold_sibling({branchcode => $pickup_branchcode})) {
+ if ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup' && !Koha::Libraries->find({branchcode => $borrower->{branchcode}})->validate_hold_sibling({branchcode => $pickup_branchcode})) {
return { status => 'pickupNotInHoldGroup' };
}
}
my ($reserve_id, $borrowernumber) = @_;
return unless $reserve_id and $borrowernumber;
- my $reserve = Koha::Holds->find($reserve_id);
+ my $reserve = Koha::Holds->find($reserve_id) or return;
return 0 unless $reserve->borrowernumber == $borrowernumber;
- return 0 if ( $reserve->found eq 'W' ) or ( $reserve->found eq 'T' );
-
- return 1;
-
+ return $reserve->is_cancelable_from_opac;
}
=head2 GetOtherReserves
my ( $notissued, $reserved );
( $notissued ) = $dbh->selectrow_array( $issue_qry, undef,
( $biblionumber ) );
- if( $notissued ) {
+ if( $notissued == 0 ) {
+ # all items are issued
( $reserved ) = $dbh->selectrow_array( $holds_qry, undef,
( $biblionumber, $borrowernumber ) );
$fee = 0 if $reserved == 0;
+ } else {
+ $fee = 0;
}
}
return $fee;
if(defined $found) {
return 'Waiting' if $found eq 'W' and $priority == 0;
+ return 'Processing' if $found eq 'P';
return 'Finished' if $found eq 'F';
}
# if item is not for loan it cannot be reserved either.....
# except where items.notforloan < 0 : This indicates the item is holdable.
- my @SkipHoldTrapOnNotForLoanValue = split( '|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
- return if @SkipHoldTrapOnNotForLoanValue && grep( $notforloan_per_item, @SkipHoldTrapOnNotForLoanValue );
+ my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
+ return if grep { $_ eq $notforloan_per_item } @SkipHoldTrapOnNotForLoanValue;
my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? ($notforloan_per_item > 0) : ($notforloan_per_item && 1 );
return if $dont_trap or $notforloan_per_itemtype;
my $priority = 10000000;
foreach my $res (@reserves) {
- if ( $res->{'itemnumber'} && $res->{'itemnumber'} == $itemnumber && $res->{'priority'} == 0) {
- 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
- }
+ if ($res->{'found'} && $res->{'found'} eq 'W') {
+ return ( "Waiting", $res, \@reserves ); # Found it, it is waiting
+ } elsif ($res->{'found'} && $res->{'found'} eq 'P') {
+ return ( "Processing", $res, \@reserves ); # Found determinated hold, e. g. the transferred one
+ } elsif ($res->{'found'} && $res->{'found'} eq 'T') {
+ return ( "Transferred", $res, \@reserves ); # Found determinated hold, e. g. the transferred one
} else {
my $patron;
my $item;
$patron = Koha::Patrons->find( $res->{borrowernumber} );
$item = Koha::Items->find($itemnumber);
- my $local_holds_priority_item_branchcode =
- $item->$LocalHoldsPriorityItemControl;
- my $local_holds_priority_patron_branchcode =
- ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' )
- ? $res->{branchcode}
- : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' )
- ? $patron->branchcode
- : undef;
- $local_hold_match =
- $local_holds_priority_item_branchcode eq
- $local_holds_priority_patron_branchcode;
+ unless ($item->exclude_from_local_holds_priority || $patron->category->exclude_from_local_holds_priority) {
+ my $local_holds_priority_item_branchcode =
+ $item->$LocalHoldsPriorityItemControl;
+ my $local_holds_priority_patron_branchcode =
+ ( $LocalHoldsPriorityPatronControl eq 'PickupLibrary' )
+ ? $res->{branchcode}
+ : ( $LocalHoldsPriorityPatronControl eq 'HomeLibrary' )
+ ? $patron->branchcode
+ : undef;
+ $local_hold_match =
+ $local_holds_priority_item_branchcode eq
+ $local_holds_priority_patron_branchcode;
+ }
}
# See if this item is more important than what we've got so far
$patron ||= Koha::Patrons->find( $res->{borrowernumber} );
my $branch = GetReservesControlBranch( $item->unblessed, $patron->unblessed );
my $branchitemrule = C4::Circulation::GetBranchItemRule($branch,$item->effective_itemtype);
- next if ($branchitemrule->{'holdallowed'} == 0);
- next if (($branchitemrule->{'holdallowed'} == 1) && ($branch ne $patron->branchcode));
+ next if ($branchitemrule->{'holdallowed'} eq 'not_allowed');
+ next if (($branchitemrule->{'holdallowed'} eq 'from_home_library') && ($item->homebranch ne $patron->branchcode));
my $library = Koha::Libraries->find({branchcode=>$item->homebranch});
- next if (($branchitemrule->{'holdallowed'} == 3) && (!$library->validate_hold_sibling({branchcode => $patron->branchcode}) ));
+ next if (($branchitemrule->{'holdallowed'} eq 'from_local_hold_group') && (!$library->validate_hold_sibling({branchcode => $patron->branchcode}) ));
my $hold_fulfillment_policy = $branchitemrule->{hold_fulfillment_policy};
next if ( ($hold_fulfillment_policy eq 'holdgroup') && (!$library->validate_hold_sibling({branchcode => $res->{branchcode}})) );
next if ( ($hold_fulfillment_policy eq 'homebranch') && ($res->{branchcode} ne $item->$hold_fulfillment_policy) );
=cut
sub CancelExpiredReserves {
+ my $cancellation_reason = shift;
my $today = dt_from_string();
my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
my $expireWaiting = C4::Context->preference('ExpireReservesMaxPickUpDelay');
my $dtf = Koha::Database->new->schema->storage->datetime_parser;
- my $params = { expirationdate => { '<', $dtf->format_date($today) } };
+ my $params = {
+ -or => [
+ { expirationdate => { '<', $dtf->format_date($today) } },
+ { patron_expiration_date => { '<' => $dtf->format_date($today) } }
+ ]
+ };
+
$params->{found} = [ { '!=', 'W' }, undef ] unless $expireWaiting;
# FIXME To move to Koha::Holds->search_expired (?)
next if !$cancel_on_holidays && $calendar->is_holiday( $today );
my $cancel_params = {};
- if ( $hold->found eq 'W' ) {
+ $cancel_params->{cancellation_reason} = $cancellation_reason if defined($cancellation_reason);
+ if ( defined($hold->found) && $hold->found eq 'W' ) {
$cancel_params->{charge_cancel_fee} = 1;
}
+ $cancel_params->{autofill} = C4::Context->preference('ExpireReservesAutoFill');
$hold->cancel( $cancel_params );
}
}
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() } } )->as_list;
map { $_->resume() } @holds;
}
Change a hold request's priority or cancel it.
C<$rank> specifies the effect of the change. If C<$rank>
-is 'W' or 'n', nothing happens. This corresponds to leaving a
+is 'n', nothing happens. This corresponds to leaving a
request alone when changing its priority in the holds queue
for a bib.
priority to a non-zero value also sets the request's found
status and waiting date to NULL.
+If the hold is 'found' (waiting, in-transit, processing) the
+only field that can be updated is the expiration date.
+
The optional C<$itemnumber> parameter is used only when
C<$rank> is a non-zero integer; if supplied, the itemnumber
of the hold request is set accordingly; if omitted, the itemnumber
my $suspend_until = $params->{'suspend_until'};
my $borrowernumber = $params->{'borrowernumber'};
my $biblionumber = $params->{'biblionumber'};
+ my $cancellation_reason = $params->{'cancellation_reason'};
+ my $date = $params->{expirationdate};
- return if $rank eq "W";
- return if $rank eq "n";
+ return if defined $rank && $rank eq "n";
return unless ( $reserve_id || ( $borrowernumber && ( $biblionumber || $itemnumber ) ) );
$hold ||= Koha::Holds->find($reserve_id);
+ # FIXME Other calls may fail
+ Koha::Exceptions::ObjectNotFound->throw( 'No hold with id ' . $reserve_id ) unless $hold;
+
if ( $rank eq "del" ) {
- $hold->cancel;
+ $hold->cancel({ cancellation_reason => $cancellation_reason });
+ }
+ elsif ($hold->found && $hold->priority eq '0' && $date) {
+ logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
+ if C4::Context->preference('HoldsLog');
+
+ # The only column that can be updated for a found hold is the expiration date
+ $hold->expirationdate($date)->store();
}
elsif ($rank =~ /^\d+/ and $rank > 0) {
- logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, Dumper($hold->unblessed) )
+ logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
if C4::Context->preference('HoldsLog');
my $properties = {
if ( defined( $suspend_until ) ) {
if ( $suspend_until ) {
- $suspend_until = eval { dt_from_string( $suspend_until ) };
$hold->suspend_hold( $suspend_until );
} else {
# If the hold is suspended leave the hold suspended, but convert it to an indefinite hold.
}
}
-=head2 ModReserveFill
-
- &ModReserveFill($reserve);
-
-Fill a reserve. If I understand this correctly, this means that the
-reserved book has been found and given to the patron who reserved it.
-
-C<$reserve> specifies the reserve to fill. It is a reference-to-hash
-whose keys are fields from the reserves table in the Koha database.
-
-=cut
-
-sub ModReserveFill {
- my ($res) = @_;
- my $reserve_id = $res->{'reserve_id'};
-
- my $hold = Koha::Holds->find($reserve_id);
- # get the priority on this record....
- my $priority = $hold->priority;
-
- # update the hold statuses, no need to store it though, we will be deleting it anyway
- $hold->set(
- {
- found => 'F',
- priority => 0,
- }
- );
-
- logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, Dumper($hold->unblessed) )
- if C4::Context->preference('HoldsLog');
-
- # FIXME Must call Koha::Hold->cancel ? => No, should call ->filled and add the correct log
- Koha::Old::Hold->new( $hold->unblessed() )->store();
-
- $hold->delete();
-
- if ( C4::Context->preference('HoldFeeMode') eq 'any_time_is_collected' ) {
- my $reserve_fee = GetReserveFee( $hold->borrowernumber, $hold->biblionumber );
- ChargeReserveFee( $hold->borrowernumber, $reserve_fee, $hold->biblio->title );
- }
-
- # now fix the priority on the others (if the priority wasn't
- # already sorted!)....
- unless ( $priority == 0 ) {
- _FixPriority( { reserve_id => $reserve_id, biblionumber => $hold->biblionumber } );
- }
-}
-
=head2 ModReserveStatus
&ModReserveStatus($itemnumber, $newstatus);
=head2 ModReserveAffect
- &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id);
+ &ModReserveAffect($itemnumber,$borrowernumber,$diffBranchSend,$reserve_id, $desk_id, $notify_library);
This function affect an item and a status for a given reserve, either fetched directly
by record_id, or by borrowernumber and itemnumber or biblionumber. If only biblionumber
otherwise, a transfer is on the way, and the end of the transfer will
take care of the waiting status
+This function also removes any entry of the hold in holds queue table.
+
=cut
sub ModReserveAffect {
- my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id ) = @_;
+ my ( $itemnumber, $borrowernumber, $transferToDo, $reserve_id, $desk_id, $notify_library ) = @_;
my $dbh = C4::Context->dbh;
# we want to attach $itemnumber to $borrowernumber, find the biblionumber
my $already_on_shelf = $hold->found && $hold->found eq 'W';
$hold->itemnumber($itemnumber);
- $hold->set_waiting($transferToDo);
- if( !$transferToDo ){
+ if ($transferToDo) {
+ $hold->set_transfer();
+ } elsif (C4::Context->preference('HoldsNeedProcessingSIP')
+ && C4::Context->interface eq 'sip'
+ && !$already_on_shelf) {
+ $hold->set_processing();
+ } else {
+ $hold->set_waiting($desk_id);
_koha_notify_reserve( $hold->reserve_id ) unless $already_on_shelf;
- my $transfers = Koha::Item::Transfers->search({
- itemnumber => $itemnumber,
- datearrived => undef
- });
- while( my $transfer = $transfers->next ){
- $transfer->datearrived( dt_from_string() )->store;
- };
+ # Complete transfer if one exists
+ my $transfer = $hold->item->get_transfer;
+ $transfer->receive if $transfer;
}
+ _koha_notify_hold_changed( $hold ) if $notify_library;
_FixPriority( { biblionumber => $biblionumber } );
my $item = Koha::Items->find($itemnumber);
CartToShelf( $itemnumber );
}
- logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, Dumper($hold->unblessed) )
+ my $std = $dbh->prepare(q{
+ DELETE q, t
+ FROM tmp_holdsqueue q
+ INNER JOIN hold_fill_targets t
+ ON q.borrowernumber = t.borrowernumber
+ AND q.biblionumber = t.biblionumber
+ AND q.itemnumber = t.itemnumber
+ AND q.item_level_request = t.item_level_request
+ AND q.holdingbranch = t.source_branchcode
+ WHERE t.reserve_id = ?
+ });
+ $std->execute($hold->reserve_id);
+
+ logaction( 'HOLDS', 'MODIFY', $hold->reserve_id, $hold )
if C4::Context->preference('HoldsLog');
return;
=head2 ModReserveCancelAll
- ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber);
+ ($messages,$nextreservinfo) = &ModReserveCancelAll($itemnumber,$borrowernumber,$reason);
function to cancel reserv,check other reserves, and transfer document if it's necessary
sub ModReserveCancelAll {
my $messages;
my $nextreservinfo;
- my ( $itemnumber, $borrowernumber ) = @_;
+ my ( $itemnumber, $borrowernumber, $cancellation_reason ) = @_;
#step 1 : cancel the reservation
my $holds = Koha::Holds->search({ itemnumber => $itemnumber, borrowernumber => $borrowernumber });
return unless $holds->count;
- $holds->next->cancel;
+ $holds->next->cancel({ cancellation_reason => $cancellation_reason });
#step 2 launch the subroutine of the others reserves
( $messages, $nextreservinfo ) = GetOtherReserves($itemnumber);
this routine does not check IndependentBranches
and canreservefromotherbranches.
+Note also that this subroutine does not checks smart
+rules limits for item by reservesallowed/holds_per_record
+values, this complemented in calling code with calls and
+checks with CanItemBeReserved or CanBookBeReserved.
+
=cut
sub IsAvailableForItemLevelRequest {
# or something similar - need to be
# consolidated
my $itemtype = $item->effective_itemtype;
+ return 0
+ unless defined $itemtype;
my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
return 0 if
$notforloan_per_itemtype ||
$item->itemlost ||
- $item->notforloan > 0 ||
+ $item->notforloan > 0 || # item with negative or zero notforloan value is holdable
$item->withdrawn ||
($item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems'));
GetReservesControlBranch( $item->unblessed(), $patron->unblessed() );
my $branchitemrule =
C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->itype );
- my $home_library = Koka::Libraries->find( {branchcode => $item->homebranch} );
+ my $home_library = Koha::Libraries->find( {branchcode => $item->homebranch} );
return 0 unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $home_library->validate_hold_sibling( {branchcode => $pickup_branchcode} );
}
sub ItemsAnyAvailableAndNotRestricted {
my $param = shift;
- my @items = Koha::Items->search( { biblionumber => $param->{biblionumber} } );
+ my @items = Koha::Items->search( { biblionumber => $param->{biblionumber} } )->as_list;
foreach my $i (@items) {
my $reserves_control_branch =
# we can return (end the loop) when first one found:
return 1
unless $i->itemlost
- || $i->notforloan > 0
+ || $i->notforloan # items with non-zero notforloan cannot be checked out
|| $i->withdrawn
|| $i->onloan
|| IsItemOnHoldAndFound( $i->id )
|| ( $i->damaged
&& ! C4::Context->preference('AllowHoldsOnDamagedItems') )
|| Koha::ItemTypes->find( $i->effective_itemtype() )->notforloan
- || $branchitemrule->{holdallowed} == 1 && $param->{patron}->branchcode ne $i->homebranch
- || $branchitemrule->{holdallowed} == 3 && ! $item_library->validate_hold_sibling( { branchcode => $param->{patron}->branchcode } )
- || CanItemBeReserved( $param->{patron}->borrowernumber, $i->id )->{status} ne 'OK';
+ || $branchitemrule->{holdallowed} eq 'from_home_library' && $param->{patron}->branchcode ne $i->homebranch
+ || $branchitemrule->{holdallowed} eq 'from_local_hold_group' && ! $item_library->validate_hold_sibling( { branchcode => $param->{patron}->branchcode } )
+ || CanItemBeReserved( $param->{patron}, $i )->{status} ne 'OK';
}
return 0;
_FixPriority({ reserve_id => $reserve_id, rank => $last_priority });
}
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $hold->biblionumber ]
+ }
+ ) if C4::Context->preference('RealTimeHoldsQueue');
# FIXME Should return the new priority
}
sub ToggleSuspend {
my ( $reserve_id, $suspend_until ) = @_;
- $suspend_until = dt_from_string($suspend_until) if ($suspend_until);
-
my $hold = Koha::Holds->find( $reserve_id );
if ( $hold->is_suspended ) {
my $suspend_until = $params{'suspend_until'} || undef;
my $suspend = defined( $params{'suspend'} ) ? $params{'suspend'} : 1;
- $suspend_until = eval { dt_from_string($suspend_until) }
- if ( defined($suspend_until) );
-
return unless ( $borrowernumber || $biblionumber );
my $params;
$params->{borrowernumber} = $borrowernumber if $borrowernumber;
$params->{biblionumber} = $biblionumber if $biblionumber;
- my @holds = Koha::Holds->search($params);
+ my @holds = Koha::Holds->search($params)->as_list;
if ($suspend) {
map { $_->suspend_hold($suspend_until) } @holds;
UPDATE reserves
SET priority = 0
WHERE reserve_id = ?
- AND found IN ('W', 'T')
+ AND found IN ('W', 'T', 'P')
";
my $sth = $dbh->prepare($query);
$sth->execute( $reserve_id );
SELECT reserve_id, borrowernumber, reservedate
FROM reserves
WHERE biblionumber = ?
- AND ((found <> 'W' AND found <> 'T') OR found IS NULL)
+ AND ((found <> 'W' AND found <> 'T' AND found <> 'P') OR found IS NULL)
ORDER BY priority ASC
";
my $sth = $dbh->prepare($query);
# if index exists in array then move it to new position
if ( $key > -1 && $rank ne 'del' && $rank > 0 ) {
- my $new_rank = $rank -
- 1; # $new_rank is what you want the new index to be in the array
+ my $new_rank = $rank - 1; # $new_rank is what you want the new index to be in the array
my $moving_item = splice( @priority, $key, 1 );
+ $new_rank = scalar @priority if $new_rank > scalar @priority;
splice( @priority, $new_rank, 0, $moving_item );
}
);
}
- $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 ORDER BY priority" );
- $sth->execute();
-
unless ( $ignoreSetLowestRank ) {
+ $sth = $dbh->prepare( "SELECT reserve_id FROM reserves WHERE lowestPriority = 1 AND biblionumber = ? ORDER BY priority" );
+ $sth->execute($biblionumber);
while ( my $res = $sth->fetchrow_hashref() ) {
_FixPriority({
reserve_id => $res->{'reserve_id'},
biblioitems.biblioitemnumber AS biblioitemnumber,
reserves.itemnumber AS itemnumber,
reserves.reserve_id AS reserve_id,
- reserves.itemtype AS itemtype
+ reserves.itemtype AS itemtype,
+ reserves.non_priority AS non_priority
FROM reserves
JOIN biblioitems USING (biblionumber)
- JOIN hold_fill_targets USING (biblionumber, borrowernumber, itemnumber)
+ JOIN hold_fill_targets USING (reserve_id)
WHERE found IS NULL
AND priority > 0
AND item_level_request = 1
- AND itemnumber = ?
+ AND hold_fill_targets.itemnumber = ?
AND reservedate <= DATE_ADD(NOW(),INTERVAL ? DAY)
AND suspend = 0
ORDER BY priority
biblioitems.biblioitemnumber AS biblioitemnumber,
reserves.itemnumber AS itemnumber,
reserves.reserve_id AS reserve_id,
- reserves.itemtype AS itemtype
+ reserves.itemtype AS itemtype,
+ reserves.non_priority AS non_priority
FROM reserves
JOIN biblioitems USING (biblionumber)
- JOIN hold_fill_targets USING (biblionumber, borrowernumber)
+ JOIN hold_fill_targets USING (reserve_id)
WHERE found IS NULL
AND priority > 0
AND item_level_request = 0
reserves.timestamp AS timestamp,
reserves.itemnumber AS itemnumber,
reserves.reserve_id AS reserve_id,
- reserves.itemtype AS itemtype
+ reserves.itemtype AS itemtype,
+ reserves.non_priority AS non_priority
FROM reserves
WHERE reserves.biblionumber = ?
AND (reserves.itemnumber IS NULL OR reserves.itemnumber = ?)
_koha_notify_reserve( $hold->reserve_id );
Sends a notification to the patron that their hold has been filled (through
-ModReserveAffect, _not_ ModReserveFill)
+ModReserveAffect)
The letter code for this notice may be found using the following query:
sub _koha_notify_reserve {
my $reserve_id = shift;
+
my $hold = Koha::Holds->find($reserve_id);
my $borrowernumber = $hold->borrowernumber;
message_name => 'Hold_Filled'
} );
- my $library = Koha::Libraries->find( $hold->branchcode )->unblessed;
-
- my $admin_email_address = $library->{branchemail} || C4::Context->preference('KohaAdminEmailAddress');
+ my $library = Koha::Libraries->find( $hold->branchcode );
+ my $inbound_email_address = $library->inbound_email_address;
my %letter_params = (
module => 'reserves',
branchcode => $hold->branchcode,
lang => $patron->lang,
tables => {
- 'branches' => $library,
+ 'branches' => $library->unblessed,
'borrowers' => $patron->unblessed,
'biblio' => $hold->biblionumber,
'biblioitems' => $hold->biblionumber,
C4::Letters::EnqueueLetter( {
letter => $letter,
borrowernumber => $borrowernumber,
- from_address => $admin_email_address,
+ from_address => $inbound_email_address,
message_transport_type => $mtt,
} );
};
next if (
( $mtt eq 'email' and not $to_address ) # No email address
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
+ or ( $mtt eq 'itiva' and C4::Context->preference('TalkingTechItivaPhoneNotification') ) # Notice is handled by TalkingTech_itiva_outbound.pl
+ or ( $mtt eq 'phone' and not $patron->phone ) # No phone number to call
);
&$send_notification($mtt, $letter_code);
}
-=head2 _ShiftPriorityByDateAndPriority
+=head2 _koha_notify_hold_changed
+
+ _koha_notify_hold_changed( $hold_object );
+
+=cut
+
+sub _koha_notify_hold_changed {
+ my $hold = shift;
+
+ my $patron = $hold->patron;
+ my $library = $hold->branch;
+
+ my $letter = C4::Letters::GetPreparedLetter(
+ module => 'reserves',
+ letter_code => 'HOLD_CHANGED',
+ branchcode => $hold->branchcode,
+ substitute => { today => output_pref( dt_from_string ) },
+ tables => {
+ 'branches' => $library->unblessed,
+ 'borrowers' => $patron->unblessed,
+ 'biblio' => $hold->biblionumber,
+ 'biblioitems' => $hold->biblionumber,
+ 'reserves' => $hold->unblessed,
+ 'items' => $hold->itemnumber,
+ },
+ );
+
+ return unless $letter;
+
+ my $email =
+ C4::Context->preference('ExpireReservesAutoFillEmail')
+ || $library->inbound_email_address;
+
+ C4::Letters::EnqueueLetter(
+ {
+ letter => $letter,
+ borrowernumber => $patron->id,
+ message_transport_type => 'email',
+ from_address => $email,
+ to_address => $email,
+ }
+ );
+}
+
+=head2 _ShiftPriority
- $new_priority = _ShiftPriorityByDateAndPriority( $biblionumber, $reservedate, $priority );
+ $new_priority = _ShiftPriority( $biblionumber, $priority );
This increments the priority of all reserves after the one
with either the lowest date after C<$reservedate>
=cut
-sub _ShiftPriorityByDateAndPriority {
- my ( $biblio, $resdate, $new_priority ) = @_;
+sub _ShiftPriority {
+ my ( $biblio, $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 $query = "SELECT priority FROM reserves WHERE biblionumber = ? AND priority > ? ORDER BY priority ASC LIMIT 1";
my $sth = $dbh->prepare( $query );
- $sth->execute( $biblio, $resdate, $new_priority );
+ $sth->execute( $biblio, $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 );
my ( $restype, $res, undef ) = CheckReserves( $itemnumber, undef, $lookahead );
return unless $res;
- my $biblionumber = $res->{biblionumber};
+ my $biblionumber = $res->{biblionumber};
if ($res->{borrowernumber} == $borrowernumber) {
- ModReserveFill($res);
+ my $hold = Koha::Holds->find( $res->{reserve_id} );
+ $hold->fill;
}
else {
# warn "Reserved";
if ( $borr_res ) {
# The item is reserved by the current patron
- ModReserveFill($borr_res->unblessed);
+ $borr_res->fill;
}
if ( $cancelreserve eq 'revert' ) { ## Revert waiting reserve to priority 1
# don't reorder those already waiting
$sth = $dbh->prepare(
-"SELECT * FROM reserves WHERE biblionumber = ? AND (found <> ? AND found <> ? OR found is NULL) ORDER BY reservedate ASC"
+"SELECT * FROM reserves WHERE biblionumber = ? AND (found NOT IN ('W', 'T', 'P') OR found is NULL) ORDER BY reservedate ASC"
);
my $upd_sth = $dbh->prepare(
"UPDATE reserves SET priority = ? WHERE biblionumber = ? AND borrowernumber = ?
AND reservedate = ? AND (itemnumber = ? or itemnumber is NULL) "
);
- $sth->execute( $to_biblio, 'W', 'T' );
+ $sth->execute( $to_biblio );
my $priority = 1;
while ( my $reserve = $sth->fetchrow_hashref() ) {
$upd_sth->execute(
priority => 1,
found => undef,
waitingdate => undef,
+ expirationdate => $hold->patron_expiration_date,
itemnumber => $hold->item_level_hold ? $hold->itemnumber : undef,
}
)->store();
_FixPriority( { biblionumber => $hold->biblionumber } );
+ Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
+ {
+ biblio_ids => [ $hold->biblionumber ]
+ }
+ ) if C4::Context->preference('RealTimeHoldsQueue');
+
+
return $hold;
}
the parameter value.
After calculation of this priority, it is recommended to call
-_ShiftPriorityByDateAndPriority. Note that this is currently done in
+_ShiftPriority. Note that this is currently done in
AddReserves.
=cut
AND priority > 0
AND (found IS NULL OR found = '')
};
- #skip found==W or found==T (waiting or transit holds)
+ #skip found==W or found==T or found==P (waiting, transit or processing holds)
if( $resdate ) {
$sql.= ' AND ( reservedate <= ? )';
}
my ( $borrowernumber, $biblionumber ) = @_;
my $patron = Koha::Patrons->find($borrowernumber);
- my @items = Koha::Items->search( { biblionumber => $biblionumber } );
+ my @items = Koha::Items->search( { biblionumber => $biblionumber } )->as_list;
my $controlbranch = C4::Context->preference('ReservesControlBranch');
$branchcode = $item->homebranch if ( $controlbranch eq "ItemHomeLibrary" );
- my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
- my $holds_per_record = $rule ? $rule->{holds_per_record} : 0;
- $max = $holds_per_record if $holds_per_record > $max;
- }
-
- return $max;
-}
-
-=head2 GetHoldRule
-
-my $rule = GetHoldRule( $categorycode, $itemtype, $branchcode );
-
-Returns the matching hold related issuingrule fields for a given
-patron category, itemtype, and library.
-
-=cut
-
-sub GetHoldRule {
- my ( $categorycode, $itemtype, $branchcode ) = @_;
-
- my $reservesallowed = Koha::CirculationRules->get_effective_rule(
- {
- itemtype => $itemtype,
+ my $rule = Koha::CirculationRules->get_effective_rule({
categorycode => $categorycode,
- branchcode => $branchcode,
- rule_name => 'reservesallowed',
- order_by => {
- -desc => [ 'categorycode', 'itemtype', 'branchcode' ]
- }
- }
- );
-
- my $rules;
- if ( $reservesallowed ) {
- $rules->{reservesallowed} = $reservesallowed->rule_value;
- $rules->{itemtype} = $reservesallowed->itemtype;
- $rules->{categorycode} = $reservesallowed->categorycode;
- $rules->{branchcode} = $reservesallowed->branchcode;
- }
-
- my $holds_per_x_rules = Koha::CirculationRules->get_effective_rules(
- {
itemtype => $itemtype,
- categorycode => $categorycode,
branchcode => $branchcode,
- rules => ['holds_per_record', 'holds_per_day'],
- order_by => {
- -desc => [ 'categorycode', 'itemtype', 'branchcode' ]
- }
- }
- );
- $rules->{holds_per_record} = $holds_per_x_rules->{holds_per_record};
- $rules->{holds_per_day} = $holds_per_x_rules->{holds_per_day};
+ rule_name => 'holds_per_record'
+ });
+ my $holds_per_record = $rule ? $rule->rule_value : 0;
+ $max = $holds_per_record if $holds_per_record > $max;
+ }
- return $rules;
+ return $max;
}
=head1 AUTHOR