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 <martin.renvoize@ptfs-europe.com> Signed-off-by: Janet McGowan <janet.mcgowan@ptfs-europe.com> Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com> Signed-off-by: Laurence Rault <laurence.rault@biblibre.com> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
parent
ea4cb8ff85
commit
3b1612cf27
|
|
@ -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 );
|
||||
|
|
|
|||
85
Koha/Item.pm
85
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,10 @@
|
|||
<div class="dialog alert">Item was checked out to [% INCLUDE 'patron-title.inc' patron = alert.RETURNED_FROM_ANOTHER.patron %] and was returned automatically.</div>
|
||||
[% END %]
|
||||
|
||||
[% IF alert.BOOKED %]
|
||||
<div class="dialog alert">Item is booked by this user<div>
|
||||
[% END %]
|
||||
|
||||
[% IF ( nopermission ) %]
|
||||
<div class="dialog alert">Staff members are not allowed to discharge borrowers, nor borrowers to request a discharge.</div>
|
||||
[% END %]
|
||||
|
|
@ -244,6 +248,17 @@
|
|||
[% END %]
|
||||
[% END %]
|
||||
|
||||
[% IF ( BOOKED_TO_ANOTHER ) %]
|
||||
<li>
|
||||
Due for another patron booking by: [% BOOKED_TO_ANOTHER.start_date | $KohaDates %]
|
||||
</li>
|
||||
[% END %]
|
||||
|
||||
[% IF ( BOOKED_EARLY ) %]
|
||||
<li>
|
||||
Patron has this item booked for checkout on [% BOOKED_EARLY.start_date | $KohaDates %]
|
||||
</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
|
||||
[% IF CAN_user_circulate_force_checkout or HIGHHOLDS %]
|
||||
|
|
@ -324,6 +339,19 @@
|
|||
<input type="hidden" name="duedatespec" value="[% duedatespec | html %]" />
|
||||
[% END %]
|
||||
|
||||
[% IF ( BOOKED_TO_ANOTHER ) %]
|
||||
<p>
|
||||
<label for="reduceddue">Reducing due date to: <label>
|
||||
<input type="text" size="20" id="reudceddue" name="reduceddue" value="[% reduceddue | $KohaDates with_hours => 1 %]" readonly="readonly">
|
||||
</p>
|
||||
[% END %]
|
||||
|
||||
[% IF ( BOOKED_EARLY ) %]
|
||||
<p>
|
||||
Allow early collection?
|
||||
</p>
|
||||
[% END %]
|
||||
|
||||
<input type="hidden" name="stickyduedate" value="[% stickyduedate | html %]" />
|
||||
<input type="hidden" name="branch" value="[% branch | html %]" />
|
||||
|
||||
|
|
@ -530,6 +558,10 @@
|
|||
[% IF (forceallow) %]
|
||||
<li>Restriction overridden temporarily.</li>
|
||||
[% END %]
|
||||
|
||||
[% IF ( BOOKED_TO_ANOTHER ) %]
|
||||
<li>!!A booking exists!!</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
</div> <!-- /#circ_impossible -->
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue