Bug 32121: Show an alert when adding a checked out item to an item bundle

When trying to add a checked out item to an item bundle, an alert
message will show up, giving the user a chance to return the item
immediately before adding it to the bundle

Test plan:
1. Create an item bundle (see bug 28854 comment 458)
2. Create a biblio with one item and check out this item.
3. Try to add the checked out item to the bundle
   You should see a message saying that the item is checked out. Next to
   this message should be a button "Check in and add to bundle".
4. Click on the button. There should be a message saying that the item
   was added to the bundle.
5. Close the modal window and verify that the item was correctly
   returned and added to the bundle

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Julian Maurice 2022-11-07 14:30:34 +01:00 committed by Tomas Cohen Arazi
parent f781fdb4a2
commit de36244266
No known key found for this signature in database
GPG Key ID: 0A272EA1B2F3C15F
7 changed files with 140 additions and 20 deletions

View File

@ -0,0 +1,32 @@
package Koha::Exceptions::Checkin;
# 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 <http://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Exception;
use Exception::Class (
'Koha::Exceptions::Checkin' => {
isa => 'Koha::Exception',
},
'Koha::Exceptions::Checkin::FailedCheckin' => {
isa => 'Koha::Exceptions::Checkin',
description => "Checkin failed"
},
);
1;

View File

@ -20,14 +20,17 @@ use Modern::Perl;
use Koha::Exception;
use Exception::Class (
'Koha::Exceptions::Item::Bundle' => {
isa => 'Koha::Exception',
},
'Koha::Exceptions::Item::Bundle::IsBundle' => {
isa => 'Koha::Exceptions::Item::Bundle',
description => "A bundle cannot be added to a bundle",
}
},
'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut' => {
isa => 'Koha::Exceptions::Item::Bundle',
description => 'Someone tried to add a checked out item to a bundle',
},
);
=head1 NAME
@ -44,6 +47,10 @@ Generic Item::Bundle exception
Exception to be used when attempting to add one bundle into another.
=head2 Koha::Exceptions::Item::Bundle::ItemIsCheckedOut
Exception to be used when attempting to add a checked out item to a bundle.
=cut
1;

View File

