From 3b1612cf27cbf8ca52b59ed0877cd2db860a7346 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 1 Oct 2021 09:03:31 +0100 Subject: [PATCH] Bug 29002: Circulation This patch adds handling for circulation of booked materials. We prevent checkouts of booked items during the booking period where the booking is for another user. We update the due date to match the end of booking period when checking out to the user who the booking is for. If a checkout would overlap a booking to another user, we ask the user if they will accept a reduced due date so the booked item is returned in time to fulfill the booking. Signed-off-by: Martin Renvoize Signed-off-by: Janet McGowan Signed-off-by: Caroline Cyr La Rose Signed-off-by: Laurence Rault Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- C4/Circulation.pm | 30 +++++++ Koha/Item.pm | 85 ++++++++++++++++++- circ/circulation.pl | 30 ++++++- .../prog/en/modules/circ/circulation.tt | 32 +++++++ 4 files changed, 173 insertions(+), 4 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index baab76fef6..ec21a6091c 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -1219,6 +1219,36 @@ sub CanBookBeIssued { } } + ## CHECK FOR BOOKINGS + if ( + my $booking = $item_object->find_booking( + { + checkout_date => $now, + due_date => $duedate, + patron_id => $patron->borrowernumber + } + ) + ) + { + # Booked to this patron :) + if ( $booking->patron_id == $patron->borrowernumber ) { + if ( $now < dt_from_string($booking->start_date) ) { + $needsconfirmation{'BOOKED_EARLY'} = $booking; + } + else { + $alerts{'BOOKED'} = $booking; + } + } + # Booking starts before due date, reduce loan? + elsif ( $duedate > dt_from_string($booking->start_date) ) { + $needsconfirmation{'BOOKED_TO_ANOTHER'} = $booking; + } + # Loan falls inside booking + else { + $issuingimpossible{'BOOKED_TO_ANOTHER'} = $booking; + } + } + ## CHECK AGE RESTRICTION my $agerestriction = $biblioitem->agerestriction; my ($restriction_age, $daysToAgeRestriction) = GetAgeRestriction( $agerestriction, $patron->unblessed ); diff --git a/Koha/Item.pm b/Koha/Item.pm index ea2b8e39d2..1f490b86f5 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -526,6 +526,85 @@ sub bookings { return Koha::Bookings->_new_from_dbic( $bookings_rs ); } +=head3 find_booking + +Find the first booking that would conflict with the passed checkout dates + +=cut + +sub find_booking { + my ( $self, $params ) = @_; + + my $checkout_date = $params->{checkout_date}; + my $due_date = $params->{due_date}; + my $biblio = $self->biblio; + + my $dtf = Koha::Database->new->schema->storage->datetime_parser; + my $bookings = $biblio->bookings( + [ + # Checkout starts during booked period + start_date => { + '-between' => [ + $dtf->format_datetime($checkout_date), + $dtf->format_datetime($due_date) + ] + }, + + # Checkout is due during booked period + end_date => { + '-between' => [ + $dtf->format_datetime($checkout_date), + $dtf->format_datetime($due_date) + ] + }, + + # Checkout contains booked period + { + start_date => { '<' => $dtf->format_datetime($checkout_date) }, + end_date => { '>' => $dtf->format_datetime($due_date) } + } + ], + { + order_by => { '-asc' => 'start_date' } + } + ); + + my $checkouts = {}; + my $loanable_items = {}; + my $bookable_items = $biblio->bookable_items; + while ( my $item = $bookable_items->next ) { + $loanable_items->{ $item->itemnumber } = 1; + if ( my $checkout = $item->checkout ) { + $checkouts->{ $item->itemnumber } = + dt_from_string( $checkout->date_due ); + } + } + + while ( my $booking = $bookings->next ) { + + # Booking for this item + if ( defined( $booking->item_id ) + && $booking->item_id == $self->itemnumber ) + { + return $booking; + } + + # Booking for another item + elsif ( defined( $booking->item_id ) ) { + # Due for another booking, remove from pool + delete $loanable_items->{ $booking->item_id }; + next; + + } + + # Booking for any item + else { + # Can another item satisfy this booking? + } + } + return; +} + =head3 check_booking my $bookable = @@ -613,9 +692,9 @@ sub place_booking { { start_date => $params->{start_date}, end_date => $params->{end_date}, - borrowernumber => $patron->borrowernumber, - biblionumber => $self->biblionumber, - itemnumber => $self->itemnumber, + patron_id => $patron->borrowernumber, + biblio_id => $self->biblionumber, + item_id => $self->itemnumber, } )->store(); return $booking; diff --git a/circ/circulation.pl b/circ/circulation.pl index 7f8c0ef080..6a2d610ecb 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -202,6 +202,10 @@ if( $onsite_checkout && !$duedatespec_allow ) { } } } +my $reduced_datedue = $query->param('reduceddue'); +if ( $reduced_datedue ) { + $datedue = dt_from_string( $reduced_datedue ); +} my $inprocess = (@$barcodes == 0) ? '' : $query->param('inprocess'); if ( @$barcodes == 0 && $charges eq 'yes' ) { @@ -370,9 +374,16 @@ if (@$barcodes) { ? qw( UNKNOWN_BARCODE NO_OPEN_DAYS ) : ( keys %$error ); + if ( $error->{BOOKED_TO_ANOTHER} ) { + $template_params->{BOOKED_TO_ANOTHER} = $error->{BOOKED_TO_ANOTHER}; + $template_params->{IMPOSSIBLE} = 1; + $blocker = 1; + } + foreach my $code ( @blocking_error_codes ) { if ($error->{$code}) { $template_params->{$code} = $error->{$code}; + $template_params->{IMPOSSIBLE} = 1; $blocker = 1; } @@ -397,6 +408,10 @@ if (@$barcodes) { $template_params->{getBarcodeMessageIteminfo} = $item->barcode; $template_params->{NEEDSCONFIRMATION} = 1; $confirm_required = 1; + if ( $needsconfirmation eq 'BOOKED_TO_ANOTHER' ) { + my $reduceddue = dt_from_string($$question{$needsconfirmation}->start_date)->subtract( days => 1 ); + $template_params->{reduceddue} = $reduceddue; + } } } unless($confirm_required) { @@ -413,7 +428,20 @@ if (@$barcodes) { ); $recall_id = ( $recall and $recall->id ) ? $recall->id : undef; } - my $issue = AddIssue( $patron, $barcode, $datedue, $cancelreserve, undef, undef, { onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), switch_onsite_checkout => $switch_onsite_checkout, cancel_recall => $cancel_recall, recall_id => $recall_id, } ); + # If booked (alerts or confirmation) update datedue to end of booking + if ( my $booked = $question->{BOOKED_EARLY} // $alerts->{BOOKED} ) { + $datedue = $booked->end_date; + } + my $issue = AddIssue( + $patron, $barcode, $datedue, + $cancelreserve, + undef, undef, + { + onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'), + switch_onsite_checkout => $switch_onsite_checkout, cancel_recall => $cancel_recall, + recall_id => $recall_id, + } + ); $template_params->{issue} = $issue; $session->clear('auto_renew'); $inprocess = 1; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt index dfc42a6b9e..cdd223688f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -92,6 +92,10 @@
Item was checked out to [% INCLUDE 'patron-title.inc' patron = alert.RETURNED_FROM_ANOTHER.patron %] and was returned automatically.
[% END %] + [% IF alert.BOOKED %] +
Item is booked by this user
+ [% END %] + [% IF ( nopermission ) %]
Staff members are not allowed to discharge borrowers, nor borrowers to request a discharge.
[% END %] @@ -244,6 +248,17 @@ [% END %] [% END %] + [% IF ( BOOKED_TO_ANOTHER ) %] +
  • + Due for another patron booking by: [% BOOKED_TO_ANOTHER.start_date | $KohaDates %] +
  • + [% END %] + + [% IF ( BOOKED_EARLY ) %] +
  • + Patron has this item booked for checkout on [% BOOKED_EARLY.start_date | $KohaDates %] +
  • + [% END %] [% IF CAN_user_circulate_force_checkout or HIGHHOLDS %] @@ -324,6 +339,19 @@ [% END %] + [% IF ( BOOKED_TO_ANOTHER ) %] +

    +

    + [% END %] + + [% IF ( BOOKED_EARLY ) %] +

    + Allow early collection? +

    + [% END %] + @@ -530,6 +558,10 @@ [% IF (forceallow) %]
  • Restriction overridden temporarily.
  • [% END %] + + [% IF ( BOOKED_TO_ANOTHER ) %] +
  • !!A booking exists!!
  • + [% END %]
    -- 2.39.5