From 9f68040dac148394eb9bdead4c440d15ea006cea Mon Sep 17 00:00:00 2001 From: David Cook <dcook@prosentient.com.au> Date: Mon, 12 Feb 2024 23:17:05 +0000 Subject: [PATCH] Bug 15565: Place multiple holds on one record on staff interface --- .../prog/en/modules/reserve/request.tt | 70 ++++++++++++++--- reserve/placerequest.pl | 78 +++++++++++-------- reserve/request.pl | 3 + 3 files changed, 108 insertions(+), 43 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt index c6a8bba7000..86530949154 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -168,6 +168,56 @@ </div> [% END %] + [% IF ( failed_holds ) %] + <div class="dialog alert"> + <strong>One or more holds were not placed due to following errors:</strong> + <ul> + [% FOREACH fail IN failed_holds %] + <li> + [% SWITCH fail %] + [% CASE 'damaged' %] + <span>Item is damaged</span> + [% CASE 'ageRestricted' %] + <span>The record and its items are age restricted</span> + [% CASE 'tooManyHoldsForThisRecord' %] + <span>Patron exceeded the number of concurrent holds for this record</span> + [% CASE 'tooManyReservesToday' %] + <span>Patron reached daily holds limit</span> + [% CASE 'tooManyReserves' %] + <span>Too many holds already</span> + [% CASE 'notReservable' %] + <span>Not holdable</span> + [% CASE 'cannotReserveFromOtherBranches' %] + <span>Patron from a different library</span> + [% CASE 'branchNotInHoldGroup' %] + <span>Holds are not allowed from patron's library</span> + [% CASE 'itemAlreadyOnHold' %] + <span>Patron already has a hold for failed item(s)</span> + [% CASE 'cannotBeTransferred' %] + <span>Cannot be transferred to pickup library</span> + [% CASE 'pickupNotInHoldGroup' %] + <span>The chosen pickup location is not allowed</span> + [% CASE 'noReservesAllowed' %] + <span>No holds are allowed on failed item(s)</span> + [% CASE 'libraryNotPickupLocation' %] + <span>Library is not a valid pickup location</span> + [% CASE 'no_valid_pickup_location' %] + <span>No valid pickup location</span> + [% CASE 'notforloan' %] + <span>Not for loan</span> + [% CASE 'items_available' %] + <span>There are items available in the library</span> + [% CASE 'not_placed' %] + <span>Error when placing hold</span> + [% CASE %] + <span>Error: [% fail | html %]</span> + [% END %] + </li> + [% END %] + </ul> + </div> + [% END %] + [% UNLESS ( multi_hold ) %] <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]</h2> [% ELSE %] @@ -736,9 +786,9 @@ Hold must be item group level </span> [% ELSIF ( itemloo.available ) %] - <input type="radio" name="checkitem" value="[% itemloo.itemnumber | html %]" /> + <input type="checkbox" name="checkitem" value="[% itemloo.itemnumber | html %]" /> [% ELSIF ( itemloo.override ) %] - <input type="radio" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber | html %]" /> + <input type="checkbox" name="checkitem" class="needsoverride" value="[% itemloo.itemnumber | html %]" /> <i class="fa fa-exclamation-triangle fa-lg" style="color:gold" title="Requires override of hold policy"/></i> [% ELSE %] <span class="error"> @@ -1507,11 +1557,11 @@ if ( ! $("#requestany").is(":checked") ) { // requestany not selected, go through the item-specific cases - if ( $('input[type="radio"]:checked').length > 0 ) { + if ( $('input[name="checkitem"]:checked').length > 0 ) { // got item-specific hold requests in the form! // verify they have a pickup location selected - if (table.find('input[type="radio"]:checked') + if (table.find('input[name="checkitem"]:checked') .closest('tr') .find(".pickup_locations").val() === null) { @@ -1602,15 +1652,17 @@ } }); $("input[name=checkitem]").click(function() { - onechecked = 0; + let onechecked = 0; $("input[name=checkitem]").each(function() { if(this.checked){ - onechecked = 1; + onechecked++; } }); - if(onechecked == 1){ + if(onechecked > 0){ $("#requestany").prop("checked", false); $("#holds_to_place_count").prop('disabled', true); + + $("#holds_to_place_count").val(onechecked); } else { $("#requestany").prop("checked",true); $("#holds_to_place_count").prop('disabled', false); @@ -1676,10 +1728,10 @@ [% IF Koha.Preference('EnableItemGroupHolds') %] $(':radio[name="item_group_id"]').change(function(){ - $(':radio[name="checkitem"]').prop('checked', false); + $('input[name="checkitem"]').prop('checked', false); }); - $(':radio[name="checkitem"]').change(function(){ + $('input[name="checkitem"]').change(function(){ $(':radio[name="item_group_id"]').prop('checked', false); }); [% END %] diff --git a/reserve/placerequest.pl b/reserve/placerequest.pl index d2fec975dd0..48dd0636c58 100755 --- a/reserve/placerequest.pl +++ b/reserve/placerequest.pl @@ -44,7 +44,7 @@ my $branch = $input->param('pickup'); my $startdate = $input->param('reserve_date') || ''; my @rank = $input->multi_param('rank-request'); my $title = $input->param('title'); -my $checkitem = $input->param('checkitem'); +my @checkitems = $input->multi_param('checkitem'); my $item_group_id = $input->param('item_group_id'); my $expirationdate = $input->param('expiration_date'); my $itemtype = $input->param('itemtype') || undef; @@ -64,43 +64,48 @@ foreach my $bibnum ( @holdable_bibs ) { $bibinfos{$bibnum} = \%bibinfo; } -my $found; -if ( $op eq 'cud-placerequest' && $patron ) { +if ( $op eq 'cud-placerequest' && $patron ) { + my %failed_holds; foreach my $biblionumber ( keys %bibinfos ) { my $can_override = C4::Context->preference('AllowHoldPolicyOverride'); - if ( defined $checkitem && $checkitem ne '' ) { - - if ( my $item_pickup_location = $input->param("item_pickup_$checkitem") ) { + if ( @checkitems ) { + for ( my $i = 0; $i < scalar @checkitems; $i++ ){ + my $checkitem = $checkitems[$i]; + if ( my $item_pickup_location = $input->param("item_pickup_$checkitem") ) { - my $item = Koha::Items->find($checkitem); + my $item = Koha::Items->find($checkitem); - if ( $item->biblionumber ne $biblionumber ) { - $biblionumber = $item->biblionumber; - } - - my $can_item_be_reserved = CanItemBeReserved($patron, $item, $item_pickup_location)->{status}; + if ( $item->biblionumber ne $biblionumber ) { + $biblionumber = $item->biblionumber; + } - if ( $can_item_be_reserved eq 'OK' || ( $can_item_be_reserved ne 'itemAlreadyOnHold' && $can_override ) ) { - AddReserve( - { - branchcode => $item_pickup_location, - borrowernumber => $patron->borrowernumber, - biblionumber => $biblionumber, - priority => $rank[0], - reservation_date => $startdate, - expiration_date => $expirationdate, - notes => $notes, - title => $title, - itemnumber => $checkitem, - found => $found, - itemtype => $itemtype, - non_priority => $non_priority, - } - ); + my $can_item_be_reserved = CanItemBeReserved($patron, $item, $item_pickup_location)->{status}; + + if ( $can_item_be_reserved eq 'OK' || ( $can_item_be_reserved ne 'itemAlreadyOnHold' && $can_override ) ) { + AddReserve( + { + branchcode => $item_pickup_location, + borrowernumber => $patron->borrowernumber, + biblionumber => $biblionumber, + priority => $rank[$i], + reservation_date => $startdate, + expiration_date => $expirationdate, + notes => $notes, + title => $title, + itemnumber => $checkitem, + found => undef, + itemtype => $itemtype, + non_priority => $non_priority, + } + ); + } + else { + $failed_holds{$can_item_be_reserved} = 1; + } } } @@ -117,8 +122,8 @@ if ( $op eq 'cud-placerequest' && $patron ) { expiration_date => $expirationdate, notes => $notes, title => $bibinfo->{title}, - itemnumber => $checkitem, - found => $found, + itemnumber => undef, + found => undef, itemtype => $itemtype, non_priority => $non_priority, } @@ -138,8 +143,8 @@ if ( $op eq 'cud-placerequest' && $patron ) { expiration_date => $expirationdate, notes => $notes, title => $title, - itemnumber => $checkitem, - found => $found, + itemnumber => undef, + found => undef, itemtype => $itemtype, non_priority => $non_priority, item_group_id => $item_group_id, @@ -151,7 +156,12 @@ if ( $op eq 'cud-placerequest' && $patron ) { } my $redirect_url = URI->new("request.pl"); - $redirect_url->query_form( biblionumber => [@biblionumbers]); + my @failed_hold_msgs = (); + #NOTE: Deduplicate failed hold reason statuses/codes + foreach my $msg (keys %failed_holds){ + push(@failed_hold_msgs,$msg); + } + $redirect_url->query_form( biblionumber => [@biblionumbers], failed_holds => \@failed_hold_msgs); print $input->redirect($redirect_url); } elsif ( $borrowernumber eq '' ) { diff --git a/reserve/request.pl b/reserve/request.pl index 36e8eb2aade..023d7de424f 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -88,6 +88,7 @@ my $warnings; my $messages; my $exceeded_maxreserves; my $exceeded_holds_per_record; +my @failed_holds = $input->multi_param('failed_holds'); my $op = $input->param('op') || q{}; @@ -709,6 +710,8 @@ $template->param( pickup => $pickup || C4::Context->userenv->{branch} ); $template->param(borrowernumber => $borrowernumber_hold); +$template->param( failed_holds => \@failed_holds ); + # printout the page output_html_with_http_headers $input, $cookie, $template->output; -- 2.30.2