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