@ -36,6 +36,8 @@ use Koha::Biblio::ItemGroups;
use Koha::Checkouts;
use Koha::CirculationRules;
use Koha::CoverImages;
use Koha::Exceptions::Checkin;
use Koha::Exceptions::Item::Bundle;
use Koha::Exceptions::Item::Transfer;
use Koha::Item::Attributes;
use Koha::Exceptions::Item::Bundle;
@ -1699,7 +1701,9 @@ Adds the bundle_item passed to this item
=cut
sub add_to_bundle {
my ( $self, $bundle_item ) = @_;
my ( $self, $bundle_item, $options ) = @_;
$options //= {};
Koha::Exceptions::Item::Bundle::IsBundle->throw()
if ( $self->itemnumber eq $bundle_item->itemnumber
@ -1713,6 +1717,19 @@ sub add_to_bundle {
try {
$schema->txn_do(
sub {
my $checkout = $bundle_item->checkout;
if ($checkout) {
unless ($options->{force_checkin}) {
Koha::Exceptions::Item::Bundle::ItemIsCheckedOut->throw();
}
my $branchcode = C4::Context->userenv->{'branch'};
my ($success) = C4::Circulation::AddReturn($bundle_item->barcode, $branchcode);
unless ($success) {
Koha::Exceptions::Checkin::FailedCheckin->throw();
}
}
$self->_result->add_to_item_bundles_hosts(
{ item => $bundle_item->itemnumber } );
@ -1764,7 +1781,7 @@ sub add_to_bundle {
$_->rethrow();
}
else {
$_;
$_->rethrow();
}
};
}

View File

@ -278,7 +278,8 @@ sub add_to_bundle {
}
return try {
my $link = $item->add_to_bundle($bundle_item);
my $force_checkin = $c->validation->param('body')->{'force_checkin'};
my $link = $item->add_to_bundle($bundle_item, { force_checkin => $force_checkin });
return $c->render(
status => 201,
openapi => $bundle_item
@ -301,8 +302,23 @@ sub add_to_bundle {
error => 'Bundles cannot be nested'
}
);
}
else {
} elsif (ref($_) eq 'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut') {
return $c->render(
status => 409,
openapi => {
error => 'Item is checked out',
key => 'checked_out'
}
);
} elsif (ref($_) eq 'Koha::Exceptions::Checkin::FailedCheckin') {
return $c->render(
status => 409,
openapi => {
error => 'Item cannot be checked in',
key => 'failed_checkin'
}
);
} else {
$c->unhandled_exception($_);
}
};

View File

@ -11,4 +11,8 @@ properties:
- string
- "null"
description: Item barcode
force_checkin:
type:
- boolean
- "null"
additionalProperties: false

View File

@ -1937,36 +1937,45 @@ Note that permanent location is a code, and location may be an authval.
bundle_form_active = item_id;
});
$("#addToBundleForm").submit(function(event) {
/* stop form from submitting normally */
event.preventDefault();
/* get the action attribute from the <form action=""> element */
var $form = $(this),
url = $form.attr('action');
function addToBundle (url, data) {
/* Send the data using post with external_id */
var posting = $.post({
url: url,
data: JSON.stringify({ external_id: $('#external_id').val()}),
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
dataType: "json"
});
const barcode = data.external_id;
/* Report the results */
posting.done(function(data) {
var barcode = $('#external_id').val();
$('#addResult').replaceWith('<div id="addResult" class="alert alert-success">'+_("Success: Added '%s'").format(barcode)+'</div>');
$('#external_id').val('').focus();
bundle_changed = 1;
});
posting.fail(function(data) {
var barcode = $('#external_id').val();
if ( data.status === 409 ) {
var response = data.responseJSON;
if ( response.key === "PRIMARY" ) {
$('#addResult').replaceWith('<div id="addResult" class="alert alert-warning">'+_("Warning: Item '%s' already attached").format(barcode)+'</div>');
} else if (response.key === 'checked_out') {
const button = $('<button type="button">')
.addClass('btn btn-xs')
.text(__('Check in and add to bundle'))
.on('click', function () {
addToBundle(url, { external_id: barcode, force_checkin: true });
});
$('#addResult')
.empty()
.attr('class', 'alert alert-warning')
.append(__x('Warning: Item {barcode} is checked out', { barcode }))
.append(' ', button);
} else if (response.key === 'failed_checkin') {
$('#addResult')
.empty()
.attr('class', 'alert alert-danger')
.append(__x('Failure: Item {barcode} cannot be checked in', { barcode }))
} else {
$('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Item '%s' belongs to another bundle").format(barcode)+'</div>');
}
@ -1984,6 +1993,16 @@ Note that permanent location is a code, and location may be an authval.
}
$('#external_id').val('').focus();
});
}
$("#addToBundleForm").submit(function(event) {
/* stop form from submitting normally */
event.preventDefault();
const url = this.action;
const data = { external_id: this.elements.external_id.value };
addToBundle(url, data);
});
$("#addToBundleModal").on("hidden.bs.modal", function(e){

View File

@ -213,12 +213,17 @@ subtest 'bundle_host tests' => sub {
};
subtest 'add_to_bundle tests' => sub {
plan tests => 6;
plan tests => 7;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'BundleNotLoanValue', 1 );
my $library = $builder->build_object({ class => 'Koha::Libraries' });
t::lib::Mocks::mock_userenv({
branchcode => $library->branchcode
});
my $host_item = $builder->build_sample_item();
my $bundle_item1 = $builder->build_sample_item();
my $bundle_item2 = $builder->build_sample_item();
@ -242,6 +247,26 @@ subtest 'add_to_bundle tests' => sub {
'Koha::Exceptions::Item::Bundle::IsBundle',
'Exception thrown if you try to add a bundle host to a bundle item';
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
C4::Circulation::AddIssue( $patron->unblessed, $bundle_item2->barcode );
throws_ok { $host_item->add_to_bundle($bundle_item2) }
'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut',
'Exception thrown if you try to add a checked out item';
$bundle_item2->withdrawn(1)->store;
t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', 1 );
throws_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
'Koha::Exceptions::Checkin::FailedCheckin',
'Exception thrown if you try to add a checked out item using
"force_checkin" and the return is not possible';
$bundle_item2->withdrawn(0)->store;
lives_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
'No exception if you try to add a checked out item using "force_checkin" and the return is possible';
$bundle_item2->discard_changes;
ok( !$bundle_item2->checkout, 'Item is not checked out after being added to a bundle' );
$schema->storage->txn_rollback;
};