Bug 42997: Sync booking end_date with due date override at checkout

When a booked item is checked out, the due date defaults to the booking
end_date. Staff may override the due date at checkout time, which
previously left the booking end_date out of sync with the checkout,
blocking the booked period for longer or shorter than the actual loan.

This patch updates C4::Circulation::AddIssue to sync any due date
override back to booking.end_date the moment the booking transitions to
'issued', keeping the booking and the checkout aligned from the point
of issue. The existing Koha::Booking->store clash detection is used to
guard the update: if the overridden due date would clash with a
subsequent booking for the same item, the checkout proceeds with the
booking's original end_date instead.

Test plan:
1) Run t/db_dependent/Circulation.t and confirm the new
   'AddIssue | booking end_date sync with due date override' subtest
   passes
2) Create a booking for a patron on a specific item
3) Check the item out to the patron, overriding the suggested due date
4) Confirm the booking's end_date now matches the overridden due date

Sponsored-by: OpenFifth <https://openfifth.co.uk/\>
Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/\>
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Martin Renvoize 2026-07-06 08:18:38 +01:00 committed by Pedro Amorim
parent 3ec3c465ba
commit 67e0051e1f
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
2 changed files with 114 additions and 2 deletions

View File

@ -1856,7 +1856,27 @@ sub AddIssue {
if ( $booking->patron_id == $patron->borrowernumber ) {
# Patron's own booking - mark as issued and link checkout to booking
$booking->status('issued')->store;
$booking->status('issued');
# Sync any due date override back to the booking end_date so the
# booking and checkout agree from the moment of issue
my $booking_end = dt_from_string( $booking->end_date );
$booking->end_date($datedue) if $datedue->compare($booking_end) != 0;
try {
$booking->store;
} catch {
my $error = $_;
if ( blessed $error && $error->isa('Koha::Exceptions::Booking::Clash') ) {
# The overridden due date would clash with a subsequent
# booking; proceed with the booking's original end_date
$booking->discard_changes;
$booking->status('issued')->store;
} else {
die $error;
}
};
$issue_attributes->{'booking_id'} = $booking->booking_id;
} else {

View File

@ -19,7 +19,7 @@ use Modern::Perl;
use utf8;
use Test::NoWarnings;
use Test::More tests => 87;
use Test::More tests => 88;
use Test::Exception;
use Test::MockModule;
use Test::Deep qw( cmp_deeply );
@ -7439,6 +7439,98 @@ subtest 'AddIssue/AddReturn | booking status handling' => sub {
$schema->storage->txn_rollback;
};
subtest 'AddIssue | booking end_date sync with due date override' => sub {
plan tests => 5;
my $schema = Koha::Database->schema;
$schema->storage->txn_begin;
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
my $patron2 = $builder->build_object( { class => 'Koha::Patrons' } );
my $pickup_library = $builder->build_object( { class => 'Koha::Libraries' } );
my $item = $builder->build_sample_item( { bookable => 1 } );
# Case 1: Due date matches booking end_date - no sync needed
my $end_date = dt_from_string()->add( days => 7 )->truncate( to => 'minute' );
my $booking = Koha::Booking->new(
{
patron_id => $patron->borrowernumber,
pickup_library_id => $pickup_library->branchcode,
item_id => $item->itemnumber,
biblio_id => $item->biblio->biblionumber,
start_date => dt_from_string(),
end_date => $end_date,
}
)->store();
AddIssue( $patron, $item->barcode, $end_date->clone );
$booking->discard_changes();
is(
dt_from_string( $booking->end_date )->compare($end_date), 0,
"Booking end_date unchanged when due date matches"
);
AddReturn( $item->barcode, $pickup_library->branchcode );
$booking->delete();
# Case 2: Due date overridden at checkout - booking end_date follows
$booking = Koha::Booking->new(
{
patron_id => $patron->borrowernumber,
pickup_library_id => $pickup_library->branchcode,
item_id => $item->itemnumber,
biblio_id => $item->biblio->biblionumber,
start_date => dt_from_string(),
end_date => $end_date,
}
)->store();
my $override_due = dt_from_string()->add( days => 10 )->truncate( to => 'minute' );
AddIssue( $patron, $item->barcode, $override_due->clone );
$booking->discard_changes();
is( $booking->status, 'issued', "Booking marked issued on checkout" );
is(
dt_from_string( $booking->end_date )->compare($override_due), 0,
"Booking end_date synced to overridden due date"
);
AddReturn( $item->barcode, $pickup_library->branchcode );
$booking->delete();
# Case 3: Override clashes with a subsequent booking - original end_date kept
$booking = Koha::Booking->new(
{
patron_id => $patron->borrowernumber,
pickup_library_id => $pickup_library->branchcode,
item_id => $item->itemnumber,
biblio_id => $item->biblio->biblionumber,
start_date => dt_from_string(),
end_date => $end_date,
}
)->store();
my $subsequent_booking = Koha::Booking->new(
{
patron_id => $patron2->borrowernumber,
pickup_library_id => $pickup_library->branchcode,
item_id => $item->itemnumber,
biblio_id => $item->biblio->biblionumber,
start_date => dt_from_string()->add( days => 8 ),
end_date => dt_from_string()->add( days => 12 ),
}
)->store();
AddIssue( $patron, $item->barcode, $override_due->clone );
$booking->discard_changes();
is( $booking->status, 'issued', "Booking still marked issued when override clashes" );
is(
dt_from_string( $booking->end_date )->compare($end_date), 0,
"Booking end_date kept when override would clash with a subsequent booking"
);
$schema->storage->txn_rollback;
};
subtest "Item's onloan value should be set if checked out item is checked out to a different patron" => sub {
plan tests => 2;