https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=42999

            Bug ID: 42999
           Summary: $valid_items flag contamination in reserve/request.pl
                    causes non-holdable items to appear as available after
                    the first holdable item in the loop
   Initiative type: ---
        Sponsorship ---
            status:
           Product: Koha
           Version: Main
          Hardware: All
                OS: All
            Status: NEW
          Severity: major
          Priority: P5 - low
         Component: Hold requests
          Assignee: [email protected]
          Reporter: [email protected]
        QA Contact: [email protected]
                CC: [email protected], [email protected],
                    [email protected]
  Target Milestone: ---

Bug 38412 (commit 1900effc9b) introduced $valid_items as a flag to track
whether any item passes all availability conditions. However, the flag is also
used as a per-item gate in the same loop without being reset between
iterations. Once any item sets $valid_items = 1, all subsequent items —
regardless of their individual availability — enter the if ($valid_items)
branch and are assigned $item->{available} = 1.

Root cause (reserve/request.pl):

The commit split what was a single if block into two consecutive, separate
blocks:


# Line 455 — initialized once, outside the loop
my $valid_items = 0;

for my $item_object (@items) {
    ...

    # Block 1 (line 570): per-item condition check
    if (
           !$item->{cantreserve}
        && !$exceeded_maxreserves
        && $can_item_be_reserved eq 'OK'
        && IsAvailableForItemLevelRequest( $item_object, $patron, undef )
    ) {
        $valid_items = 1;    # set but never reset between iterations
    }

    # Block 2 (line 583): uses $valid_items as per-item gate — BUG
    if ($valid_items) {      # true for ALL items after the first valid one
        $num_items_available++;
        $item->{available} = 1;
        ...
    } elsif ( C4::Context->preference('AllowHoldPolicyOverride') ) {
        ...
    } else {
        $item->{available} = 0;   # never reached after first valid item
    }
}

# Line 661 — post-loop: this use of $valid_items is correct
if ( $valid_items == 0 ) {
    $template->param( none_available_override => 1 );
}
The variable serves two conflicting roles:

Global accumulator (correct, line 661): "did any item pass all checks?"
Per-item gate (incorrect, line 583): "does this item pass all checks?" — but it
carries over from previous iterations.
Steps to reproduce:

Create a biblio with at least two items: one with a holdable item type (e.g.
Books) and one with a not-for-loan item type (e.g. Reference,
itemtypes.notforloan = 1).
The not-for-loan item must have a higher itemnumber than at least one holdable
item (i.e., it is processed later in the loop).
Open the place-hold staff form:
reserve/request.pl?biblionumber=X&borrowernumber=Y
Expected: The not-for-loan item shows no radio button / is not selectable for
holds.

Actual: The not-for-loan item has a radio button and available = 1, despite the
Information column correctly showing "Not for loan (Itemtype not for loan)".

This is reproducible with AllowHoldPolicyOverride either on or off.

Diagnostic trace on a local instance running main (as of 2026-07-06,
biblionumber 47160, patron borrowernumber 2):


AllowHoldPolicyOverride: NO

itemno  barcode         itype   ifl  can_reserve  cond_passes  valid_items 
available  branch_taken
---------------------------------------------------------------------------------------------------
27924   FFL24040002     BK      0    OK           YES          0->1         1  
       if($valid_items)
27926   CPL24040004     BK      0    OK           YES          1->1         1  
       if($valid_items)
27936   CPL24050001     BK      0    OK           YES          1->1         1  
       if($valid_items)
28043   CPL24110003     REF     1    OK           NO           1->1         1  
       if($valid_items) <-- BUG
28046   CPL25030002     BK      0    OK           YES          1->1         1  
       if($valid_items)
...

Legend:
  ifl         = itemtype notforloan flag
  cond_passes = item individually passes all availability conditions
  valid_items = flag value before->after this iteration
  <-- BUG     = actual result differs from correct result

Note: in this test case CanItemBeReserved returns OK for the REF item
(depending on circulation rules, it may also return notforloan); either way,
IsAvailableForItemLevelRequest returns false due to the itemtype flag, so
cond_passes = NO. The bug manifests regardless.

Proposed fix:

Merge the two if blocks back into one, so the pickup-location logic and
$item->{available} assignment only execute when the current item individually
passes all conditions. The global $valid_items accumulator for the post-loop
check at line 661 is preserved:


if (
       !$item->{cantreserve}
    && !$exceeded_maxreserves
    && $can_item_be_reserved eq 'OK'
    && IsAvailableForItemLevelRequest( $item_object, $patron, undef )
) {
    $valid_items = 1;

    my $pickup_locations = $item_object->pickup_locations( { patron => $patron
} );
    $item->{pickup_locations_count} = $pickup_locations->count;
    if ( $item->{pickup_locations_count} > 0 ||
C4::Context->preference('AllowHoldPolicyOverride') ) {
        $num_items_available++;
        $item->{available} = 1;
        ...
    } else {
        $item->{available}    = 0;
        $item->{not_holdable} = "no_valid_pickup_location";
    }
} elsif ( C4::Context->preference('AllowHoldPolicyOverride') ) {
    ...
} else {
    $item->{available} = 0;
}

-- 
You are receiving this mail because:
You are the assignee for the bug.
You are watching all bug changes.
_______________________________________________
Koha-bugs mailing list
[email protected]
https://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-bugs
website : http://www.koha-community.org/
git : http://git.koha-community.org/
bugs : http://bugs.koha-community.org/

Reply via email to