use Modern::Perl;
use utf8;
-use Test::More tests => 56;
+use Test::More tests => 63;
use Test::Exception;
use Test::MockModule;
use Test::Deep qw( cmp_deeply );
use Koha::Item::Transfers;
use Koha::Checkouts;
use Koha::Patrons;
+use Koha::Patron::Debarments qw( GetDebarments AddDebarment DelUniqueDebarment );
use Koha::Holds;
use Koha::CirculationRules;
use Koha::Subscriptions;
use Koha::Account::Offsets;
use Koha::ActionLogs;
use Koha::Notice::Messages;
+use Koha::Cache::Memory::Lite;
+my $builder = t::lib::TestBuilder->new;
sub set_userenv {
my ( $library ) = @_;
- t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
+ my $staff = $builder->build_object({ class => "Koha::Patrons" });
+ t::lib::Mocks::mock_userenv({ patron => $staff, branchcode => $library->{branchcode} });
}
sub str {
my $schema = Koha::Database->schema;
$schema->storage->txn_begin;
-my $builder = t::lib::TestBuilder->new;
my $dbh = C4::Context->dbh;
# Prevent random failures by mocking ->now
my ( $reused_itemnumber_1, $reused_itemnumber_2 );
subtest "CanBookBeRenewed tests" => sub {
- plan tests => 95;
+ plan tests => 104;
C4::Context->set_preference('ItemsDeniedRenewal','');
# Generate test biblio
# Make sure fine calculation isn't skipped when adding renewal
t::lib::Mocks::mock_preference('CalculateFinesOnReturn', 1);
+ my $staff = $builder->build_object({ class => "Koha::Patrons" });
+ t::lib::Mocks::mock_userenv({ patron => $staff });
+
t::lib::Mocks::mock_preference('RenewalLog', 0);
my $date = output_pref( { dt => dt_from_string(), dateonly => 1, dateformat => 'iso' } );
my %params_renewal = (
);
$issue = AddIssue( $renewing_borrower, $item_4->barcode, undef, undef, undef, undef, { auto_renew => 1 } );
- ( $renewokay, $error ) =
+ my $info;
+ ( $renewokay, $error, $info ) =
CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
is( $error, 'auto_too_soon',
'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = undef (returned code is auto_too_soon)' );
+ is( $info->{soonest_renew_date} , dt_from_string($issue->date_due), "Due date is returned as earliest renewal date when error is 'auto_too_soon'" );
AddReserve(
{
branchcode => $branch,
( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'Still should not be able to renew' );
is( $error, 'on_reserve', 'returned code is on_reserve, reserve checked when not checking for cron' );
- ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, undef, 1 );
+ ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, undef, 1 );
is( $renewokay, 0, 'Still should not be able to renew' );
is( $error, 'auto_too_soon', 'returned code is auto_too_soon, reserve not checked when checking for cron' );
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due), "Due date is returned as earliest renewal date when error is 'auto_too_soon'" );
( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, 1 );
is( $renewokay, 0, 'Still should not be able to renew' );
is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
is( $renewokay, 0, 'Still should not be able to renew' );
is( $error, 'on_reserve', 'returned code is on_reserve, auto_too_soon limit is overridden' );
$dbh->do('UPDATE circulation_rules SET rule_value = 0 where rule_name = "norenewalbefore"');
+ Koha::Cache::Memory::Lite->flush();
( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber, 1 );
is( $renewokay, 0, 'Still should not be able to renew' );
is( $error, 'on_reserve', 'returned code is on_reserve, auto_renew only happens if not on reserve' );
}
);
- ( $renewokay, $error ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
+ ( $renewokay, $error, $info ) = CanBookBeRenewed($renewing_borrowernumber, $item_1->itemnumber);
is( $renewokay, 0, 'Bug 7413: Cannot renew, renewal is premature');
is( $error, 'too_soon', 'Bug 7413: Cannot renew, renewal is premature (returned code is too_soon)');
-
- # Bug 14395
- # Test 'exact time' setting for syspref NoRenewalBeforePrecision
- t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact_time' );
- is(
- GetSoonestRenewDate( $renewing_borrowernumber, $item_1->itemnumber ),
- $datedue->clone->add( days => -7 ),
- 'Bug 14395: Renewals permitted 7 days before due date, as expected'
- );
-
- # Bug 14395
- # Test 'date' setting for syspref NoRenewalBeforePrecision
- t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
- is(
- GetSoonestRenewDate( $renewing_borrowernumber, $item_1->itemnumber ),
- $datedue->clone->add( days => -7 )->truncate( to => 'day' ),
- 'Bug 14395: Renewals permitted 7 days before due date, as expected'
- );
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due)->subtract( days => 7 ), "Soonest renew date returned when error is 'too_soon'");
# Bug 14101
# Test premature automatic renewal
- ( $renewokay, $error ) =
+ ( $renewokay, $error, $info ) =
CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
is( $error, 'auto_too_soon',
'Bug 14101: Cannot renew, renewal is automatic and premature (returned code is auto_too_soon)'
);
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due)->subtract( days => 7 ), "Soonest renew date returned when error is 'auto_too_soon'");
$renewing_borrower_obj->autorenew_checkouts(0)->store;
- ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+ ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'No renewal before is 7, patron opted out of auto_renewal still cannot renew early' );
is( $error, 'too_soon', 'Error is too_soon, no auto' );
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due)->subtract( days => 7 ), "Soonest renew date returned when error is 'too_soon'");
$renewing_borrower_obj->autorenew_checkouts(1)->store;
# Change policy so that loans can only be renewed exactly on due date (0 days prior to due date)
# and test automatic renewal again
$dbh->do(q{UPDATE circulation_rules SET rule_value = '0' WHERE rule_name = 'norenewalbefore'});
- ( $renewokay, $error ) =
+ Koha::Cache::Memory::Lite->flush();
+ ( $renewokay, $error, $info ) =
CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic and premature' );
is( $error, 'auto_too_soon',
'Bug 14101: Cannot renew, renewal is automatic and premature, "No renewal before" = 0 (returned code is auto_too_soon)'
);
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due), "Soonest renew date returned when error is 'auto_too_soon'");
$renewing_borrower_obj->autorenew_checkouts(0)->store;
- ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
+ ( $renewokay, $error, $info ) = CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'No renewal before is 0, patron opted out of auto_renewal still cannot renew early' );
is( $error, 'too_soon', 'Error is too_soon, no auto' );
+ is( $info->{soonest_renew_date}, dt_from_string($issue->date_due), "Soonest renew date returned when error is 'auto_too_soon'");
$renewing_borrower_obj->autorenew_checkouts(1)->store;
# Change policy so that loans can be renewed 99 days prior to the due date
# and test automatic renewal again
$dbh->do(q{UPDATE circulation_rules SET rule_value = '99' WHERE rule_name = 'norenewalbefore'});
+ Koha::Cache::Memory::Lite->flush();
( $renewokay, $error ) =
CanBookBeRenewed( $renewing_borrowernumber, $item_4->itemnumber );
is( $renewokay, 0, 'Bug 14101: Cannot renew, renewal is automatic' );
$dbh->do(q{UPDATE circulation_rules SET rule_value = '10' WHERE rule_name = 'norenewalbefore'});
$dbh->do(q{UPDATE circulation_rules SET rule_value = '15' WHERE rule_name = 'no_auto_renewal_after'});
$dbh->do(q{UPDATE circulation_rules SET rule_value = NULL WHERE rule_name = 'no_auto_renewal_after_hard_limit'});
+ Koha::Cache::Memory::Lite->flush();
Koha::CirculationRules->set_rules(
{
categorycode => undef,
$item_3->itemcallnumber || '' ),
"Account line description must not contain 'Lost Items ', but be title, barcode, itemcallnumber"
);
+
+ # Recalls
+ t::lib::Mocks::mock_preference('UseRecalls', 1);
+ Koha::CirculationRules->set_rules({
+ categorycode => undef,
+ branchcode => undef,
+ itemtype => undef,
+ rules => {
+ recalls_allowed => 10,
+ renewalsallowed => 5,
+ },
+ });
+ my $recall_borrower = $builder->build_object({ class => 'Koha::Patrons' });
+ my $recall_biblio = $builder->build_object({ class => 'Koha::Biblios' });
+ my $recall_item1 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $recall_biblio->biblionumber } });
+ my $recall_item2 = $builder->build_object({ class => 'Koha::Items' }, { value => { biblionumber => $recall_biblio->biblionumber } });
+
+ AddIssue( $renewing_borrower, $recall_item1->barcode );
+
+ # item-level and this item: renewal not allowed
+ my $recall = Koha::Recall->new({
+ biblio_id => $recall_item1->biblionumber,
+ item_id => $recall_item1->itemnumber,
+ patron_id => $recall_borrower->borrowernumber,
+ pickup_library_id => $recall_borrower->branchcode,
+ item_level => 1,
+ })->store;
+ ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $recall_item1->itemnumber );
+ is( $error, 'recalled', 'Cannot renew item that has been recalled' );
+ $recall->set_cancelled;
+
+ # biblio-level requested recall: renewal not allowed
+ $recall = Koha::Recall->new({
+ biblio_id => $recall_item1->biblionumber,
+ item_id => undef,
+ patron_id => $recall_borrower->borrowernumber,
+ pickup_library_id => $recall_borrower->branchcode,
+ item_level => 0,
+ })->store;
+ ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $recall_item1->itemnumber );
+ is( $error, 'recalled', 'Cannot renew item if biblio is recalled and has no item allocated' );
+ $recall->set_cancelled;
+
+ # item-level and not this item: renewal allowed
+ $recall = Koha::Recall->new({
+ biblio_id => $recall_item2->biblionumber,
+ item_id => $recall_item2->itemnumber,
+ patron_id => $recall_borrower->borrowernumber,
+ pickup_library_id => $recall_borrower->branchcode,
+ item_level => 1,
+ })->store;
+ ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $recall_item1->itemnumber );
+ is( $renewokay, 1, 'Can renew item if item-level recall on biblio is not on this item' );
+ $recall->set_cancelled;
+
+ # biblio-level waiting recall: renewal allowed
+ $recall = Koha::Recall->new({
+ biblio_id => $recall_item1->biblionumber,
+ item_id => undef,
+ patron_id => $recall_borrower->borrowernumber,
+ pickup_library_id => $recall_borrower->branchcode,
+ item_level => 0,
+ })->store;
+ $recall->set_waiting({ item => $recall_item1 });
+ ( $renewokay, $error ) = CanBookBeRenewed( $renewing_borrowernumber, $recall_item1->itemnumber );
+ is( $renewokay, 1, 'Can renew item if biblio-level recall has already been allocated an item' );
+ $recall->set_cancelled;
};
subtest "GetUpcomingDueIssues" => sub {
};
subtest "AllowRenewalIfOtherItemsAvailable tests" => sub {
- $dbh->do('DELETE FROM issues');
- $dbh->do('DELETE FROM items');
- $dbh->do('DELETE FROM circulation_rules');
+ plan tests => 13;
+ my $biblio = $builder->build_sample_biblio();
+ my $item_1 = $builder->build_sample_item(
+ {
+ biblionumber => $biblio->biblionumber,
+ library => $library2->{branchcode},
+ }
+ );
+ my $item_2= $builder->build_sample_item(
+ {
+ biblionumber => $biblio->biblionumber,
+ library => $library2->{branchcode},
+ itype => $item_1->effective_itemtype,
+ }
+ );
+
Koha::CirculationRules->set_rules(
{
categorycode => undef,
- itemtype => undef,
+ itemtype => $item_1->effective_itemtype,
branchcode => undef,
rules => {
reservesallowed => 25,
+ holds_per_record => 25,
issuelength => 14,
lengthunit => 'days',
renewalsallowed => 1,
}
}
);
- my $biblio = $builder->build_sample_biblio();
-
- my $item_1 = $builder->build_sample_item(
- {
- biblionumber => $biblio->biblionumber,
- library => $library2->{branchcode},
- itype => $itemtype,
- }
- );
- my $item_2= $builder->build_sample_item(
- {
- biblionumber => $biblio->biblionumber,
- library => $library2->{branchcode},
- itype => $itemtype,
- }
- );
my $borrowernumber1 = Koha::Patron->new({
firstname => 'Kyle',
categorycode => $patron_category->{categorycode},
branchcode => $library2->{branchcode},
})->store->borrowernumber;
+ my $patron_category_2 = $builder->build(
+ {
+ source => 'Category',
+ value => {
+ category_type => 'P',
+ enrolmentfee => 0,
+ BlockExpiredPatronOpacActions => -1, # Pick the pref value
+ }
+ }
+ );
+ my $borrowernumber3 = Koha::Patron->new({
+ firstname => 'Carnegie',
+ surname => 'Hall',
+ categorycode => $patron_category_2->{categorycode},
+ branchcode => $library2->{branchcode},
+ })->store->borrowernumber;
my $borrower1 = Koha::Patrons->find( $borrowernumber1 )->unblessed;
my $borrower2 = Koha::Patrons->find( $borrowernumber2 )->unblessed;
Koha::CirculationRules->set_rules(
{
categorycode => undef,
- itemtype => undef,
+ itemtype => $item_1->effective_itemtype,
branchcode => undef,
rules => {
onshelfholds => 0,
( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfholds are disabled' );
+ t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
+
Koha::CirculationRules->set_rules(
{
categorycode => undef,
- itemtype => undef,
+ itemtype => $item_1->effective_itemtype,
branchcode => undef,
rules => {
- onshelfholds => 0,
+ onshelfholds => 1,
}
}
);
+ t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
+
t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
- is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled and onshelfholds is disabled' );
+ is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
+
+ AddReserve(
+ {
+ branchcode => $library2->{branchcode},
+ borrowernumber => $borrowernumber3,
+ biblionumber => $biblio->biblionumber,
+ priority => 1,
+ }
+ );
+
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ is( $renewokay, 0, 'Verify the borrower cannot renew with 2 holds on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and one other item on record' );
+
+ my $item_3= $builder->build_sample_item(
+ {
+ biblionumber => $biblio->biblionumber,
+ library => $library2->{branchcode},
+ itype => $item_1->effective_itemtype,
+ }
+ );
+
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ is( $renewokay, 1, 'Verify the borrower cannot renew with 2 holds on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and two other items on record' );
Koha::CirculationRules->set_rules(
{
- categorycode => undef,
- itemtype => undef,
+ categorycode => $patron_category_2->{categorycode},
+ itemtype => $item_1->effective_itemtype,
branchcode => undef,
rules => {
- onshelfholds => 1,
+ reservesallowed => 0,
}
}
);
- t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 0 );
+
( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
- is( $renewokay, 0, 'Bug 14337 - Verify the borrower cannot renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is disabled and onshelfhold is enabled' );
+ is( $renewokay, 0, 'Verify the borrower cannot renew with 2 holds on the record, but only one of those holds can be filled when AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled and two other items on record' );
Koha::CirculationRules->set_rules(
{
- categorycode => undef,
- itemtype => undef,
+ categorycode => $patron_category_2->{categorycode},
+ itemtype => $item_1->effective_itemtype,
branchcode => undef,
rules => {
- onshelfholds => 1,
+ reservesallowed => 25,
}
}
);
- t::lib::Mocks::mock_preference( 'AllowRenewalIfOtherItemsAvailable', 1 );
- ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
- is( $renewokay, 1, 'Bug 14337 - Verify the borrower can renew with a hold on the record if AllowRenewalIfOtherItemsAvailable and onshelfhold are enabled' );
# Setting item not checked out to be not for loan but holdable
$item_2->notforloan(-1)->store;
( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
is( $renewokay, 0, 'Bug 14337 - Verify the borrower can not renew with a hold on the record if AllowRenewalIfOtherItemsAvailable is enabled but the only available item is notforloan' );
+
+ my $mock_circ = Test::MockModule->new("C4::Circulation");
+ $mock_circ->mock( CanItemBeReserved => sub {
+ warn "Checked";
+ return { status => 'no' }
+ } );
+
+ $item_2->notforloan(0)->store;
+ $item_3->delete();
+ # Two items total, one item available, one issued, two holds on record
+
+ warnings_are{
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ } [], "CanItemBeReserved not called when there are more possible holds than available items";
+ is( $renewokay, 0, 'Borrower cannot renew when there are more holds than available items' );
+
+ $item_3 = $builder->build_sample_item(
+ {
+ biblionumber => $biblio->biblionumber,
+ library => $library2->{branchcode},
+ itype => $item_1->effective_itemtype,
+ }
+ );
+
+ Koha::CirculationRules->set_rules(
+ {
+ categorycode => undef,
+ itemtype => $item_1->effective_itemtype,
+ branchcode => undef,
+ rules => {
+ reservesallowed => 0,
+ }
+ }
+ );
+
+ warnings_are{
+ ( $renewokay, $error ) = CanBookBeRenewed( $borrowernumber1, $item_1->itemnumber );
+ } ["Checked","Checked"], "CanItemBeReserved only called once per available item if it returns a negative result for all items for a borrower";
+ is( $renewokay, 0, 'Borrower cannot renew when there are more holds than available items' );
+
};
{
holdingbranch => $holdingbranch->{branchcode},
}
);
+ Koha::CirculationRules->set_rules(
+ {
+ categorycode => undef,
+ itemtype => $item->effective_itemtype,
+ branchcode => undef,
+ rules => {
+ reservesallowed => 25,
+ issuelength => 14,
+ lengthunit => 'days',
+ renewalsallowed => 1,
+ renewalperiod => 7,
+ norenewalbefore => undef,
+ auto_renew => 0,
+ fine => .10,
+ chargeperiod => 1,
+ maxissueqty => 20
+ }
+ }
+ );
set_userenv($holdingbranch);
# TODO t::lib::Mocks::mock_preference('AllowReturnToBranch', 'homeorholdingbranch');
};
+subtest 'AddIssue | recalls' => sub {
+ plan tests => 3;
+
+ t::lib::Mocks::mock_preference("UseRecalls", 1);
+ t::lib::Mocks::mock_preference("item-level_itypes", 1);
+ my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $item = $builder->build_sample_item;
+ Koha::CirculationRules->set_rules({
+ branchcode => undef,
+ itemtype => undef,
+ categorycode => undef,
+ rules => {
+ recalls_allowed => 10,
+ },
+ });
+
+ # checking out item that they have recalled
+ my $recall1 = Koha::Recall->new(
+ { patron_id => $patron1->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => $item->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron1->branchcode,
+ }
+ )->store;
+ AddIssue( $patron1->unblessed, $item->barcode, undef, undef, undef, undef, { recall_id => $recall1->id } );
+ $recall1 = Koha::Recalls->find( $recall1->id );
+ is( $recall1->fulfilled, 1, 'Recall was fulfilled when patron checked out item' );
+ AddReturn( $item->barcode, $item->homebranch );
+
+ # this item is has a recall request. cancel recall
+ my $recall2 = Koha::Recall->new(
+ { patron_id => $patron2->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => $item->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron2->branchcode,
+ }
+ )->store;
+ AddIssue( $patron1->unblessed, $item->barcode, undef, undef, undef, undef, { recall_id => $recall2->id, cancel_recall => 'cancel' } );
+ $recall2 = Koha::Recalls->find( $recall2->id );
+ is( $recall2->cancelled, 1, 'Recall was cancelled when patron checked out item' );
+ AddReturn( $item->barcode, $item->homebranch );
+
+ # this item is waiting to fulfill a recall. revert recall
+ my $recall3 = Koha::Recall->new(
+ { patron_id => $patron2->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => $item->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron2->branchcode,
+ }
+ )->store;
+ $recall3->set_waiting;
+ AddIssue( $patron1->unblessed, $item->barcode, undef, undef, undef, undef, { recall_id => $recall3->id, cancel_recall => 'revert' } );
+ $recall3 = Koha::Recalls->find( $recall3->id );
+ is( $recall3->requested, 1, 'Recall was reverted from waiting when patron checked out item' );
+ AddReturn( $item->barcode, $item->homebranch );
+};
+
+subtest 'AddIssue & illrequests.due_date' => sub {
+ plan tests => 2;
+
+ t::lib::Mocks::mock_preference( 'ILLModule', 1 );
+ my $library = $builder->build( { source => 'Branch' } );
+ my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
+ my $item = $builder->build_sample_item();
+
+ set_userenv($library);
+
+ my $custom_date_due = '9999-12-18 12:34:56';
+ my $expected_date_due = '9999-12-18 23:59:00';
+ my $illrequest = Koha::Illrequest->new({
+ borrowernumber => $patron->borrowernumber,
+ biblio_id => $item->biblionumber,
+ branchcode => $library->{'branchcode'},
+ due_date => $custom_date_due,
+ })->store;
+
+ my $issue = AddIssue( $patron->unblessed, $item->barcode );
+ is( $issue->date_due, $expected_date_due, 'Custom illrequest date due has been set for this issue');
+
+ $patron = $builder->build_object( { class => 'Koha::Patrons' } );
+ $item = $builder->build_sample_item();
+ $custom_date_due = '9999-12-19';
+ $expected_date_due = '9999-12-19 23:59:00';
+ $illrequest = Koha::Illrequest->new({
+ borrowernumber => $patron->borrowernumber,
+ biblio_id => $item->biblionumber,
+ branchcode => $library->{'branchcode'},
+ due_date => $custom_date_due,
+ })->store;
+
+ $issue = AddIssue( $patron->unblessed, $item->barcode );
+ is( $issue->date_due, $expected_date_due, 'Custom illrequest date due has been set for this issue');
+};
+
subtest 'CanBookBeIssued + Koha::Patron->is_debarred|has_overdues' => sub {
plan tests => 8;
library => $library->{branchcode},
}
);
+ Koha::CirculationRules->set_rules(
+ {
+ categorycode => undef,
+ itemtype => undef,
+ branchcode => $library->{branchcode},
+ rules => {
+ reservesallowed => 25,
+ issuelength => 14,
+ lengthunit => 'days',
+ renewalsallowed => 1,
+ renewalperiod => 7,
+ norenewalbefore => undef,
+ auto_renew => 0,
+ fine => .10,
+ chargeperiod => 1,
+ maxissueqty => 20
+ }
+ }
+ );
+
my ( $error, $question, $alerts );
}
);
+ Koha::CirculationRules->set_rules(
+ {
+ categorycode => undef,
+ itemtype => undef,
+ branchcode => $library->{branchcode},
+ rules => {
+ reservesallowed => 25,
+ issuelength => 14,
+ lengthunit => 'days',
+ renewalsallowed => 1,
+ renewalperiod => 7,
+ norenewalbefore => undef,
+ auto_renew => 0,
+ fine => .10,
+ chargeperiod => 1,
+ maxissueqty => 20
+ }
+ }
+ );
+
my ( $error, $question, $alerts );
my $issue = AddIssue( $patron->unblessed, $item_1->barcode, dt_from_string->add( days => 1 ) );
}
);
- my $now = dt_from_string;
- my $five_days_go = output_pref({ dt => $now->clone->add( days => 5 ), dateonly => 1});
- my $ten_days_go = output_pref({ dt => $now->clone->add( days => 10), dateonly => 1 });
+ my $now = dt_from_string()->truncate( to => 'day' );
+ my $five_days_go = $now->clone->add( days => 5 );
+ my $ten_days_go = $now->clone->add( days => 10);
my $library = $builder->build( { source => 'Branch' } );
my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { categorycode => $patron_category->{categorycode} } } );
my $issue = AddIssue( $patron->unblessed, $item->barcode, $five_days_go ); # date due was 10d ago
my $actualissue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
- is( output_pref({ str => $actualissue->date_due, dateonly => 1}), $five_days_go, "First issue works");
+ is( output_pref({ str => $actualissue->date_due, dateonly => 1}), output_pref({ str => $five_days_go, dateonly => 1}), "First issue works");
my ($issuingimpossible, $needsconfirmation) = CanBookBeIssued($patron,$item->barcode,$ten_days_go, undef, undef, undef);
is( $needsconfirmation->{RENEW_ISSUE}, 1, "This is a renewal");
is( $needsconfirmation->{TOO_MANY}, undef, "Not too many, is a renewal");
}
});
my $future = dt_from_string->add( days => 1 );
- my $deny_issue = $builder->build_object({ class => 'Koha::Checkouts', value => {
- returndate => undef,
- renewals => 0,
- auto_renew => 0,
- borrowernumber => $idr_borrower->borrowernumber,
- itemnumber => $deny_book->itemnumber,
- onsite_checkout => 0,
- date_due => $future,
+ my $deny_issue = $builder->build_object(
+ {
+ class => 'Koha::Checkouts',
+ value => {
+ returndate => undef,
+ renewals_count => 0,
+ auto_renew => 0,
+ borrowernumber => $idr_borrower->borrowernumber,
+ itemnumber => $deny_book->itemnumber,
+ onsite_checkout => 0,
+ date_due => $future,
+ }
}
- });
- my $allow_issue = $builder->build_object({ class => 'Koha::Checkouts', value => {
- returndate => undef,
- renewals => 0,
- auto_renew => 0,
- borrowernumber => $idr_borrower->borrowernumber,
- itemnumber => $allow_book->itemnumber,
- onsite_checkout => 0,
- date_due => $future,
+ );
+ my $allow_issue = $builder->build_object(
+ {
+ class => 'Koha::Checkouts',
+ value => {
+ returndate => undef,
+ renewals_count => 0,
+ auto_renew => 0,
+ borrowernumber => $idr_borrower->borrowernumber,
+ itemnumber => $allow_book->itemnumber,
+ onsite_checkout => 0,
+ date_due => $future,
+ }
}
- });
+ );
my $idr_rules;
# TODO test with AllowNotForLoanOverride = 1
};
+subtest 'CanBookBeIssued | recalls' => sub {
+ plan tests => 3;
+
+ t::lib::Mocks::mock_preference("UseRecalls", 1);
+ t::lib::Mocks::mock_preference("item-level_itypes", 1);
+ my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $item = $builder->build_sample_item;
+ Koha::CirculationRules->set_rules({
+ branchcode => undef,
+ itemtype => undef,
+ categorycode => undef,
+ rules => {
+ recalls_allowed => 10,
+ },
+ });
+
+ # item-level recall
+ my $recall = Koha::Recall->new(
+ { patron_id => $patron1->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => $item->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron1->branchcode,
+ }
+ )->store;
+
+ my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron2, $item->barcode, undef, undef, undef, undef );
+ is( $needsconfirmation->{RECALLED}->id, $recall->id, "Another patron has placed an item-level recall on this item" );
+
+ $recall->set_cancelled;
+
+ # biblio-level recall
+ $recall = Koha::Recall->new(
+ { patron_id => $patron1->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => undef,
+ item_level => 0,
+ pickup_library_id => $patron1->branchcode,
+ }
+ )->store;
+
+ ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron2, $item->barcode, undef, undef, undef, undef );
+ is( $needsconfirmation->{RECALLED}->id, $recall->id, "Another patron has placed a biblio-level recall and this item is eligible to fill it" );
+
+ $recall->set_cancelled;
+
+ # biblio-level recall
+ $recall = Koha::Recall->new(
+ { patron_id => $patron1->borrowernumber,
+ biblio_id => $item->biblionumber,
+ item_id => undef,
+ item_level => 0,
+ pickup_library_id => $patron1->branchcode,
+ }
+ )->store;
+ $recall->set_waiting( { item => $item, expirationdate => dt_from_string() } );
+
+ my ( undef, undef, undef, $messages ) = CanBookBeIssued( $patron1, $item->barcode, undef, undef, undef, undef );
+ is( $messages->{RECALLED}, $recall->id, "This book can be issued by this patron and they have placed a recall" );
+
+ $recall->set_cancelled;
+};
+
subtest 'AddReturn should clear items.onloan for unissued items' => sub {
plan tests => 1;
is( $item->onloan, undef, 'AddReturn did clear items.onloan' );
};
+subtest 'AddReturn | recalls' => sub {
+ plan tests => 3;
+
+ t::lib::Mocks::mock_preference("UseRecalls", 1);
+ t::lib::Mocks::mock_preference("item-level_itypes", 1);
+ my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $patron2 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $item1 = $builder->build_sample_item;
+ Koha::CirculationRules->set_rules({
+ branchcode => undef,
+ itemtype => undef,
+ categorycode => undef,
+ rules => {
+ recalls_allowed => 10,
+ },
+ });
+
+ # this item can fill a recall with pickup at this branch
+ AddIssue( $patron1->unblessed, $item1->barcode );
+ my $recall1 = Koha::Recall->new(
+ { patron_id => $patron2->borrowernumber,
+ biblio_id => $item1->biblionumber,
+ item_id => $item1->itemnumber,
+ item_level => 1,
+ pickup_library_id => $item1->homebranch,
+ }
+ )->store;
+ my ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $item1->homebranch );
+ is( $messages->{RecallFound}->id, $recall1->id, "Recall found" );
+ $recall1->set_cancelled;
+
+ # this item can fill a recall but needs transfer
+ AddIssue( $patron1->unblessed, $item1->barcode );
+ $recall1 = Koha::Recall->new(
+ { patron_id => $patron2->borrowernumber,
+ biblio_id => $item1->biblionumber,
+ item_id => $item1->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron2->branchcode,
+ }
+ )->store;
+ ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $item1->homebranch );
+ is( $messages->{RecallNeedsTransfer}, $item1->homebranch, "Recall requiring transfer found" );
+ $recall1->set_cancelled;
+
+ # this item is already in transit, do not ask to transfer
+ AddIssue( $patron1->unblessed, $item1->barcode );
+ $recall1 = Koha::Recall->new(
+ { patron_id => $patron2->borrowernumber,
+ biblio_id => $item1->biblionumber,
+ item_id => $item1->itemnumber,
+ item_level => 1,
+ pickup_library_id => $patron2->branchcode,
+ }
+ )->store;
+ $recall1->start_transfer;
+ ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $item1->barcode, $patron2->branchcode );
+ is( $messages->{TransferredRecall}->id, $recall1->id, "In transit recall found" );
+ $recall1->set_cancelled;
+};
+
+subtest 'AddReturn | bundles' => sub {
+ plan tests => 1;
+
+ my $schema = Koha::Database->schema;
+ $schema->storage->txn_begin;
+
+ my $patron1 = $builder->build_object({ class => 'Koha::Patrons' });
+ my $host_item1 = $builder->build_sample_item;
+ my $bundle_item1 = $builder->build_sample_item;
+ $schema->resultset('ItemBundle')
+ ->create(
+ { host => $host_item1->itemnumber, item => $bundle_item1->itemnumber } );
+
+ my ( $doreturn, $messages, $iteminfo, $borrowerinfo ) = AddReturn( $bundle_item1->barcode, $bundle_item1->homebranch );
+ is($messages->{InBundle}->id, $host_item1->id, 'AddReturn returns InBundle host item when item is part of a bundle');
+
+ $schema->storage->txn_rollback;
+};
subtest 'AddRenewal and AddIssuingCharge tests' => sub {
};
+subtest 'AddRenewal() adds to renewals' => sub {
+ plan tests => 4;
+
+ my $library = $builder->build_object({ class => 'Koha::Libraries' });
+ my $patron = $builder->build_object({
+ class => 'Koha::Patrons',
+ value => { branchcode => $library->id }
+ });
+
+ my $item = $builder->build_sample_item();
+
+ set_userenv( $library->unblessed );
+
+ # Check the item out
+ my $issue = AddIssue( $patron->unblessed, $item->barcode );
+ is(ref($issue), 'Koha::Checkout', 'Issue added');
+
+ # Renew item
+ my $duedate = AddRenewal( $patron->id, $item->id, $library->id );
+
+ ok( $duedate, "Renewal added" );
+
+ my $renewals = Koha::Checkouts::Renewals->search({ checkout_id => $issue->issue_id });
+ is($renewals->count, 1, 'One renewal added');
+ my $THE_renewal = $renewals->next;
+ is( $THE_renewal->renewer_id, C4::Context->userenv->{'number'}, 'Renewer recorded from context' );
+};
+
subtest 'ProcessOfflinePayment() tests' => sub {
plan tests => 4;
};
subtest "SendCirculationAlert" => sub {
- plan tests => 2;
+ plan tests => 3;
+
+ # When you would unsuspectingly call this unit test (with perl, not prove), you will be bitten by LOCK.
+ # LOCK will commit changes and ruin your data
+ # In order to prevent that, we will add KOHA_TESTING to $ENV; see further Circulation.pm
+ $ENV{KOHA_TESTING} = 1;
# Setup branch, borrowr, and notice
my $library = $builder->build_object({ class => 'Koha::Libraries' });
my $patron = $builder->build_object({ class => 'Koha::Patrons' });
C4::Members::Messaging::SetMessagingPreference({
borrowernumber => $patron->id,
- message_transport_types => ['email'],
+ message_transport_types => ['sms'],
message_attribute_id => 5
});
my $item = $builder->build_sample_item();
name => 'Test Checkin',
is_html => 0,
content => "Checkins:\n----\n[% biblio.title %]-[% old_checkout.issue_id %]\n----Thank you.",
- message_transport_type => 'email',
+ message_transport_type => 'sms',
lang => 'default'
}
})->store;
});
my $notice = Koha::Notice::Messages->find({ borrowernumber => $patron->id, letter_code => 'CHECKIN' });
is($notice->content,"Checkins:\n".$item->biblio->title."-".$issue_1->id."\nThank you.", 'Letter generated with expected output on first checkin' );
+ is($notice->to_address, $patron->smsalertnumber, "Letter has the correct to_address set to smsalertnumber for SMS type notices");
# Checkout an item, mark it returned, generate a notice
my $issue_2 = AddIssue( $patron->unblessed, $item->barcode);
};
+subtest "GetSoonestRenewDate tests" => sub {
+ plan tests => 5;
+ Koha::CirculationRules->set_rule(
+ {
+ categorycode => undef,
+ branchcode => undef,
+ itemtype => undef,
+ rule_name => 'norenewalbefore',
+ rule_value => '7',
+ }
+ );
+ my $patron = $builder->build_object({ class => 'Koha::Patrons' });
+ my $item = $builder->build_sample_item();
+ my $issue = AddIssue( $patron->unblessed, $item->barcode);
+ my $datedue = dt_from_string( $issue->date_due() );
+
+ # Bug 14395
+ # Test 'exact time' setting for syspref NoRenewalBeforePrecision
+ t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact_time' );
+ is(
+ GetSoonestRenewDate( $patron->id, $item->itemnumber ),
+ $datedue->clone->add( days => -7 ),
+ 'Bug 14395: Renewals permitted 7 days before due date, as expected'
+ );
+
+ # Bug 14395
+ # Test 'date' setting for syspref NoRenewalBeforePrecision
+ t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
+ is(
+ GetSoonestRenewDate( $patron->id, $item->itemnumber ),
+ $datedue->clone->add( days => -7 )->truncate( to => 'day' ),
+ 'Bug 14395: Renewals permitted 7 days before due date, as expected'
+ );
+
+
+ Koha::CirculationRules->set_rule(
+ {
+ categorycode => undef,
+ branchcode => undef,
+ itemtype => undef,
+ rule_name => 'norenewalbefore',
+ rule_value => undef,
+ }
+ );
+
+ is(
+ GetSoonestRenewDate( $patron->id, $item->itemnumber ),
+ dt_from_string,
+ 'Checkouts without auto-renewal can be renewed immediately if no norenewalbefore'
+ );
+
+ t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'date' );
+ $issue->auto_renew(1)->store;
+ is(
+ GetSoonestRenewDate( $patron->id, $item->itemnumber ),
+ $datedue->clone->truncate( to => 'day' ),
+ 'Checkouts with auto-renewal can be renewed earliest on due date if no renewalbefore'
+ );
+ t::lib::Mocks::mock_preference( 'NoRenewalBeforePrecision', 'exact' );
+ is(
+ GetSoonestRenewDate( $patron->id, $item->itemnumber ),
+ $datedue,
+ 'Checkouts with auto-renewal can be renewed earliest on due date if no renewalbefore'
+ );
+};
+
$schema->storage->txn_rollback;
C4::Context->clear_syspref_cache();
$branches = Koha::Libraries->search();