Bug 41886: Filter checkouts to bookable items in check_booking

- Biblio::check_booking counts current checkouts toward the
  unavailable pool to detect booking clashes
- It counts checkouts on ALL items for the biblio, including
  non-bookable ones (e.g. items with an excluded item type)
- When only a subset of items are bookable, checkouts on
  non-bookable siblings incorrectly reduce the available pool,
  producing false clashes
- Restrict the checkout query to only count items whose
  itemnumber is in the bookable items set

To test:
1. Have a biblio with at least two items, where one item is
   bookable and the other is not (bookable = 0).
2. Check out the non-bookable item to a patron.
3. Attempt to create a booking for the bookable item.
4. Without the patch: a clash is detected even though the
   bookable item is available.
5. Apply the patch.
6. Repeat steps 2-3.
7. Verify the booking succeeds without a false clash.
8. Run: prove t/db_dependent/Koha/Biblio.t
   and confirm the new subtest
   "checkouts on non-bookable items do not cause false clashes"
   passes.

Sponsored-by: Büchereizentrale Schleswig-Holstein <https://www.bz-sh.de/>
Signed-off-by: David Nind <david@davidnind.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Laura Escamilla <laura.escamilla@bywatersolutions.com>
This commit is contained in:
Paul Derscheid 2026-02-19 10:37:38 +01:00 committed by Laura Escamilla
parent 42908f8c68
commit e4be46c2c5
No known key found for this signature in database
GPG Key ID: 6B1D9CA1AF817BC3
2 changed files with 53 additions and 3 deletions

View File

@ -394,10 +394,17 @@ sub check_booking {
? $existing_bookings->search( { booking_id => { '!=' => $booking_id } } )->count
: $existing_bookings->count;
# Only count checkouts on bookable items; checkouts on non-bookable
# sibling items should not reduce the pool of available bookable items.
my $bookable_item_ids = [ $bookable_items->reset->get_column('itemnumber') ];
my $checkouts = $self->current_checkouts->search(
{
date_due => { '>=' => $dtf->format_datetime($start_date) },
"me.itemnumber" => { '-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query }
date_due => { '>=' => $dtf->format_datetime($start_date) },
"me.itemnumber" => {
'-not_in' => $existing_bookings->_resultset->get_column('item_id')->as_query,
'-in' => $bookable_item_ids,
},
}
);
$booked_count += $checkouts->count;

View File

@ -2214,7 +2214,7 @@ sub host_record {
}
subtest 'check_booking tests' => sub {
plan tests => 6;
plan tests => 7;
$schema->storage->txn_begin;
@ -2396,5 +2396,48 @@ subtest 'check_booking tests' => sub {
is( $check_booking, 1, "Koha::Biblio->check_booking returns true when we can book on an item" );
subtest 'checkouts on non-bookable items do not cause false clashes' => sub {
plan tests => 2;
my $nb_biblio = $builder->build_sample_biblio();
my $bookable_item = $builder->build_sample_item( { biblionumber => $nb_biblio->biblionumber, bookable => 1 } );
my $non_bookable_item =
$builder->build_sample_item( { biblionumber => $nb_biblio->biblionumber, bookable => 0 } );
my $nb_patron = $builder->build_object( { class => 'Koha::Patrons' } );
# Check out the non-bookable item
AddIssue( $nb_patron, $non_bookable_item->barcode );
my $nb_start = dt_from_string()->truncate( to => 'day' );
my $nb_end = $nb_start->clone->add( days => 7 );
my $can_book = $nb_biblio->check_booking(
{
start_date => $nb_start,
end_date => $nb_end,
}
);
is(
$can_book, 1,
"Checkout on non-bookable item does not reduce availability"
);
# Now also check out the bookable item to confirm it
# correctly detects unavailability
AddIssue( $nb_patron, $bookable_item->barcode );
$can_book = $nb_biblio->check_booking(
{
start_date => $nb_start,
end_date => $nb_end,
}
);
is(
$can_book, 0,
"Checkout on bookable item correctly reduces availability"
);
};
$schema->storage->txn_rollback;
};