Koha-clms/t/db_dependent/Reserves/CancelExpiredReserves.t

389 lines
13 KiB
Perl
Executable File

#!/usr/bin/perl
use Modern::Perl;
use Test::More tests => 6;
use Test::NoWarnings;
use Test::MockModule;
use Time::Fake;
use t::lib::Mocks;
use t::lib::TestBuilder;
use C4::Members;
use C4::Reserves qw( CancelExpiredReserves );
use Koha::Database;
use Koha::DateUtils qw( dt_from_string );
use Koha::Holds;
use Koha::Old::Holds;
my $builder = t::lib::TestBuilder->new();
my $schema = Koha::Database->new->schema;
$schema->storage->txn_begin;
subtest 'CancelExpiredReserves tests incl. holidays' => sub {
plan tests => 4;
t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays', 0 );
# Waiting holds could be cancelled only if ExpireReservesMaxPickUpDelay is set to "allow", see bug 19260
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
my $today = dt_from_string();
my $reserve_reservedate = $today->clone;
$reserve_reservedate->subtract( days => 30 );
my $reserve1_expirationdate = $today->clone;
$reserve1_expirationdate->add( days => 1 );
# Reserve not expired
my $reserve1 = $builder->build(
{
source => 'Reserve',
value => {
reservedate => $reserve_reservedate,
patron_expiration_date => $reserve1_expirationdate,
cancellationdate => undef,
priority => 0,
found => 'W',
},
}
);
CancelExpiredReserves();
my $r1 = Koha::Holds->find( $reserve1->{reserve_id} );
ok( $r1, 'Reserve 1 should not be canceled.' );
my $reserve2_expirationdate = $today->clone;
$reserve2_expirationdate->subtract( days => 1 );
# Reserve expired
my $reserve2 = $builder->build(
{
source => 'Reserve',
value => {
reservedate => $reserve_reservedate,
patron_expiration_date => $reserve2_expirationdate,
cancellationdate => undef,
priority => 0,
found => 'W',
},
}
);
CancelExpiredReserves();
my $r2 = Koha::Holds->find( $reserve2->{reserve_id} );
is( $r2, undef, 'reserve 2 should be canceled.' );
# Reserve expired on holiday
my $reserve3 = $builder->build(
{
source => 'Reserve',
value => {
reservedate => $reserve_reservedate,
patron_expiration_date => $reserve2_expirationdate,
branchcode => 'LIB1',
cancellationdate => undef,
priority => 0,
found => 'W',
},
}
);
Koha::Caches->get_instance()->flush_all();
my $holiday = $builder->build_object(
{
class => 'Koha::Library::Calendar::SingleClosures',
value => { library_id => 'LIB1', date => $today->ymd, title => 'My holiday', description => '' }
}
);
CancelExpiredReserves();
my $r3 = Koha::Holds->find( $reserve3->{reserve_id} );
ok( $r3, 'Reserve 3 should not be canceled.' );
t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays', 1 );
CancelExpiredReserves();
$r3 = Koha::Holds->find( $reserve3->{reserve_id} );
is( $r3, undef, 'Reserve 3 should be canceled.' );
};
subtest 'Test handling of waiting reserves by CancelExpiredReserves' => sub {
plan tests => 2;
Koha::Holds->delete;
my $category = $builder->build( { source => 'Category' } );
my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
my $item = $builder->build_sample_item;
my $itemnumber = $item->itemnumber;
my $borrowernumber = $builder->build(
{ source => 'Borrower', value => { categorycode => $category->{categorycode}, branchcode => $branchcode } } )
->{borrowernumber};
my $resdate = dt_from_string->add( days => -20 );
my $expdate = dt_from_string->add( days => -2 );
my $notexpdate = dt_from_string->add( days => 2 );
my $hold1 = Koha::Hold->new(
{
branchcode => $branchcode,
borrowernumber => $borrowernumber,
biblionumber => $item->biblionumber,
priority => 1,
reservedate => $resdate,
expirationdate => $notexpdate,
found => undef,
}
)->store;
my $hold2 = Koha::Hold->new(
{
branchcode => $branchcode,
borrowernumber => $borrowernumber,
biblionumber => $item->biblionumber,
priority => 2,
reservedate => $resdate,
expirationdate => $expdate,
found => undef,
}
)->store;
my $hold3 = Koha::Hold->new(
{
branchcode => $branchcode,
borrowernumber => $borrowernumber,
biblionumber => $item->biblionumber,
itemnumber => $itemnumber,
priority => 0,
reservedate => $resdate,
expirationdate => $expdate,
found => 'W',
}
)->store;
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 0 );
CancelExpiredReserves();
my $count1 = Koha::Holds->search->count;
is( $count1, 2, 'Only the non-waiting expired holds should be cancelled' );
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
CancelExpiredReserves();
my $count2 = Koha::Holds->search->count;
is( $count2, 1, 'Also the waiting expired hold should be cancelled now' );
};
subtest 'Test handling of in transit reserves by CancelExpiredReserves' => sub {
plan tests => 2;
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
my $expdate = dt_from_string->add( days => -2 );
my $reserve = $builder->build(
{
source => 'Reserve',
value => {
patron_expiration_date => '2018-01-01',
found => 'T',
cancellationdate => undef,
suspend => 0,
suspend_until => undef
}
}
);
my $count = Koha::Holds->search->count;
CancelExpiredReserves();
is( Koha::Holds->search->count, $count - 1, "Transit hold is cancelled if ExpireReservesMaxPickUpDelay set" );
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 0 );
my $reserve2 = $builder->build(
{
source => 'Reserve',
value => {
patron_expiration_date => '2018-01-01',
found => 'T',
cancellationdate => undef,
suspend => 0,
suspend_until => undef
}
}
);
CancelExpiredReserves();
is( Koha::Holds->search->count, $count - 1, "Transit hold is cancelled if ExpireReservesMaxPickUpDelay unset" );
};
subtest 'Test handling of cancellation reason if passed' => sub {
plan tests => 2;
my $expdate = dt_from_string->add( days => -2 );
my $reserve = $builder->build(
{
source => 'Reserve',
value => {
patron_expiration_date => '2018-01-01',
found => 'T',
cancellationdate => undef,
suspend => 0,
suspend_until => undef
}
}
);
my $reserve_id = $reserve->{reserve_id};
my $count = Koha::Holds->search->count;
{
# Prevent warning 'No reserves HOLD_CANCELLATION letter transported by email'
my $mock_letters = Test::MockModule->new('C4::Letters');
$mock_letters->mock( 'GetPreparedLetter', sub { return } );
CancelExpiredReserves("EXPIRED");
}
is( Koha::Holds->search->count, $count - 1, "Hold is cancelled when reason is passed" );
my $old_reserve = Koha::Old::Holds->find($reserve_id);
is( $old_reserve->cancellation_reason, 'EXPIRED', "Hold cancellation_reason was set correctly" );
};
subtest 'Holiday logic edge cases' => sub {
plan tests => 2;
subtest 'Hold expired on business day should be cancelled retrospectively when script runs on holiday' => sub {
plan tests => 1;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays', 0 );
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
# Clear any existing holidays from previous tests
Koha::Caches->get_instance()->flush_all();
# Create a library for our tests
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
# The bug scenario: Hold expired on Monday (business day), script runs on Wednesday (holiday)
# The hold SHOULD be cancelled because it expired on a business day and Tuesday was a business day
# that was missed, so we need retrospective cancellation even though today is a holiday
my $monday = dt_from_string('2024-01-15'); # Monday (business day, hold expires)
my $wednesday = dt_from_string('2024-01-17'); # Wednesday (holiday, script runs now)
# Create a hold that expired on Monday (business day)
my $hold_expired_monday = $builder->build_object(
{
class => 'Koha::Holds',
value => {
branchcode => $library->branchcode,
patron_expiration_date => $monday->ymd,
expirationdate => $monday->ymd,
cancellationdate => undef,
priority => 0,
found => 'W',
},
}
);
# Make Wednesday a holiday (but Monday and Tuesday are business days)
my $wednesday_holiday = $builder->build_object(
{
class => 'Koha::Library::Calendar::SingleClosures',
value => {
library_id => $library->branchcode, date => $wednesday->ymd, title => 'Wednesday Holiday',
description => ''
}
}
);
# Clear cache so holiday is recognized
Koha::Caches->get_instance()->flush_all();
# Set fake time to Wednesday (holiday)
Time::Fake->offset( $wednesday->epoch );
# The hold SHOULD be cancelled because:
# 1. It expired on Monday (business day)
# 2. Tuesday was a business day that was missed
# 3. Even though today (Wednesday) is a holiday, we need retrospective cancellation
CancelExpiredReserves();
my $hold_check = Koha::Holds->find( $hold_expired_monday->id );
is(
$hold_check, undef,
'Hold that expired on business day should be cancelled retrospectively even when script runs on holiday'
);
# Reset time
Time::Fake->reset;
$schema->storage->txn_rollback;
};
subtest 'Hold expired on holiday should not be cancelled when script runs on same holiday' => sub {
plan tests => 1;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'ExpireReservesOnHolidays', 0 );
t::lib::Mocks::mock_preference( 'ExpireReservesMaxPickUpDelay', 1 );
# Clear any existing holidays from previous tests
Koha::Caches->get_instance()->flush_all();
# Create a library for our tests
my $library = $builder->build_object( { class => 'Koha::Libraries' } );
# Control scenario: Hold expired on Monday (holiday), script runs on Monday (same holiday)
# The hold should NOT be cancelled because it expired today and today is a holiday
my $monday = dt_from_string('2024-01-15'); # Monday (holiday, hold expires and script runs)
# Create a hold that expired on Monday (holiday)
my $hold_expired_monday = $builder->build_object(
{
class => 'Koha::Holds',
value => {
branchcode => $library->branchcode,
patron_expiration_date => $monday->ymd,
expirationdate => $monday->ymd,
cancellationdate => undef,
priority => 0,
found => 'W',
},
}
);
# Make Monday a holiday
my $monday_holiday = $builder->build_object(
{
class => 'Koha::Library::Calendar::SingleClosures',
value => {
library_id => $library->branchcode, date => $monday->ymd, title => 'Monday Holiday',
description => ''
}
}
);
# Clear cache so holiday is recognized
Koha::Caches->get_instance()->flush_all();
# Set fake time to Monday (holiday)
Time::Fake->offset( $monday->epoch );
# The hold should NOT be cancelled because it expired today and today is a holiday
CancelExpiredReserves();
my $hold_check = Koha::Holds->find( $hold_expired_monday->id );
ok(
$hold_check,
'Hold that expired on holiday should not be cancelled when script runs on same holiday'
);
# Reset time
Time::Fake->reset;
$schema->storage->txn_rollback;
};
};
$schema->storage->txn_rollback;