From 53da1e6697c7d1809b90ab10f56657a06b7447a1 Mon Sep 17 00:00:00 2001
From: Alex Arnaud <alex.arnaud@biblibre.com>
Date: Wed, 12 Dec 2018 11:08:43 +0100
Subject: [PATCH] Bug 23732: Hold rules checker

Test plan:
- apply patch
- try to place a hold, on the Hold details page you should see a
  "Holding rules" button. Click on it
- check that the info is consistent with the holding rules, the syspref
  and the current and future number of holds of the patron
- place the hold
- set maxreserves syspref to 1
- try to place a hold
- see in the holds rules checker that the maxreserves is highlighted in
  red
- restore maxreserves syspref
- test that sabotaging the matched circulation rule or hold policy is
  reflected in the hold rules checker
---
 C4/Reserves.pm                                | 246 +++++++++++++-----
 .../prog/en/modules/reserve/request.tt        | 150 +++++++++++
 reserve/request.pl                            |  11 +-
 t/db_dependent/Reserves/HoldRulesChecker.t    | 110 ++++++++
 4 files changed, 454 insertions(+), 63 deletions(-)
 create mode 100644 t/db_dependent/Reserves/HoldRulesChecker.t

diff --git a/C4/Reserves.pm b/C4/Reserves.pm
index 987543e1cd..8e4e8da7d5 100644
--- a/C4/Reserves.pm
+++ b/C4/Reserves.pm
@@ -341,19 +341,23 @@ sub CanBookBeReserved{
         return { status =>'alreadypossession' };
     }
 
+    my @circulation_rules;
+
     if ( $params->{itemtype} ) {
 
         # biblio-level, item type-contrained
         my $patron          = Koha::Patrons->find($borrowernumber);
-        my $reservesallowed = Koha::CirculationRules->get_effective_rule(
+        my $reservesallowed_rule = Koha::CirculationRules->get_effective_rule(
             {
                 itemtype     => $params->{itemtype},
                 categorycode => $patron->categorycode,
                 branchcode   => $pickup_branchcode,
                 rule_name    => 'reservesallowed',
             }
-        )->rule_value;
+        );
+        push @circulation_rules, $reservesallowed_rule if $reservesallowed_rule;
 
+        my $reservesallowed = $reservesallowed_rule ? $reservesallowed_rule->rule_value : undef;
         $reservesallowed = ( $reservesallowed eq '' ) ? undef : $reservesallowed;
 
         my $count = $patron->holds->search(
@@ -368,7 +372,7 @@ sub CanBookBeReserved{
             }
         )->count;
 
-        return { status => '' }
+        return { status => '', circulation_rules => \@circulation_rules }
           if defined $reservesallowed and $reservesallowed < $count + 1;
     }
 
@@ -386,36 +390,100 @@ sub CanBookBeReserved{
         $items = Koha::Items->search({ biblionumber => $biblionumber});
     }
 
-    my $canReserve = { status => '' };
+    my $response = { status => '', item_responses => [] };
     my $patron = Koha::Patrons->find( $borrowernumber );
     while ( my $item = $items->next ) {
-        $canReserve = CanItemBeReserved( $patron, $item, $pickup_branchcode, $params );
-        return { status => 'OK' } if $canReserve->{status} eq 'OK';
+        my $itemResponse = CanItemBeReserved( $patron, $item, $pickup_branchcode, $params );
+        push @{ $response->{item_responses} }, { %$itemResponse, item => $item };
+
+        if ($itemResponse->{status} eq 'OK') {
+            $response->{status} = 'OK';
+
+            return $response;
+        }
     }
-    return $canReserve;
+
+    return $response;
 }
 
 =head2 CanItemBeReserved
 
