Koha-clms/t/db_dependent/Koha/Library/FloatLimits.t

460 lines
15 KiB
Perl
Executable File

#!/usr/bin/perl
# Copyright 2023 ByWater Solutions
#
# This file is part of Koha
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <https://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 7;
use Test::NoWarnings;
use Koha::Database;
use C4::Circulation qw(CreateBranchTransferLimit);
use Koha::DateUtils qw( dt_from_string );
use Koha::Library::FloatLimit;
use Koha::Library::FloatLimits;
use t::lib::Mocks;
use t::lib::TestBuilder;
my $schema = Koha::Database->new->schema;
$schema->storage->txn_begin;
my $builder = t::lib::TestBuilder->new;
my $library1 = $builder->build( { source => 'Branch' } );
my $library2 = $builder->build( { source => 'Branch' } );
my $library3 = $builder->build( { source => 'Branch' } );
my $itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
my $biblio = $builder->build_sample_biblio();
# Create initial items for testing
for ( 1 .. 5 ) {
$builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library1->{branchcode},
holdingbranch => $library1->{branchcode},
itype => $itemtype->itemtype,
}
);
}
for ( 1 .. 10 ) {
$builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library2->{branchcode},
holdingbranch => $library2->{branchcode},
itype => $itemtype->itemtype,
}
);
}
for ( 1 .. 15 ) {
$builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library3->{branchcode},
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
}
);
}
my $item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library1->{branchcode},
holdingbranch => $library1->{branchcode},
itype => $itemtype->itemtype,
}
);
my $limit1 = Koha::Library::FloatLimit->new(
{
branchcode => $library1->{branchcode},
itemtype => $itemtype->itemtype,
float_limit => 1,
}
)->store();
my $float_limit2 = Koha::Library::FloatLimit->new(
{
branchcode => $library2->{branchcode},
itemtype => $itemtype->itemtype,
float_limit => 100,
}
)->store();
my $float_limit3 = Koha::Library::FloatLimit->new(
{
branchcode => $library3->{branchcode},
itemtype => $itemtype->itemtype,
float_limit => 1000,
}
)->store();
is(
Koha::Library::FloatLimits->lowest_ratio_library( $item, $library1->{branchcode}, $item->holdingbranch )
->branchcode,
$library3->{branchcode},
"Correct library selected for float limit transfer"
);
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '1' );
t::lib::Mocks::mock_preference( 'BranchTransferLimitsType', 'itemtype' );
my $to = $library3->{branchcode};
my $from = $item->holdingbranch;
my $code = $itemtype->itemtype;
CreateBranchTransferLimit( $to, $from, $code );
is( C4::Circulation::IsBranchTransferAllowed( $to, $from, $code ), 0, "Transfer to best library is no longer allowed" );
subtest 'FloatLimits: General tests' => sub {
$schema->storage->txn_begin;
plan tests => 2;
# Test with no float limits defined
my $no_limits_item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library1->{branchcode},
holdingbranch => $library1->{branchcode},
itype => $builder->build_object( { class => 'Koha::ItemTypes' } )->itemtype,
}
);
my $no_limits_result = Koha::Library::FloatLimits->lowest_ratio_library(
$no_limits_item, $library1->{branchcode},
$no_limits_item->holdingbranch
);
is( $no_limits_result, undef, "Returns undef when no float limits defined" );
# Test with only zero float limits
my $zero_lib = $builder->build( { source => 'Branch' } );
my $zero_itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
Koha::Library::FloatLimit->new(
{
branchcode => $zero_lib->{branchcode},
itemtype => $zero_itemtype->itemtype,
float_limit => 0, # Zero limit should be excluded
}
)->store();
# Test with item type not in any float limits
my $unknown_itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
my $unknown_item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library1->{branchcode},
holdingbranch => $library1->{branchcode},
itype => $unknown_itemtype->itemtype,
}
);
my $unknown_result = Koha::Library::FloatLimits->lowest_ratio_library(
$unknown_item, $library1->{branchcode},
$unknown_item->holdingbranch
);
is( $unknown_result, undef, "Returns undef for item type not in float limits" );
$schema->storage->txn_rollback;
};
subtest 'FloatLimits: Count on loan items against total' => sub {
$schema->storage->txn_begin;
plan tests => 5;
# Reset transfer limits for clean test
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
# Verify initial item counts
my $library2_initial = Koha::Items->search(
{
holdingbranch => $library2->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
my $library3_initial = Koha::Items->search(
{
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
is( $library2_initial, 10, "Library2 initially has 10 available items" );
is( $library3_initial, 15, "Library3 initially has 15 available items" );
# Initial library selection based on ratios
is(
Koha::Library::FloatLimits->lowest_ratio_library( $item, $library1->{branchcode} )->branchcode,
$library3->{branchcode},
"Library3 initially selected (lowest ratio: 15/1000 = 0.015)"
);
# Check out enough items from library3 to change the selection
my @library3_items = Koha::Items->search(
{
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->as_list;
my $checkout_date = dt_from_string();
for my $i ( 0 .. 9 ) {
$library3_items[$i]->onloan($checkout_date)->store;
}
# Verify the count decreased
my $library3_after_checkout = Koha::Items->search(
{
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
is( $library3_after_checkout, 5, "Library3 now has 5 available items after 10 checkouts" );
# Library3 should still be selected
is(
Koha::Library::FloatLimits->lowest_ratio_library( $item, $library1->{branchcode} )->branchcode,
$library3->{branchcode},
"Library3 still selected after checkouts (ratio now 5/1000 = 0.005, still better than library2's 10/100 = 0.1)"
);
$schema->storage->txn_rollback;
};
subtest 'FloatLimits: Count in transit TO items against total' => sub {
$schema->storage->txn_begin;
plan tests => 6;
# Reset transfer limits for clean test
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
Koha::Items->search(
{
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
onloan => { '!=' => undef }
}
)->update( { onloan => undef } );
# Verify initial counts
my $library2_initial = Koha::Items->search(
{
holdingbranch => $library2->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
my $library3_initial = Koha::Items->search(
{
holdingbranch => $library3->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
is( $library2_initial, 10, "Library2 initially has 10 available items" );
is( $library3_initial, 15, "Library3 initially has 15 available items" );
# Initial selection should be library3
is(
Koha::Library::FloatLimits->lowest_ratio_library( $item, $library1->{branchcode} )->branchcode,
$library3->{branchcode},
"Library3 initially selected (ratio: 15/1000 = 0.015)"
);
# Create items in transit TO library2
my @transit_items;
for ( 1 .. 2 ) {
my $transit_item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $library1->{branchcode},
holdingbranch => $library1->{branchcode},
itype => $itemtype->itemtype,
}
);
push @transit_items, $transit_item;
}
# Create active transfers to library2
my $transfer_date = dt_from_string();
for my $transit_item (@transit_items) {
Koha::Item::Transfer->new(
{
itemnumber => $transit_item->itemnumber,
frombranch => $library1->{branchcode},
tobranch => $library2->{branchcode},
daterequested => $transfer_date,
datesent => $transfer_date,
reason => 'LibraryFloatLimit'
}
)->store;
}
# Verify that items in transit are counted toward destination
my $in_transit_count = Koha::Items->search(
{
itype => $itemtype->itemtype,
},
{
join => 'branchtransfers',
where => {
'branchtransfers.tobranch' => $library2->{branchcode},
'branchtransfers.datearrived' => undef,
'branchtransfers.datecancelled' => undef,
},
distinct => 1
}
)->count;
is( $in_transit_count, 2, "2 items are in transit to library2" );
my $library2_physical = Koha::Items->search(
{
holdingbranch => $library2->{branchcode},
itype => $itemtype->itemtype,
onloan => undef
}
)->count;
is( $library2_physical, 10, "Library2 still has 10 physical items" );
is(
Koha::Library::FloatLimits->lowest_ratio_library( $item, $library1->{branchcode} )->branchcode,
$library3->{branchcode},
"Library3 still selected after items in transit to library2"
);
$schema->storage->txn_rollback;
};
subtest 'FloatLimits: Do not count in transit FROM items against total' => sub {
$schema->storage->txn_begin;
plan tests => 2;
# Reset transfer limits
t::lib::Mocks::mock_preference( 'UseBranchTransferLimits', '0' );
# Create unique itemtype for this test
my $transit_itemtype = $builder->build_object( { class => 'Koha::ItemTypes' } );
# Create test libraries
my $from_lib = $builder->build( { source => 'Branch' } );
my $to_lib = $builder->build( { source => 'Branch' } );
# Create items at from_lib
my @from_items;
for ( 1 .. 8 ) {
my $from_item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $from_lib->{branchcode},
holdingbranch => $from_lib->{branchcode},
itype => $transit_itemtype->itemtype, # Use unique itemtype
}
);
push @from_items, $from_item;
}
# Create items at to_lib
for ( 1 .. 5 ) {
$builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $to_lib->{branchcode},
holdingbranch => $to_lib->{branchcode},
itype => $transit_itemtype->itemtype, # Use unique itemtype
}
);
}
# Set float limits
Koha::Library::FloatLimit->new(
{
branchcode => $from_lib->{branchcode},
itemtype => $transit_itemtype->itemtype, # Use unique itemtype
float_limit => 10,
}
)->store();
Koha::Library::FloatLimit->new(
{
branchcode => $to_lib->{branchcode},
itemtype => $transit_itemtype->itemtype, # Use unique itemtype
float_limit => 10,
}
)->store();
# Create test item with unique itemtype
my $transit_item = $builder->build_sample_item(
{
biblionumber => $biblio->biblionumber,
homebranch => $from_lib->{branchcode},
holdingbranch => $from_lib->{branchcode},
itype => $transit_itemtype->itemtype,
}
);
# Initial state: from_lib has 8 items, to_lib has 5 items
# Ratios: from_lib = 8/10 = 0.8, to_lib = 5/10 = 0.5
my $initial_result = Koha::Library::FloatLimits->lowest_ratio_library( $transit_item, $from_lib->{branchcode} );
is( $initial_result->branchcode, $to_lib->{branchcode}, "to_lib selected initially (better ratio)" );
# Create transfers FROM from_lib TO to_lib (3 items)
my $transfer_date = dt_from_string();
for my $i ( 0 .. 2 ) {
Koha::Item::Transfer->new(
{
itemnumber => $from_items[$i]->itemnumber,
frombranch => $from_lib->{branchcode},
tobranch => $to_lib->{branchcode},
daterequested => $transfer_date,
datesent => $transfer_date,
reason => 'LibraryFloatLimit'
}
)->store;
}
# After transfers:
# from_lib effective count = 8 - 3 = 5 (ratio = 5/10 = 0.5)
# to_lib effective count = 5 + 3 = 8 (ratio = 8/10 = 0.8)
# Now from_lib should be better choice
my $after_transfer_result =
Koha::Library::FloatLimits->lowest_ratio_library( $transit_item, $to_lib->{branchcode} );
is(
$after_transfer_result->branchcode, $from_lib->{branchcode},
"from_lib selected after transfers (items subtracted)"
);
$schema->storage->txn_rollback;
};
$schema->storage->txn_rollback;