-  $canReserve = &CanItemBeReserved($patron, $item, $branchcode, $params)
+  $canReserve = CanItemBeReserved($patron, $item, $branchcode, $params)
   if ($canReserve->{status} eq 'OK') { #We can reserve this Item! }
 
-  current params are:
-  'ignore_hold_counts' - we use this routine to check if an item can fill a hold - on this case we
-  should not check if there are too many holds as we only care about reservability
-
-@RETURNS { status => OK },              if the Item can be reserved.
-         { status => ageRestricted },   if the Item is age restricted for this borrower.
-         { status => damaged },         if the Item is damaged.
-         { status => cannotReserveFromOtherBranches }, if syspref 'canreservefromotherbranches' is OK.
-         { status => branchNotInHoldGroup }, if borrower home library is not in hold group, and holds are only allowed from hold groups.
-         { status => tooManyReserves, limit => $limit }, if the borrower has exceeded their maximum reserve amount.
-         { status => notReservable },   if holds on this item are not allowed
-         { status => libraryNotFound },   if given branchcode is not an existing library
-         { status => libraryNotPickupLocation },   if given branchcode is not configured to be a pickup location
-         { status => cannotBeTransferred }, if branch transfer limit applies on given item and branchcode
-         { status => pickupNotInHoldGroup }, pickup location is not in hold group, and pickup locations are only allowed from hold groups.
-         { status => recall }, if the borrower has already placed a recall on this item
+Current params are:
+
+=over
+
+=item * C<ignore_hold_counts>
+
+we use this routine to check if an item can
+fill a hold - on this case we should not check if there are too many holds as
+we only care about reservability
+
+=back
+
+Returns a hashref with the following keys:
+
+=over
+
+=item * C<status>
+
+A string that can have the following values:
+
+=over
+
+=item * C<OK> if the item can be reserved
+
+=item * C<ageRestricted> if the item is aged restricted for this borrower
+
+=item * C<damaged> if the item is damaged
+
+=item * C<cannotReserveFromOtherBranches> if syspref
+'canreservefromotherbranches' is OK
+
+=item * C<branchNotInHoldGroup> if borrower home library is not in hold group,
+and holds are only allowed from hold groups.
+
+=item * C<tooManyReserves> if the borrower has exceeded their maximum reserve
+amount.
+
+=item * C<tooManyHoldsForThisRecord> if the borrower has exceeded their maximum
+reserve amount for this biblio record
+
+=item * C<tooManyReservesToday> if the borrower has exceeded their maximum
+reserve amount for today
+
+=item * C<notReservable> if holds on this item are not allowed
+
+=item * C<libraryNotFound> if given branchcode is not an existing library
+
+=item * C<libraryNotPickupLocation> if given branchcode is not configured to be
+a pickup location
+
+=item * C<cannotBeTransferred> if branch transfer limit applies on given item
+and branchcode
+
+=item * C<pickupNotInHoldGroup> if pickup location is not in hold group, and
+pickup locations are only allowed from hold groups.
+
+=item * C<recall> if the borrower has already placed a recall on this item
+
+=back
+
+=item * C<limit>
+
+Only if C<status> is equal to C<tooManyReserves>, C<tooManyHoldsForThisRecord>,
+or C<tooManyReservesToday>.
+
+It contains the number of reserves allowed.
+
+=item * C<circulation_rules>
+
+An arrayref containing all circulations rules (L<Koha::CirculationRule>) that
+have been checked
+
+=back
 
 =cut
 
@@ -423,8 +491,6 @@ sub CanItemBeReserved {
     my ( $patron, $item, $pickup_branchcode, $params ) = @_;
 
     my $dbh = C4::Context->dbh;
-    my $ruleitemtype;    # itemtype of the matching issuing rule
-    my $allowedreserves  = 0; # Total number of holds allowed across all records, default to none
 
     # We check item branch if IndependentBranches is ON
     # and canreservefromotherbranches is OFF
@@ -481,34 +547,21 @@ sub CanItemBeReserved {
         $reserves_control_branch  = $borrower->{branchcode};
     }
 
-    # we retrieve rights
-    if (
-        my $reservesallowed = Koha::CirculationRules->get_effective_rule({
-                itemtype     => $item->effective_itemtype,
-                categorycode => $borrower->{categorycode},
-                branchcode   => $reserves_control_branch,
-                rule_name    => 'reservesallowed',
-        })
-    ) {
-        $ruleitemtype     = $reservesallowed->itemtype;
-        $allowedreserves  = $reservesallowed->rule_value // 0; #undefined is 0, blank is unlimited
-    }
-    else {
-        $ruleitemtype = undef;
-    }
+    my @circulation_rules;
 
-    my $rights = Koha::CirculationRules->get_effective_rules({
-        categorycode => $borrower->{'categorycode'},
+    my $holds_per_record_rule = Koha::CirculationRules->get_effective_rule({
+        categorycode => $borrower->{categorycode},
         itemtype     => $item->effective_itemtype,
         branchcode   => $reserves_control_branch,
-        rules        => ['holds_per_record','holds_per_day']
+        rule_name    => 'holds_per_record',
     });
-    my $holds_per_record = $rights->{holds_per_record} // 1;
-    my $holds_per_day    = $rights->{holds_per_day};
+    push @circulation_rules, $holds_per_record_rule if $holds_per_record_rule;
+
+    my $holds_per_record = $holds_per_record_rule ? $holds_per_record_rule->rule_value : 1;
 
     if (   defined $holds_per_record && $holds_per_record ne '' ){
         if ( $holds_per_record == 0 ) {
-            return { status => "noReservesAllowed" };
+            return { status => "noReservesAllowed", circulation_rules => \@circulation_rules };
         }
         if ( !$params->{ignore_hold_counts} ) {
             my $search_params = {
@@ -516,24 +569,66 @@ sub CanItemBeReserved {
                 biblionumber   => $item->biblionumber,
             };
             my $holds = Koha::Holds->search($search_params);
-            return { status => "tooManyHoldsForThisRecord", limit => $holds_per_record } if $holds->count() >= $holds_per_record;
+
+            if ($holds->count() >= $holds_per_record) {
+                return {
+                    status => "tooManyHoldsForThisRecord",
+                    limit  => $holds_per_record,
+                    circulation_rules => \@circulation_rules,
+                };
+            }
         }
     }
 
+    my $holds_per_day_rule = Koha::CirculationRules->get_effective_rule({
+        categorycode => $borrower->{'categorycode'},
+        itemtype     => $item->effective_itemtype,
+        branchcode   => $reserves_control_branch,
+        rule_name    => 'holds_per_day',
+    });
+    push @circulation_rules, $holds_per_day_rule if $holds_per_day_rule;
+
+    my $holds_per_day = $holds_per_day_rule ? $holds_per_day_rule->rule_value : undef;
     if (!$params->{ignore_hold_counts} && defined $holds_per_day && $holds_per_day ne '')
     {
         my $today_holds = Koha::Holds->search({
             borrowernumber => $patron->borrowernumber,
             reservedate    => dt_from_string->date
         });
-        return { status => 'tooManyReservesToday', limit => $holds_per_day } if $today_holds->count() >= $holds_per_day;
+
+        if ($today_holds->count() >= $holds_per_day) {
+            return {
+                status => 'tooManyReservesToday',
+                limit  => $holds_per_day,
+                circulation_rules => \@circulation_rules,
+            };
+        }
+    }
+
+    my $reservesallowed_rule = Koha::CirculationRules->get_effective_rule({
+        itemtype     => $item->effective_itemtype,
+        categorycode => $borrower->{categorycode},
+        branchcode   => $reserves_control_branch,
+        rule_name    => 'reservesallowed',
+    });
+    push @circulation_rules, $reservesallowed_rule if $reservesallowed_rule;
+
+    my $ruleitemtype;    # itemtype of the matching issuing rule
+    my $allowedreserves  = 0; # Total number of holds allowed across all records, default to none
+    if ($reservesallowed_rule) {
+        $ruleitemtype     = $reservesallowed_rule->itemtype;
+        $allowedreserves  = $reservesallowed_rule->rule_value // 0; #undefined is 0, blank is unlimited
     }
 
     # we check if it's ok or not
     if ( defined $allowedreserves && $allowedreserves ne '' ){
-        if( $allowedreserves == 0 ){
-            return { status => 'noReservesAllowed' };
+        if ( $allowedreserves == 0 ) {
+            return {
+                status            => 'noReservesAllowed',
+                circulation_rules => \@circulation_rules,
+            };
         }
+
         if ( !$params->{ignore_hold_counts} ) {
             # we retrieve count
             my $querycount = q{
@@ -577,45 +672,66 @@ sub CanItemBeReserved {
                 $reservecount = $rowcount->{count};
             }
 
-            return { status => 'tooManyReserves', limit => $allowedreserves } if $reservecount >= $allowedreserves;
+            if ($reservecount >= $allowedreserves) {
+                return {
+                    status => 'tooManyReserves',
+                    limit  => $allowedreserves,
+                    circulation_rules => \@circulation_rules,
+                };
+            }
         }
     }
 
     # Now we need to check hold limits by patron category
-    my $rule = Koha::CirculationRules->get_effective_rule(
+    my $max_holds_rule = Koha::CirculationRules->get_effective_rule(
         {
             categorycode => $patron->categorycode,
             branchcode   => $reserves_control_branch,
             rule_name    => 'max_holds',
         }
     );
-    if (!$params->{ignore_hold_counts} && $rule && defined( $rule->rule_value ) && $rule->rule_value ne '' ) {
+    push @circulation_rules, $max_holds_rule if $max_holds_rule;
+
+    if (!$params->{ignore_hold_counts} && $max_holds_rule && defined( $max_holds_rule->rule_value ) && $max_holds_rule->rule_value ne '' ) {
         my $total_holds_count = Koha::Holds->search(
             {
                 borrowernumber => $patron->borrowernumber
             }
         )->count();
 
-        return { status => 'tooManyReserves', limit => $rule->rule_value} if $total_holds_count >= $rule->rule_value;
+        if ($total_holds_count >= $max_holds_rule->rule_value) {
+            return {
+                status => 'tooManyReserves',
+                limit => $max_holds_rule->rule_value,
+                circulation_rules => \@circulation_rules,
+            };
+        }
     }
 
+    my $holdallowed_rule = Koha::CirculationRules->get_effective_rule({
+        branchcode => $reserves_control_branch,
+        itemtype => $item->effective_itemtype,
+        rule_name => 'holdallowed',
+    });
+    push @circulation_rules, $holdallowed_rule if $holdallowed_rule;
+
     my $branchitemrule =
       C4::Circulation::GetBranchItemRule( $reserves_control_branch, $item->effective_itemtype );
 
     if ( $branchitemrule->{holdallowed} eq 'not_allowed' ) {
-        return { status => 'notReservable' };
+        return { status => 'notReservable', circulation_rules => \@circulation_rules };
     }
 
     if (   $branchitemrule->{holdallowed} eq 'from_home_library'
         && $borrower->{branchcode} ne $item->homebranch )
     {
-        return { status => 'cannotReserveFromOtherBranches' };
+        return { status => 'cannotReserveFromOtherBranches', circulation_rules => \@circulation_rules };
     }
 
     my $item_library = Koha::Libraries->find( {branchcode => $item->homebranch} );
     if ( $branchitemrule->{holdallowed} eq 'from_local_hold_group') {
         if($patron->branchcode ne $item->homebranch && !$item_library->validate_hold_sibling( {branchcode => $patron->branchcode} )) {
-            return { status => 'branchNotInHoldGroup' };
+            return { status => 'branchNotInHoldGroup', circulation_rules => \@circulation_rules };
         }
     }
 
@@ -633,15 +749,23 @@ sub CanItemBeReserved {
         unless ($item->can_be_transferred({ to => $destination })) {
             return { status => 'cannotBeTransferred' };
         }
+
+        my $hold_fulfillment_policy_rule = Koha::CirculationRules->get_effective_rule({
+            branchcode => $reserves_control_branch,
+            itemtype => $item->effective_itemtype,
+            rule_name => 'hold_fulfillment_policy',
+        });
+        push @circulation_rules, $hold_fulfillment_policy_rule if $hold_fulfillment_policy_rule;
+
         if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup' && !$item_library->validate_hold_sibling( {branchcode => $pickup_branchcode} )) {
-            return { status => 'pickupNotInHoldGroup' };
+            return { status => 'pickupNotInHoldGroup', circulation_rules => \@circulation_rules };
         }
         if ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup' && !Koha::Libraries->find({branchcode => $borrower->{branchcode}})->validate_hold_sibling({branchcode => $pickup_branchcode})) {
-            return { status => 'pickupNotInHoldGroup' };
+            return { status => 'pickupNotInHoldGroup', circulation_rules => \@circulation_rules };
         }
     }
 
-    return { status => 'OK' };
+    return { status => 'OK', circulation_rules => \@circulation_rules };
 }
 
 =head2 CanReserveBeCanceledFromOpac
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 e2d33c5415..1531188c19 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt
@@ -205,6 +205,10 @@
                 </h2>
             [% END %]
 
+            [% IF canbookbereserved_responses %]
+                <button type="button" class="btn btn-sm" data-toggle="modal" data-target="#holdruleschecker">[% t('Show circulation rules') | html %]</button>
+            [% END %]
+
             [% UNLESS club OR patron OR patron.borrowernumber OR noitems OR nobiblio %]
                 [% IF ( messageborrower ) %]
                     <div class="dialog alert">
@@ -472,6 +476,7 @@
                                 <li><strong>No items available: </strong>One or more records have no items that can be held</li>
                             [% END # /IF exceeded_maxreserves %]
                         [% END # /UNLESS multi_hold %]
+
                     </div>
                 [% END # /IF ( exceeded_maxreserves || ... %]
 
@@ -1756,4 +1761,149 @@
     </script>
 [% END %]
 
+<div class="modal fade" id="holdruleschecker" tabindex="-1" role="dialog" aria-labelledby="holdruleschecker-title">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="holdruleschecker-title">[% t('Holding rules') | html %]</h4>
+            </div>
+            <div class="modal-body">
+                <div>
+                    <h4>[% t('Overall info') | html %]</h4>
+
+                    <ul>
+                        <li>
+                            [% tnx('Patron has {count} hold', 'Patron has {count} holds', reserves_count, { count = '<strong>' _ reserves_count _ '</strong>' }) | $raw %]
+                            [% tx('and this request would raise the number to {count}.', { count = '<strong>' _ (new_reserves_count + reserves_count) _ '</strong>' }) | $raw %]
+                        </li>
+                        <li>
+                            [% IF reserves_count >= Koha.Preference('maxreserves') %]
+                                <span class="text-danger">
+                            [% ELSE %]
+                                <span>
+                            [% END %]
+                                [% tx('{preference_name} preference:', { preference_name = '<em>maxreserves</em>' }) | $raw %]
+                                <strong>[% Koha.Preference('maxreserves') | html %]</strong>
+                            </span>
+                        </li>
+                        <li>
+                            [% tx('{preference_name} preference:', { preference_name = '<em>ReservesControlBranch</em>' }) | $raw %]
+                            <strong>[% Koha.Preference('ReservesControlBranch') | html %]</strong>
+                        </li
+                    </ul>
+                </div>
+
+                <h4>[% t('Per record info') | html %]</h4>
+
+                <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
+                    [% FOREACH response IN canbookbereserved_responses %]
+                        <div class="panel panel-default">
+                            <div class="panel-heading" role="tab" id="heading-[% response.biblio.biblionumber | html %]">
+                                <h4 class="panel-title">
+                                    <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse-[% response.biblio.biblionumber | html %]" aria-expanded="true" aria-controls="collapse-[% response.biblio.biblionumber | html %]">
+                                        [% response.biblio.title | html %]
+                                    </a>
+                                </h4>
+                            </div>
+                            <div id="collapse-[% response.biblio.biblionumber | html %]" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-[% response.biblio.biblionumber | html %]">
+                                <div class="panel-body">
+                                    <h5>[% t('Circulation rules checked') | html %]</h5>
+
+                                    <table>
+                                        <thead>
+                                            <tr>
+                                                <th>[% t('Item') | html %]</th>
+                                                <th>[% t('Library') | html %]</th>
+                                                <th>[% t('Category') | html %]</th>
+                                                <th>[% t('Item type') | html %]</th>
+                                                <th>[% t('Rule name') | html %]</th>
+                                                <th>[% t('Rule value') | html %]</th>
+                                            </tr>
+                                        </thead>
+                                        <tbody>
+                                            [% FOREACH circulation_rule IN response.circulation_rules %]
+                                                <tr>
+                                                    <td></td>
+                                                    <td>
+                                                        [% IF circulation_rule.branchcode %]
+                                                            [% Branches.GetName(circulation_rule.branchcode) | html %]
+                                                            ([% circulation_rule.branchcode | html %])
+                                                        [% ELSE %]
+                                                            <em>[% t('All') | html %]</em>
+                                                        [% END %]
+                                                    </td>
+                                                    <td>
+                                                        [% IF circulation_rule.categorycode %]
+                                                            [% Categories.GetName(circulation_rule.categorycode) | html %]
+                                                        [% ELSE %]
+                                                            <em>[% t('All') | html %]</em>
+                                                        [% END %]
+                                                    </td>
+                                                    <td>
+                                                        [% IF circulation_rule.itemtype %]
+                                                            [% ItemTypes.GetDescription(circulation_rule.itemtype) | html %]
+                                                        [% ELSE %]
+                                                            <em>[% t('All') | html %]</em>
+                                                        [% END %]
+                                                    </td>
+                                                    <td>
+                                                        [% circulation_rule.rule_name | html %]
+                                                    </td>
+                                                    <td>
+                                                        [% circulation_rule.rule_value | html %]
+                                                    </td>
+                                                </tr>
+                                            [% END %]
+                                            [% FOREACH item_response IN response.item_responses %]
+                                                [% FOREACH circulation_rule IN item_response.circulation_rules %]
+                                                    <tr>
+                                                        <td>
+                                                            [% item_response.item.barcode | html %] (#[% item_response.item.itemnumber | html %])
+                                                        </td>
+                                                        <td>
+                                                            [% IF circulation_rule.branchcode %]
+                                                                [% Branches.GetName(circulation_rule.branchcode) | html %]
+                                                                ([% circulation_rule.branchcode | html %])
+                                                            [% ELSE %]
+                                                                <em>[% t('All') | html %]</em>
+                                                            [% END %]
+                                                        </td>
+                                                        <td>
+                                                            [% IF circulation_rule.categorycode %]
+                                                                [% Categories.GetName(circulation_rule.categorycode) | html %]
+                                                            [% ELSE %]
+                                                                <em>[% t('All') | html %]</em>
+                                                            [% END %]
+                                                        </td>
+                                                        <td>
+                                                            [% IF circulation_rule.itemtype %]
+                                                                [% ItemTypes.GetDescription(circulation_rule.itemtype) | html %]
+                                                            [% ELSE %]
+                                                                <em>[% t('All') | html %]</em>
+                                                            [% END %]
+                                                        </td>
+                                                        <td>
+                                                            [% circulation_rule.rule_name | html %]
+                                                        </td>
+                                                        <td>
+                                                            [% circulation_rule.rule_value | html %]
+                                                        </td>
+                                                    </tr>
+                                                [% END %]
+                                            [% END %]
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                    [% END %]
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">[% t('Close') | html %]</button>
+            </div>
+        </div>
+    </div>
+</div> <!-- end of Modal -->
+
 [% INCLUDE 'intranet-bottom.inc' %]
diff --git a/reserve/request.pl b/reserve/request.pl
index 0c37d6e4c1..71a87b16b6 100755
--- a/reserve/request.pl
+++ b/reserve/request.pl
@@ -181,8 +181,10 @@ if ($borrowernumber_hold && !$action) {
     # FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
 
     my $reserves_count = $patron->holds->count;
+    $template->param(reserves_count => $reserves_count);
 
     my $new_reserves_count = scalar( @biblionumbers );
+    $template->param(new_reserves_count   => $new_reserves_count);
 
     my $maxreserves = C4::Context->preference('maxreserves');
     $template->param( maxreserves => $maxreserves );
@@ -198,8 +200,6 @@ if ($borrowernumber_hold && !$action) {
         $exceeded_maxreserves = 1;
         $template->param(
             new_reserves_allowed => $new_reserves_allowed,
-            new_reserves_count   => $new_reserves_count,
-            reserves_count       => $reserves_count,
             maxreserves          => $maxreserves,
         );
     }
@@ -293,6 +293,7 @@ if (   ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
     my @biblioloop = ();
     my $no_reserves_allowed = 0;
     my $num_bibs_available = 0;
+    my @canbookbereserved_responses;
     foreach my $biblionumber (@biblionumbers) {
         next unless $biblionumber =~ m|^\d+$|;
 
@@ -311,6 +312,9 @@ if (   ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
         if ( $patron ) {
             { # CanBookBeReserved
                 my $canReserve = CanBookBeReserved( $patron->borrowernumber, $biblionumber );
+
+                push @canbookbereserved_responses, { %$canReserve, biblio => $biblio };
+
                 if ( $canReserve->{status} eq 'OK' ) {
 
                     #All is OK and we can continue
@@ -694,6 +698,9 @@ if (   ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
 
     $template->param( no_bibs_available => 1 ) unless $num_bibs_available > 0;
 
+    $Data::Dumper::Maxdepth = 3;
+    use Data::Dumper; warn Dumper \@canbookbereserved_responses;
+    $template->param( canbookbereserved_responses => \@canbookbereserved_responses );
     $template->param( biblioloop => \@biblioloop );
     $template->param( no_reserves_allowed => $no_reserves_allowed );
     $template->param( exceeded_maxreserves => $exceeded_maxreserves );
diff --git a/t/db_dependent/Reserves/HoldRulesChecker.t b/t/db_dependent/Reserves/HoldRulesChecker.t
new file mode 100644
index 0000000000..1bebad86ef
--- /dev/null
+++ b/t/db_dependent/Reserves/HoldRulesChecker.t
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+
+# Copyright 2019 Biblibre
+#
+# 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 Test::More tests => 13;
+use t::lib::TestBuilder;
+
+use C4::Reserves qw( CanBookBeReserved );
+
+my $schema = Koha::Database->new->schema;
+$schema->storage->txn_begin;
+
+my $builder = t::lib::TestBuilder->new();
+
+my $library = $builder->build_object({ class => 'Koha::Libraries' });
+my $category = $builder->build_object({ class => 'Koha::Patron::Categories' });
+my $patron = $builder->build_object(
+    {
+        class => 'Koha::Patrons',
+        value  => {
+            categorycode => $category->categorycode,
+            branchcode   => $library->branchcode,
+        },
+    }
+);
+
+my $itemtype = $builder->build_object({ class => 'Koha::ItemTypes' });
+
+my $biblio = $builder->build_sample_biblio(
+    {
+        title => 'Title 1',
+    }
+);
+my $item = $builder->build_sample_item(
+    {
+        biblionumber => $biblio->biblionumber,
+        library      => $library->branchcode,
+        itype        => $itemtype->itemtype,
+        damaged      => 0,
+    }
+);
+
+my $rules_rs = Koha::Database->new()->schema()->resultset('CirculationRule');
+$rules_rs->delete();
+
+my $default_reservesallowed_rule = $rules_rs->create(
+    {
+        categorycode     => undef,
+        itemtype         => undef,
+        branchcode       => undef,
+        rule_name => 'reservesallowed',
+        rule_value => 10,
+    }
+);
+
+my $reservesallowed_rule = $rules_rs->create(
+    {
+        categorycode     => $patron->categorycode,
+        itemtype         => undef,
+        branchcode       => $library->branchcode,
+        rule_name => 'reservesallowed',
+        rule_value => 0,
+    }
+);
+
+my $can = CanBookBeReserved($patron->borrowernumber, $biblio->biblionumber);
+is($can->{status}, '');
+is(scalar @{ $can->{circulation_rules} }, 0);
+is(scalar @{ $can->{item_responses} }, 1);
+is($can->{item_responses}->[0]->{status}, 'noReservesAllowed');
+is(scalar @{ $can->{item_responses}->[0]->{circulation_rules} }, 1);
+is($can->{item_responses}->[0]->{circulation_rules}->[0]->id, $reservesallowed_rule->id);
+
+my $holds_per_record_rule = $rules_rs->create(
+    {
+        categorycode     => $patron->categorycode,
+        itemtype         => undef,
+        branchcode       => $library->branchcode,
+        rule_name => 'holds_per_record',
+        rule_value => 1,
+    }
+);
+
+$can = CanBookBeReserved($patron->borrowernumber, $biblio->biblionumber);
+is($can->{status}, '');
+is(scalar @{ $can->{circulation_rules} }, 0);
+is(scalar @{ $can->{item_responses} }, 1);
+is($can->{item_responses}->[0]->{status}, 'noReservesAllowed');
+is(scalar @{ $can->{item_responses}->[0]->{circulation_rules} }, 2);
+is($can->{item_responses}->[0]->{circulation_rules}->[0]->id, $holds_per_record_rule->id);
+is($can->{item_responses}->[0]->{circulation_rules}->[1]->id, $reservesallowed_rule->id);
+
+$schema->storage->txn_rollback;
-- 
2.30.2