View | Details | Raw Unified | Return to bug 38666
Collapse All | Expand All

(-)a/C4/Reserves.pm (-32 / +43 lines)
Lines 200-210 sub AddReserve { Link Here
200
200
201
    $resdate ||= dt_from_string;
201
    $resdate ||= dt_from_string;
202
202
203
    my $item = $checkitem ? Koha::Items->find($checkitem) : undef;
204
203
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
205
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
204
    # of the document, we force the value $priority and $found .
206
    # of the document, we force the value $priority and $found .
205
    if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) {
207
    if ( $item and not C4::Context->preference('ReservesNeedReturns') ) {
206
        my $item = Koha::Items->find($checkitem);    # FIXME Prevent bad calls
207
208
        if (
208
        if (
209
            # If item is already checked out, it cannot be set waiting
209
            # If item is already checked out, it cannot be set waiting
210
            !$item->onloan
210
            !$item->onloan
Lines 883-897 sub CheckReserves { Link Here
883
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
883
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
884
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
884
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
885
885
886
    my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
886
    unless ( $item->is_closed_stack ) {
887
    if ( !$dont_trap ) {
887
        my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
888
        my $effective_item_type = $item->effective_itemtype;
888
        if ( !$dont_trap ) {
889
            my $effective_item_type = $item->effective_itemtype;
889
890
890
        my $item_type = Koha::ItemTypes->find($effective_item_type);
891
            my $item_type = Koha::ItemTypes->find($effective_item_type);
891
        return
892
            return
892
            if $item_type && $item_type->notforloan;
893
                if $item_type && $item_type->notforloan;
893
    } else {
894
        } else {
894
        return;
895
            return;
896
        }
895
    }
897
    }
896
898
897
    # Find this item in the reserves
899
    # Find this item in the reserves
Lines 1535-1546 sub IsAvailableForItemLevelRequest { Link Here
1535
        unless defined $itemtype;
1537
        unless defined $itemtype;
1536
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1538
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1537
1539
1540
    # Closed stack items can be requested even if marked as "not for loan"
1541
    unless ( $item->is_closed_stack ) {
1542
        return 0
1543
            if $notforloan_per_itemtype
1544
            || $item->notforloan > 0;    # item with negative or zero notforloan value is holdable
1545
    }
1546
1538
    return 0
1547
    return 0
1539
        if $notforloan_per_itemtype
1548
        if $item->itemlost
1540
        || $item->itemlost
1549
        || $item->withdrawn
1541
        || $item->notforloan > 0
1542
        ||    # item with negative or zero notforloan value is holdable
1543
        $item->withdrawn
1544
        || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
1550
        || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
1545
1551
1546
    if ($pickup_branchcode) {
1552
    if ($pickup_branchcode) {
Lines 1556-1584 sub IsAvailableForItemLevelRequest { Link Here
1556
            || $home_library->validate_hold_sibling( { branchcode => $pickup_branchcode } );
1562
            || $home_library->validate_hold_sibling( { branchcode => $pickup_branchcode } );
1557
    }
1563
    }
1558
1564
1559
    my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1565
    unless ( $item->is_closed_stack ) {
1566
        my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1560
1567
1561
    if ( $on_shelf_holds == 1 ) {
1568
        if ( $on_shelf_holds == 1 ) {
1562
        return 1;
1569
            return 1;
1563
    } elsif ( $on_shelf_holds == 2 ) {
1570
        } elsif ( $on_shelf_holds == 2 ) {
1564
1571
1565
        # These calculations work at the biblio level, and can be expensive
1572
            # These calculations work at the biblio level, and can be expensive
1566
        # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1573
            # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1567
1574
1568
        my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1575
            my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1569
        my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1576
            my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1570
1577
1571
        my $any_available = $memory_cache->get_from_cache($cache_key);
1578
            my $any_available = $memory_cache->get_from_cache($cache_key);
1572
        return $any_available ? 0 : 1 if defined($any_available);
1579
            return $any_available ? 0 : 1 if defined($any_available);
1573
1580
1574
        $any_available =
1581
            $any_available =
1575
            ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1582
                ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1576
        $memory_cache->set_in_cache( $cache_key, $any_available );
1583
            $memory_cache->set_in_cache( $cache_key, $any_available );
1577
        return $any_available ? 0 : 1;
1584
            return $any_available ? 0 : 1;
1578
1585
1579
    } else {  # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1586
        } else
1580
        return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1587
        {    # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1588
            return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1589
        }
1581
    }
1590
    }
1591
1592
    return 1;
1582
}
1593
}
1583
1594
1584
=head2 ItemsAnyAvailableAndNotRestricted
1595
=head2 ItemsAnyAvailableAndNotRestricted
(-)a/Koha/Biblio.pm (+33 lines)
Lines 2393-2398 sub merge_with { Link Here
2393
    return \%results;
2393
    return \%results;
2394
}
2394
}
2395
2395
2396
=head3 forced_hold_level
2397
2398
Returns forced hold level for a biblio
2399
2400
Returns 'item' if item-level hold should be forced, or undef
2401
2402
=cut
2403
2404
sub forced_hold_level {
2405
    my ($self) = @_;
2406
2407
    my $forced_hold_level;
2408
2409
    # If there is at least one closed stack item that is not checked out and
2410
    # not reserved, force item-level hold
2411
    my $available_closed_stack_items = $self->items->search(
2412
        {
2413
            is_closed_stack       => 1,
2414
            onloan                => undef,
2415
            'reserves.reserve_id' => undef,
2416
        },
2417
        {
2418
            join => 'reserves',
2419
        },
2420
    );
2421
2422
    if ( $available_closed_stack_items->count > 0 ) {
2423
        $forced_hold_level = 'item';
2424
    }
2425
2426
    return $forced_hold_level;
2427
}
2428
2396
=head2 Internal methods
2429
=head2 Internal methods
2397
2430
2398
=head3 type
2431
=head3 type
(-)a/Koha/CirculationRules.pm (+2 lines)
Lines 567-572 sub get_opacitemholds_policy { Link Here
567
567
568
    return unless $item or $patron;
568
    return unless $item or $patron;
569
569
570
    return 'F' if $item->is_closed_stack;
571
570
    my $rule = Koha::CirculationRules->get_effective_rule(
572
    my $rule = Koha::CirculationRules->get_effective_rule(
571
        {
573
        {
572
            categorycode => $patron->categorycode,
574
            categorycode => $patron->categorycode,
(-)a/Koha/Hold.pm (-2 / +4 lines)
Lines 507-514 This is used from the OPAC. Link Here
507
sub is_cancelable_from_opac {
507
sub is_cancelable_from_opac {
508
    my ($self) = @_;
508
    my ($self) = @_;
509
509
510
    return 1 unless $self->is_found();
510
    return 0 if $self->is_found();
511
    return 0;    # if ->is_in_transit or if ->is_waiting or ->is_in_processing
511
    return 0 if $self->item && $self->item->is_closed_stack;
512
513
    return 1;
512
}
514
}
513
515
514
=head3 cancellation_requestable_from_opac
516
=head3 cancellation_requestable_from_opac
(-)a/Koha/Holds.pm (-4 / +74 lines)
Lines 132-137 Items that are not: Link Here
132
  widthdrawn
132
  widthdrawn
133
  not for loan
133
  not for loan
134
  not on loan
134
  not on loan
135
  in closed stack
135
136
136
=cut
137
=cut
137
138
Lines 164-173 sub get_items_that_can_fill { Link Here
164
165
165
    return Koha::Items->search(
166
    return Koha::Items->search(
166
        {
167
        {
167
            -or        => \@bibs_or_items,
168
            -or             => \@bibs_or_items,
168
            itemnumber => { -not_in => [ @branchtransfers, @waiting_holds ] },
169
            itemnumber      => { -not_in => [ @branchtransfers, @waiting_holds ] },
169
            onloan     => undef,
170
            onloan          => undef,
170
            notforloan => 0,
171
            notforloan      => 0,
172
            is_closed_stack => 0,
171
        }
173
        }
172
    )->filter_by_for_hold();
174
    )->filter_by_for_hold();
173
}
175
}
Lines 229-234 sub count_holds { Link Here
229
    return $holds_without_group_count + $hold_groups_count;
231
    return $holds_without_group_count + $hold_groups_count;
230
}
232
}
231
233
234
=head3 filter_by_closed_stack_requests
235
236
Apply filter to keep only closed stack requests
237
238
    $holds = Koha::Holds->search($filter)->filter_by_closed_stack_requests();
239
240
Returns a C<Koha::Holds> object
241
242
=cut
243
244
sub filter_by_closed_stack_requests {
245
    my ($self) = @_;
246
247
    return $self->search( $self->_closed_stack_request_filter, { join => 'item' } );
248
}
249
250
=head3 filter_out_closed_stack_requests
251
252
Apply filter to exclude closed stack requests
253
254
    $holds = Koha::Holds->search($filter)->filter_out_closed_stack_requests();
255
256
Returns a C<Koha::Holds> object
257
258
=cut
259
260
sub filter_out_closed_stack_requests {
261
    my ($self) = @_;
262
263
    return $self->search( { -not_bool => $self->_closed_stack_request_filter }, { join => 'item' } );
264
}
265
266
sub _closed_stack_request_filter {
267
268
    # This query returns the top priority reserve's id for each item
269
    my $reserve_id_subselect = q{
270
        select reserve_id from (
271
            select reserve_id, itemnumber, row_number() over (partition by itemnumber order by priority) rank
272
            from reserves where itemnumber is not null
273
        ) r
274
        where rank = 1
275
    };
276
277
    my %where = (
278
        'me.reserve_id'        => { -in => \$reserve_id_subselect },
279
        'me.suspend'           => 0,
280
        'me.found'             => undef,
281
        'me.priority'          => { '!=' => 0 },
282
        'item.itemlost'        => 0,
283
        'item.withdrawn'       => 0,
284
        'item.onloan'          => undef,
285
        'item.is_closed_stack' => 1,
286
        'item.itemnumber'      => {
287
            -not_in => \'SELECT itemnumber FROM branchtransfers WHERE datearrived IS NULL AND datecancelled IS NULL'
288
        },
289
    );
290
291
    if ( !C4::Context->preference('AllowHoldsOnDamagedItems') ) {
292
        $where{'item.damaged'} = 0;
293
    }
294
295
    if ( C4::Context->only_my_library() ) {
296
        $where{'me.branchcode'} = C4::Context->userenv->{'branch'};
297
    }
298
299
    return \%where;
300
}
301
232
=head2 Internal methods
302
=head2 Internal methods
233
303
234
=head3 _type
304
=head3 _type
(-)a/Koha/Item.pm (-2 / +33 lines)
Lines 1455-1462 sub columns_to_str { Link Here
1455
              $subfield
1455
              $subfield
1456
            ? $subfield->{authorised_value}
1456
            ? $subfield->{authorised_value}
1457
                ? C4::Biblio::GetAuthorisedValueDesc(
1457
                ? C4::Biblio::GetAuthorisedValueDesc(
1458
                    $itemtagfield,
1458
                $itemtagfield,
1459
                    $subfield->{tagsubfield}, $value, '', $tagslib
1459
                $subfield->{tagsubfield}, $value, '', $tagslib
1460
                )
1460
                )
1461
                : $value
1461
                : $value
1462
            : $value;
1462
            : $value;
Lines 2690-2695 sub analytics_count { Link Here
2690
    return C4::Items::GetAnalyticsCount( $self->itemnumber );
2690
    return C4::Items::GetAnalyticsCount( $self->itemnumber );
2691
}
2691
}
2692
2692
2693
=head3 is_available_for_closed_stack_request
2694
2695
Returns 1 if item is available for a closed stack request, 0 otherwise
2696
2697
An item is available for a closed stack request if:
2698
2699
=over
2700
2701
=item * it is flagged as "closed stack"
2702
2703
=item * there is no holds on it
2704
2705
=item * it is not checked out
2706
2707
=item * it is not in transfer
2708
2709
=back
2710
2711
=cut
2712
2713
sub is_available_for_closed_stack_request {
2714
    my ($self) = @_;
2715
2716
    return 0 unless $self->is_closed_stack;
2717
    return 0 if $self->holds->count > 0;
2718
    return 0 if $self->checkout;
2719
    return 0 if $self->get_transfers->count > 0;
2720
2721
    return 1;
2722
}
2723
2693
=head3 strings_map
2724
=head3 strings_map
2694
2725
2695
Returns a map of column name to string representations including the string,
2726
Returns a map of column name to string representations including the string,
(-)a/Koha/Items.pm (-1 / +26 lines)
Lines 108-113 sub filter_by_for_hold { Link Here
108
        notforloan => { '<=' => 0 },    # items with negative or zero notforloan value are holdable
108
        notforloan => { '<=' => 0 },    # items with negative or zero notforloan value are holdable
109
        ( C4::Context->preference('AllowHoldsOnDamagedItems') ? () : ( damaged => 0 ) ),
109
        ( C4::Context->preference('AllowHoldsOnDamagedItems') ? () : ( damaged => 0 ) ),
110
        ( C4::Context->only_my_library()                      ? ( homebranch => C4::Context::mybranch() ) : () ),
110
        ( C4::Context->only_my_library()                      ? ( homebranch => C4::Context::mybranch() ) : () ),
111
        -or => [
112
            { is_closed_stack => 0 },
113
            {
114
                is_closed_stack       => 1,
115
                'reserves.reserve_id' => { '!=', undef },
116
            },
117
        ],
111
    };
118
    };
112
119
113
    if ( C4::Context->preference("item-level_itypes") ) {
120
    if ( C4::Context->preference("item-level_itypes") ) {
Lines 115-120 sub filter_by_for_hold { Link Here
115
            {
122
            {
116
                %$params,
123
                %$params,
117
                itype => { -not_in => \@hold_not_allowed_itypes },
124
                itype => { -not_in => \@hold_not_allowed_itypes },
125
            },
126
            {
127
                join => 'reserves',
118
            }
128
            }
119
        );
129
        );
120
    } else {
130
    } else {
Lines 124-130 sub filter_by_for_hold { Link Here
124
                'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes },
134
                'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes },
125
            },
135
            },
126
            {
136
            {
127
                join => 'biblioitem',
137
                join => [ 'biblioitem', 'reserves' ],
128
            }
138
            }
129
        );
139
        );
130
    }
140
    }
Lines 367-372 sub filter_by_available { Link Here
367
    );
377
    );
368
}
378
}
369
379
380
=head3 filter_by_closed_stack
381
382
  my $filterd_items = $items->filter_by_closed_stack;
383
384
Returns a new resultset, containing only those items that are flagged as
385
"closed stack".
386
387
=cut
388
389
sub filter_by_closed_stack {
390
    my ($self) = @_;
391
392
    return $self->search( { is_closed_stack => 1 } );
393
}
394
370
=head3 move_to_biblio
395
=head3 move_to_biblio
371
396
372
 $items->move_to_biblio($to_biblio);
397
 $items->move_to_biblio($to_biblio);
(-)a/Koha/Template/Plugin/Biblio.pm (-1 / +1 lines)
Lines 32-38 sub HoldsCount { Link Here
32
32
33
    warn "HoldsCount is deprecated, you should use biblio.holds.count instead";
33
    warn "HoldsCount is deprecated, you should use biblio.holds.count instead";
34
34
35
    my $holds = Koha::Holds->search( { biblionumber => $biblionumber } );
35
    my $holds = Koha::Holds->search( { 'me.biblionumber' => $biblionumber } )->filter_out_closed_stack_requests();
36
36
37
    return $holds->count();
37
    return $holds->count();
38
}
38
}
(-)a/Koha/Template/Plugin/GD/Barcode.pm (+86 lines)
Line 0 Link Here
1
package Koha::Template::Plugin::GD::Barcode;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
=head1 NAME
19
20
Koha::Template::Plugin::GD::Barcode
21
22
=head1 SYNOPSIS
23
24
    [% USE GD.Barcode %]
25
26
    <img src="[% GD.Barcode.create_as_data_url('Code39', item.barcode) | html %]">
27
28
=head1 DESCRIPTION
29
30
This Template plugin allows to create barcode image as data URL
31
32
Mostly useful in notices and slips
33
34
=cut
35
36
use Modern::Perl;
37
38
use GD::Barcode;
39
use MIME::Base64;
40
41
use base qw( Template::Plugin );
42
43
=head1 METHODS
44
45
=head2 new
46
47
Creates a new instance of the plugin
48
49
=cut
50
51
sub new {
52
    my ($class) = @_;
53
54
    my $self = {};
55
56
    return bless $self, $class;
57
}
58
59
=head2 create_as_data_url
60
61
Create a barcode image as a data URL
62
63
    [% GD.Barcode.create_as_data_url(type, barcode, args, plot_args) %]
64
65
C<type>, C<barcode> and C<args> are passed to C<GD::Barcode::new>
66
67
C<plot_args> is passed to C<GD::Barcode::plot>
68
69
See L<GD::Barcode/new> and L<GD::Barcode/plot>
70
71
It returns a data URL suited for use in an img src attribute
72
73
=cut
74
75
sub create_as_data_url {
76
    my ( $self, $type, $barcode, $args, $plot_args ) = @_;
77
78
    $args      //= {};
79
    $plot_args //= {};
80
81
    my $data = GD::Barcode->new( $type, $barcode, $args )->plot(%$plot_args)->png;
82
83
    return 'data:image/png;base64,' . encode_base64($data);
84
}
85
86
1;
(-)a/admin/columns_settings.yml (+21 lines)
Lines 2071-2076 modules: Link Here
2071
              columnname: suspended_until
2071
              columnname: suspended_until
2072
            -
2072
            -
2073
              columnname: notes
2073
              columnname: notes
2074
2075
      closed-stack-requests:
2076
        default_display_length: 20
2077
        default_sort_order: 0
2078
        columns:
2079
            - columnname: patron
2080
            - columnname: title
2081
            - columnname: libraries
2082
            - columnname: barcodes
2083
            - columnname: call_numbers
2084
            - columnname: copy_numbers
2085
            - columnname: stocknumber
2086
            - columnname: enumeration
2087
            - columnname: itemtypes
2088
            - columnname: locations
2089
            - columnname: collection
2090
            - columnname: hold_date
2091
            - columnname: reserve_notes
2092
            - columnname: pickup_location
2093
            - columnname: action
2094
2074
    holdsratios:
2095
    holdsratios:
2075
      holds-ratios:
2096
      holds-ratios:
2076
        default_display_length: 20
2097
        default_display_length: 20
(-)a/api/v1/swagger/definitions/item.yaml (+4 lines)
Lines 218-223 properties: Link Here
218
      - "null"
218
      - "null"
219
    description: Itemtype defining the type for this item
219
    description: Itemtype defining the type for this item
220
    maxLength: 10
220
    maxLength: 10
221
  is_closed_stack:
222
    type:
223
      - boolean
224
      - "null"
221
  effective_item_type_id:
225
  effective_item_type_id:
222
    type:
226
    type:
223
      - string
227
      - string
(-)a/catalogue/detail.pl (-1 / +4 lines)
Lines 502-508 if ( C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pre Link Here
502
502
503
#we only need to pass the number of holds to the template
503
#we only need to pass the number of holds to the template
504
my $holds = $biblio->holds;
504
my $holds = $biblio->holds;
505
$template->param( holdcount => $holds->count );
505
$template->param(
506
    holdcount                  => $holds->filter_out_closed_stack_requests->count,
507
    closed_stack_request_count => $holds->filter_by_closed_stack_requests->count,
508
);
506
509
507
# Check if there are any ILL requests connected to the biblio
510
# Check if there are any ILL requests connected to the biblio
508
my $illrequests =
511
my $illrequests =
(-)a/catalogue/updateitem.pl (+4 lines)
Lines 41-46 my $damaged = $cgi->param('damaged'); Link Here
41
my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority');
41
my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority');
42
my $bookable                          = $cgi->param('bookable') // q{};
42
my $bookable                          = $cgi->param('bookable') // q{};
43
43
44
my $is_closed_stack = $cgi->param('is_closed_stack');
45
44
my $confirm = $cgi->param('confirm');
46
my $confirm = $cgi->param('confirm');
45
my $dbh     = C4::Context->dbh;
47
my $dbh     = C4::Context->dbh;
46
48
Lines 74-79 if ( $op eq "cud-set_non_public_note" ) { Link Here
74
    $item->itemlost($itemlost);
76
    $item->itemlost($itemlost);
75
} elsif ( $op eq "cud-set_withdrawn" && $withdrawn ne $item_data_hashref->{'withdrawn'} ) {
77
} elsif ( $op eq "cud-set_withdrawn" && $withdrawn ne $item_data_hashref->{'withdrawn'} ) {
76
    $item->withdrawn($withdrawn);
78
    $item->withdrawn($withdrawn);
79
} elsif ( $op eq "cud-set_is_closed_stack" ) {
80
    $item->is_closed_stack($is_closed_stack);
77
} elsif ( $op eq "cud-set_exclude_priority"
81
} elsif ( $op eq "cud-set_exclude_priority"
78
    && $exclude_from_local_holds_priority ne $item_data_hashref->{'exclude_from_local_holds_priority'} )
82
    && $exclude_from_local_holds_priority ne $item_data_hashref->{'exclude_from_local_holds_priority'} )
79
{
83
{
(-)a/circ/circulation.pl (-2 / +3 lines)
Lines 610-617 if ($patron) { Link Here
610
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
610
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
611
    my $waiting_holds = $holds->waiting;
611
    my $waiting_holds = $holds->waiting;
612
    $template->param(
612
    $template->param(
613
        holds_count  => $holds->count_holds,
613
        holds_count                 => $holds->filter_out_closed_stack_requests()->count_holds(),
614
        WaitingHolds => $waiting_holds,
614
        closed_stack_requests_count => $holds->filter_by_closed_stack_requests()->count_holds(),
615
        WaitingHolds                => $waiting_holds,
615
    );
616
    );
616
617
617
    if ( C4::Context->preference('UseRecalls') ) {
618
    if ( C4::Context->preference('UseRecalls') ) {
(-)a/circ/closed-stack-requests.pl (+120 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use CGI '-utf8';
21
22
use C4::Output qw( output_html_with_http_headers );
23
use C4::Auth   qw( get_template_and_user );
24
use Koha::Holds;
25
use Koha::I18N;
26
27
my $cgi = CGI->new;
28
29
my $op = $cgi->param('op') || '';
30
if ( $op eq 'cud-print_slip' ) {
31
    my $reserve_id = $cgi->param('reserve_id');
32
33
    my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
34
        {
35
            template_name => 'circ/printslip.tt',
36
            query         => $cgi,
37
            type          => 'intranet',
38
            flagsrequired => { circulate => 'circulate_remaining_permissions' },
39
        }
40
    );
41
42
    my $hold = Koha::Holds->find($reserve_id);
43
44
    my $letter = C4::Letters::GetPreparedLetter(
45
        module                 => 'reserves',
46
        letter_code            => 'CLOSED_STACK_SLIP',
47
        branchcode             => $hold->branchcode,
48
        lang                   => $hold->patron->lang,
49
        message_transport_type => 'print',
50
        tables                 => {
51
            reserves    => $hold->unblessed,
52
            branches    => $hold->branchcode,
53
            borrowers   => $hold->borrowernumber,
54
            biblio      => $hold->biblionumber,
55
            biblioitems => $hold->biblionumber,
56
            items       => $hold->itemnumber,
57
        },
58
        objects => {
59
            hold => $hold,
60
        }
61
    );
62
    if ($letter) {
63
        $template->param(
64
            title => __('Closed stack request slip'),
65
            slip  => $letter->{content},
66
            plain => !$letter->{is_html},
67
        );
68
        $hold->closed_stack_request_slip_printed(1);
69
        $hold->store();
70
    }
71
72
    output_html_with_http_headers $cgi, $cookie, $template->output;
73
    exit;
74
}
75
76
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
77
    {
78
        template_name => "circ/closed-stack-requests.tt",
79
        query         => $cgi,
80
        type          => "intranet",
81
        flagsrequired => { circulate => "circulate_remaining_permissions" },
82
    }
83
);
84
85
my @messages;
86
if ( $op eq 'cud-cancel_reserve' ) {
87
    my $reserve_id = $cgi->param('reserve_id');
88
    my $hold       = Koha::Holds->find($reserve_id);
89
    if ($hold) {
90
        my $cancellation_reason = $cgi->param('cancellation-reason');
91
        $hold->cancel( { cancellation_reason => $cancellation_reason } );
92
        push @messages, { type => 'message', code => 'hold_cancelled' };
93
    }
94
}
95
96
my $holds = Koha::Holds->search()->filter_by_closed_stack_requests();
97
98
my $branchcode = $cgi->param('branchcode') // C4::Context->userenv->{branch};
99
if ( $branchcode ne '' ) {
100
    $holds = $holds->search( { branchcode => $branchcode } );
101
}
102
$template->param( branchcode => $branchcode );
103
104
my ( @pending_holds, @printed_slip_holds );
105
106
foreach my $hold ( $holds->as_list ) {
107
    if ( $hold->closed_stack_request_slip_printed ) {
108
        push @printed_slip_holds, $hold;
109
    } else {
110
        push @pending_holds, $hold;
111
    }
112
}
113
114
$template->param(
115
    pending_holds      => \@pending_holds,
116
    printed_slip_holds => \@printed_slip_holds,
117
    messages           => \@messages,
118
);
119
120
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/installer/data/mysql/atomicupdate/closed-stack-requests.pl (+47 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number  => "38666",
5
    description => "Closed stack requests",
6
    up          => sub {
7
        my ($args) = @_;
8
        my ( $dbh, $out ) = @$args{qw(dbh out)};
9
10
        unless ( column_exists( 'items', 'is_closed_stack' ) ) {
11
            $dbh->do(
12
                "ALTER TABLE `items` ADD COLUMN `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item' AFTER `itype`"
13
            );
14
            say $out "Column items.is_closed_stack created";
15
        } else {
16
            say $out "Column items.is_closed_stack not created (already exists)";
17
        }
18
19
        unless ( column_exists( 'deleteditems', 'is_closed_stack' ) ) {
20
            $dbh->do(
21
                "ALTER TABLE `deleteditems` ADD COLUMN `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item' AFTER `itype`"
22
            );
23
            say $out "Column deleteditems.is_closed_stack created";
24
        } else {
25
            say $out "Column deleteditems.is_closed_stack not created (already exists)";
26
        }
27
28
        unless ( column_exists( 'reserves', 'closed_stack_request_slip_printed' ) ) {
29
            $dbh->do(
30
                "ALTER TABLE `reserves` ADD COLUMN `closed_stack_request_slip_printed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'boolean flag to track if closed stack request slip was printed (useful for display/filtering)' AFTER `non_priority`"
31
            );
32
            say $out "Column reserves.closed_stack_request_slip_printed created";
33
        } else {
34
            say $out "Column reserves.closed_stack_request_slip_printed not created (already exists)";
35
        }
36
37
        unless ( column_exists( 'old_reserves', 'closed_stack_request_slip_printed' ) ) {
38
            $dbh->do(
39
                "ALTER TABLE `old_reserves` ADD COLUMN `closed_stack_request_slip_printed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'boolean flag to track if closed stack request slip was printed (useful for display/filtering)' AFTER `non_priority`"
40
            );
41
            say $out "Column old_reserves.closed_stack_request_slip_printed created";
42
        } else {
43
            say $out "Column old_reserves.closed_stack_request_slip_printed not created (already exists)";
44
        }
45
46
    },
47
};
(-)a/installer/data/mysql/kohastructure.sql (+4 lines)
Lines 2829-2834 CREATE TABLE `deleteditems` ( Link Here
2829
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
2829
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
2830
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
2830
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
2831
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
2831
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
2832
  `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item',
2832
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
2833
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
2833
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
2834
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
2834
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
2835
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
Lines 4193-4198 CREATE TABLE `items` ( Link Here
4193
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4194
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4194
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4195
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4195
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4196
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4197
  `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item',
4196
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4198
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4197
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4199
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4198
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
4200
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
Lines 5143-5148 CREATE TABLE `old_reserves` ( Link Here
5143
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5145
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5144
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5146
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5145
  `hold_group_id` int(10) unsigned DEFAULT NULL COMMENT 'The id of a group of titles reservations fulfilled when one title is picked',
5147
  `hold_group_id` int(10) unsigned DEFAULT NULL COMMENT 'The id of a group of titles reservations fulfilled when one title is picked',
5148
  `closed_stack_request_slip_printed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'boolean flag to track if closed stack request slip was printed (useful for display/filtering)',
5146
  PRIMARY KEY (`reserve_id`),
5149
  PRIMARY KEY (`reserve_id`),
5147
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5150
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5148
  KEY `old_reserves_biblionumber` (`biblionumber`),
5151
  KEY `old_reserves_biblionumber` (`biblionumber`),
Lines 5715-5720 CREATE TABLE `reserves` ( Link Here
5715
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5718
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5716
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5719
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5717
  `hold_group_id` int(10) unsigned DEFAULT NULL COMMENT 'The id of a group of titles reservations fulfilled when one title is picked',
5720
  `hold_group_id` int(10) unsigned DEFAULT NULL COMMENT 'The id of a group of titles reservations fulfilled when one title is picked',
5721
  `closed_stack_request_slip_printed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'boolean flag to track if closed stack request slip was printed (useful for display/filtering)',
5718
  PRIMARY KEY (`reserve_id`),
5722
  PRIMARY KEY (`reserve_id`),
5719
  KEY `priorityfoundidx` (`priority`,`found`),
5723
  KEY `priorityfoundidx` (`priority`,`found`),
5720
  KEY `borrowernumber` (`borrowernumber`),
5724
  KEY `borrowernumber` (`borrowernumber`),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc (-2 / +5 lines)
Lines 50-59 Link Here
50
        [% END %]
50
        [% END %]
51
51
52
        [%- IF ( CAN_user_reserveforothers ) -%]
52
        [%- IF ( CAN_user_reserveforothers ) -%]
53
            <li [% IF holdsview %]class="active"[% END %]>
53
            <li [% IF holdsview && !closed_stack_request %]class="active"[% END %]>
54
                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio_object_id | url %]">Holds ([% biblio.holds.count | html %])</a>
54
                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio_object_id | url %]">Holds ([% biblio.holds.filter_out_closed_stack_requests.count | html %])</a>
55
            </li>
55
            </li>
56
        [%- END -%]
56
        [%- END -%]
57
        <li [%- IF ( holdsview && closed_stack_request ) -%]class="active"[% END %]>
58
            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio_object_id | url %]&closed_stack_request=1">Closed stack requests ([% biblio.holds.filter_by_closed_stack_requests.count | html %])</a>
59
        </li>
57
60
58
        [%- IF ( EasyAnalyticalRecords ) -%]
61
        [%- IF ( EasyAnalyticalRecords ) -%]
59
            <li [% IF analyze %]class="active"[% END %]>
62
            <li [% IF analyze %]class="active"[% END %]>
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc (+7 lines)
Lines 281-286 Link Here
281
            [% END %]
281
            [% END %]
282
        [% END %]
282
        [% END %]
283
    [% END %]
283
    [% END %]
284
285
    [% IF items.filter_by_closed_stack.count %]
286
        <div class="btn-group"
287
            ><a class="btn btn-default" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | html %]&closed_stack_request=1"><i class="fa-solid fa-bookmark"></i> Closed stack request</a></div
288
        >
289
    [% END %]
290
284
    [% IF ( Koha.Preference('EnableBooking') &&  CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
291
    [% IF ( Koha.Preference('EnableBooking') &&  CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
285
        <div class="btn-group"
292
        <div class="btn-group"
286
            ><button id="placbooking" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#placeBookingModal" data-biblionumber="[% biblionumber | html %]"><i class="fa fa-calendar"></i> Place booking</button></div
293
            ><button id="placbooking" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#placeBookingModal" data-biblionumber="[% biblionumber | html %]"><i class="fa fa-calendar"></i> Place booking</button></div
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc (+3 lines)
Lines 51-56 Link Here
51
        <li>
51
        <li>
52
            <a href="/cgi-bin/koha/circ/pendingreserves.pl">Holds to pull</a>
52
            <a href="/cgi-bin/koha/circ/pendingreserves.pl">Holds to pull</a>
53
        </li>
53
        </li>
54
        <li>
55
            <a href="/cgi-bin/koha/circ/closed-stack-requests.pl">Closed stack requests</a>
56
        </li>
54
        <li>
57
        <li>
55
            <a href="/cgi-bin/koha/circ/waitingreserves.pl">Holds awaiting pickup</a>
58
            <a href="/cgi-bin/koha/circ/waitingreserves.pl">Holds awaiting pickup</a>
56
        </li>
59
        </li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-detail-tabs.inc (+46 lines)
Lines 24-29 Link Here
24
            [% WRAPPER tab_item tabname= "holds" %]
24
            [% WRAPPER tab_item tabname= "holds" %]
25
                <span>Holds</span> ([% holds_count || 0 | html %])
25
                <span>Holds</span> ([% holds_count || 0 | html %])
26
            [% END %]
26
            [% END %]
27
            [% IF closed_stack_requests_count > 0 %]
28
                [% WRAPPER tab_item tabname="closed-stack-requests" %]
29
                    <span>Closed stack requests</span> ([% closed_stack_requests_count | html %])
30
                [% END %]
31
            [% END %]
27
            [% IF Koha.Preference('EnableBooking') %]
32
            [% IF Koha.Preference('EnableBooking') %]
28
                [% WRAPPER tab_item tabname="bookings" %]
33
                [% WRAPPER tab_item tabname="bookings" %]
29
                    [% SET bookings_count = patron.bookings.filter_by_active.count %]
34
                    [% SET bookings_count = patron.bookings.filter_by_active.count %]
Lines 193-198 Link Here
193
                [% END %]
198
                [% END %]
194
            [% END # /tab_panel#holds %]
199
            [% END # /tab_panel#holds %]
195
200
201
            [% IF closed_stack_requests_count > 0 %]
202
                [% WRAPPER tab_panel tabname="closed-stack-requests" %]
203
                    <div id="closed-stack-requests" role="tabpanel" class="tab-pane">
204
                        <form action="/cgi-bin/koha/reserve/modrequest.pl" method="post">
205
                            <input type="hidden" name="from" value="circ" />
206
207
                            <table id="closed-stack-requests-table">
208
                                <thead>
209
                                    <tr>
210
                                        <th>Hold date</th>
211
                                        <th>Title</th>
212
                                        <th>Call number</th>
213
                                        <th>Item type</th>
214
                                        <th>Barcode</th>
215
                                        <th>Expiration</th>
216
                                        <th>Priority</th>
217
                                        <th>Delete?</th>
218
                                        <th>Status</th>
219
                                    </tr>
220
                                </thead>
221
                            </table>
222
223
                            <fieldset class="action">
224
                                <input type="submit" class="cancel" name="submit" value="Cancel marked holds" />
225
226
                                [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
227
                                [% IF hold_cancellation.count %]
228
                                    <label for="cancellation-reason">Cancellation reason:</label>
229
                                    <select name="cancellation-reason">
230
                                        <option value="">No reason given</option>
231
                                        [% FOREACH reason IN hold_cancellation %]
232
                                            <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
233
                                        [% END %]
234
                                    </select>
235
                                [% END %]
236
                            </fieldset>
237
                        </form>
238
                    </div>
239
                [% END %]
240
            [% END %]
241
196
            [% IF Koha.Preference('EnableBooking') %]
242
            [% IF Koha.Preference('EnableBooking') %]
197
                [% WRAPPER tab_panel tabname="bookings" %]
243
                [% WRAPPER tab_panel tabname="bookings" %]
198
                    [% IF ( bookings_count ) %]
244
                    [% IF ( bookings_count ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt (+13 lines)
Lines 198-203 Link Here
198
                    </span>
198
                    </span>
199
                [% END %]
199
                [% END %]
200
200
201
                [% IF ( closed_stack_request_count ) %]
202
                    <span class="results_summary">
203
                        <span class="label">Closed stack requests:</span>
204
                        <span class="badge text-bg-info">
205
                            [% IF CAN_user_reserveforothers_place_holds %]
206
                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | uri %]&closed_stack_request=1">[% closed_stack_request_count | html %]</a>
207
                            [% ELSE %]
208
                                <span>[% closed_stack_request_count | html %]</span>
209
                            [% END %]
210
                        </span>
211
                    </span>
212
                [% END %]
213
201
                [% IF illrequests.count %]
214
                [% IF illrequests.count %]
202
                    <span class="results_summary">
215
                    <span class="results_summary">
203
                        <span class="label">ILL requests:</span>
216
                        <span class="label">ILL requests:</span>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt (+28 lines)
Lines 365-370 Link Here
365
                                        <li><span class="label">Withdrawn on:</span>[% ITEM_DAT.withdrawn_on | $KohaDates %]</li>
365
                                        <li><span class="label">Withdrawn on:</span>[% ITEM_DAT.withdrawn_on | $KohaDates %]</li>
366
                                    [% END %]
366
                                    [% END %]
367
                                [% END %]
367
                                [% END %]
368
369
                                <li>
370
                                    <span class="label">Closed stack:</span>
371
                                    [% IF ( CAN_user_circulate ) %]
372
                                        <form action="updateitem.pl" method="post">
373
                                            [% INCLUDE 'csrf-token.inc' %]
374
                                            <input type="hidden" name="biblionumber" value="[% ITEM_DAT.biblionumber | html %]" />
375
                                            <input type="hidden" name="biblioitemnumber" value="[% ITEM_DAT.biblioitemnumber | html %]" />
376
                                            <input type="hidden" name="itemnumber" value="[% ITEM_DAT.itemnumber | html %]" />
377
                                            <select name="is_closed_stack">
378
                                                <option value="0">No</option>
379
                                                [% IF ITEM_DAT.is_closed_stack %]
380
                                                    <option value="1" selected>Yes</option>
381
                                                [% ELSE %]
382
                                                    <option value="1">Yes</option>
383
                                                [% END %]
384
                                            </select>
385
                                            <input type="hidden" name="op" value="cud-set_is_closed_stack" />
386
                                            <input type="submit" name="submit" class="btn btn-primary btn-xs" value="Set status" />
387
                                        </form>
388
                                    [% ELSE %]
389
                                        [% IF ITEM_DAT.is_closed_stack %]
390
                                            <span>Yes</span>
391
                                        [% ELSE %]
392
                                            <span>No</span>
393
                                        [% END %]
394
                                    [% END %]
395
                                </li>
368
                            </ol>
396
                            </ol>
369
                            <!-- /.bibliodetails -->
397
                            <!-- /.bibliodetails -->
370
                        </div>
398
                        </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt (+3 lines)
Lines 85-90 Link Here
85
                    <li>
85
                    <li>
86
                        <a class="circ-button holds-to-pull" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
86
                        <a class="circ-button holds-to-pull" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
87
                    </li>
87
                    </li>
88
                    <li>
89
                        <a class="circ-button" href="/cgi-bin/koha/circ/closed-stack-requests.pl"><i class="fa-solid fa-hand-back-fist"></i> Closed stack requests</a>
90
                    </li>
88
                    <li>
91
                    <li>
89
                        <a class="circ-button holds-awaiting" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
92
                        <a class="circ-button holds-awaiting" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
90
                    </li>
93
                    </li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt (+300 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Branches %]
4
[% USE To %]
5
[% USE Koha %]
6
[% USE KohaDates %]
7
[% USE TablesSettings %]
8
[% USE AuthorisedValues %]
9
[%- USE Branches -%]
10
[%- USE ItemTypes -%]
11
[% SET footerjs = 1 %]
12
[% INCLUDE 'doc-head-open.inc' %]
13
<title>Closed stack requests &rsaquo; Circulation &rsaquo; Koha</title>
14
[% INCLUDE 'doc-head-close.inc' %]
15
</head>
16
17
<body id="circ_closed_stack_requests" class="circ">
18
[% WRAPPER 'header.inc' %]
19
    [% INCLUDE 'circ-search.inc' %]
20
[% END %]
21
22
[% WRAPPER 'sub-header.inc' %]
23
    [% WRAPPER breadcrumbs %]
24
        [% WRAPPER breadcrumb_item %]
25
            <a href="/cgi-bin/koha/circ/circulation-home.pl">Circulation</a>
26
        [% END %]
27
        [% WRAPPER breadcrumb_item bc_active= 1 %]
28
            <span>Closed stack requests</span>
29
        [% END %]
30
    [% END #/ WRAPPER breadcrumbs %]
31
[% END #/ WRAPPER sub-header.inc %]
32
33
<div class="main container-fluid">
34
    <div class="row">
35
        <div class="col-md-10 order-md-2 order-sm-1">
36
            <main>
37
                [% FOR m IN messages %]
38
                    <div class="dialog [% m.type | html %]">
39
                        [% SWITCH m.code %]
40
                        [% CASE 'hold_cancelled' %]
41
                            <span>The hold has been correctly cancelled.</span>
42
                        [% CASE %]
43
                            [% m.code | html %]
44
                        [% END %]
45
                    </div>
46
                [% END %]
47
48
                <h1>Closed stack requests</h1>
49
50
                [% BLOCK holds_table %]
51
                    [% IF holds %]
52
                        <table id="[% id | html %]">
53
                            <thead>
54
                                <tr>
55
                                    <th>Patron</th>
56
                                    <th class="anti-the">Title</th>
57
                                    <th class="string-sort">Library</th>
58
                                    <th>Barcode</th>
59
                                    <th>Call number</th>
60
                                    <th>Copy number</th>
61
                                    <th>Stock number</th>
62
                                    <th>Enumeration</th>
63
                                    <th class="string-sort">Item type</th>
64
                                    <th class="string-sort">Location</th>
65
                                    <th class="string-sort">Collection</th>
66
                                    <th>Hold date</th>
67
                                    <th>Hold notes</th>
68
                                    <th class="string-sort">Pickup location</th>
69
                                    <th data-searchable="false">Action</th>
70
                                </tr>
71
                            </thead>
72
                            <tbody>
73
                                [% FOREACH hold IN holds %]
74
                                    <tr>
75
                                        [% SET patron = hold.patron %]
76
                                        [% SET item = hold.item %]
77
                                        [% SET biblio = hold.biblio %]
78
79
                                        <td><a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% patron.borrowernumber | uri %]">[% patron.firstname | html %] [% patron.surname | html %]</a></td>
80
81
                                        <td>
82
                                            <p> [% INCLUDE 'biblio-title.inc' biblio=biblio link = 1 %] </p>
83
                                            [% IF ( biblio.author ) %]<p> by [% biblio.author | html %]</p>[% END %]
84
                                            [% IF ( biblio.biblioitem.editionstatement ) %]<p>[% biblio.biblioitem.editionstatement | html %]</p>[% END %]
85
                                            [% IF ( Koha.Preference('marcflavour') == 'MARC21' ) %]
86
                                                [% IF ( biblio.copyrightdate ) %]<p>[% biblio.copyrightdate | html %]</p>[% END %]
87
                                            [% ELSE %]
88
                                                [% IF ( biblio.biblioitem.publicationyear ) %]<p>[% biblio.biblioitem.publicationyear | html %]</p>[% END %]
89
                                            [% END %]
90
                                        </td>
91
92
                                        <td data-search="[% item.holdingbranch | html %]">[% Branches.GetName(item.holdingbranch) | html %]</td>
93
94
                                        <td>[% item.barcode | html %]</td>
95
96
                                        <td>[% item.itemcallnumber | html %]</td>
97
98
                                        <td>[% item.copynumber | html %]</td>
99
100
                                        <td>[% item.stocknumber | html %]</td>
101
102
                                        <td>[% item.enumchron | html %]</td>
103
104
                                        <td data-search="[% item.itype | html %]">[% ItemTypes.GetDescription(item.itype) | html %]</td>
105
106
                                        <td data-search="[% item.location | html %]">[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => item.location ) | html %] </td
107
                                        ><td data-search="[% item.ccode | html %]">[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => item.ccode ) | html %]</td>
108
109
                                        <td data-order="[% hold.reservedate | html %]"> [% hold.reservedate | $KohaDates %] in [% Branches.GetName ( hold.branchcode ) | html %] </td>
110
111
                                        <td>[% hold.reservenotes | html %]</td>
112
113
                                        <td data-search="[% hold.branchcode | html %]">[% Branches.GetName ( hold.branchcode ) | html %]</td>
114
115
                                        <td>
116
                                            <form method="post" id="print-closed-stack-request-slip-[% hold.reserve_id | html %]" target="_blank">
117
                                                [% INCLUDE 'csrf-token.inc' %]
118
                                                <input type="hidden" name="op" value="cud-print_slip" />
119
                                                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
120
                                                <button class="btn btn-default btn-sm print-closed-stack-request-slip" data-reserve-id="[% hold.reserve_id | html %]">Print closed stack request slip</button>
121
                                            </form>
122
123
                                            <form name="cancelReserve" action="/cgi-bin/koha/circ/closed-stack-requests.pl" method="post">
124
                                                [% INCLUDE 'csrf-token.inc' %]
125
                                                <input type="hidden" name="op" value="cud-cancel_reserve" />
126
                                                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
127
128
                                                [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
129
                                                [% IF hold_cancellation.count %]
130
                                                    <div class="form-group">
131
                                                        <label for="cancellation-reason">Cancellation reason:</label>
132
                                                        <select class="cancellation-reason" name="cancellation-reason" id="cancellation-reason">
133
                                                            <option value="">No reason given</option>
134
                                                            [% FOREACH reason IN hold_cancellation %]
135
                                                                <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
136
                                                            [% END %]
137
                                                        </select>
138
                                                    </div>
139
                                                [% END %]
140
141
                                                [% IF item.holdingbranch != item.homebranch %]
142
                                                    <button class="btn btn-default btn-sm" type="submit">Cancel hold and return to : [% Branches.GetName( item.homebranch ) | html %]</button>
143
                                                [% ELSE %]
144
                                                    <button class="btn btn-default btn-sm" type="submit">Cancel hold</button>
145
                                                [% END %]
146
                                            </form>
147
                                        </td>
148
                                    </tr>
149
                                [% END %]
150
                            </tbody>
151
                        </table>
152
                    [% ELSE %]
153
                        <strong>No items found.</strong>
154
                    [% END %]
155
                [% END %]
156
157
                [% PROCESS 'html_helpers.inc' %]
158
                [% WRAPPER tabs %]
159
                    [% WRAPPER tabs_nav %]
160
                        [% WRAPPER tab_item tabname="pending" bt_active=1 %]<span>Pending</span> ([% pending_holds.size || 0 | html %])[% END %]
161
                        [% WRAPPER tab_item tabname="printed" %]<span>Slip printed</span> ([% printed_slip_holds.size || 0 | html %])[% END %]
162
                    [% END %]
163
                    [% WRAPPER tab_panels %]
164
                        [% WRAPPER tab_panel tabname="pending" bt_active=1 %]
165
                            <p>The following holds have not been filled. Please retrieve them and check them in.</p>
166
167
                            [% INCLUDE holds_table id="holdst" holds=pending_holds %]
168
                        [% END %]
169
170
                        [% WRAPPER tab_panel tabname="printed" %]
171
                            [% INCLUDE holds_table id="printed_slip_holds" holds=printed_slip_holds %]
172
                        [% END %]
173
                    [% END %]
174
                [% END %]
175
            </main>
176
        </div>
177
        <!-- /.col-sm-10.col-sm-push-2 -->
178
        <div class="col-sm-2 col-sm-pull-10">
179
            <aside>
180
                <form>
181
                    <fieldset class="brief">
182
                        <h4>Filters</h4>
183
                        <label for="library">Library</label>
184
                        <select id="library" name="branchcode">
185
                            <option value="">All</option>
186
                            [% FOREACH library IN Branches.all() %]
187
                                [% IF branchcode && branchcode == library.branchcode %]
188
                                    <option value="[% library.branchcode | html %]" selected>[% library.branchname | html %]</option>
189
                                [% ELSE %]
190
                                    <option value="[% library.branchcode | html %]">[% library.branchname | html %]</option>
191
                                [% END %]
192
                            [% END %]
193
                        </select>
194
                    </fieldset>
195
                    <fieldset class="action">
196
                        <button class="btn btn-primary" type="submit">Filter</button>
197
                    </fieldset>
198
                </form>
199
            </aside>
200
            [% IF Koha.Preference('CircSidebar') %]
201
                <aside> [% INCLUDE 'circ-nav.inc' %] </aside>
202
            [% END %]
203
        </div>
204
        <!-- /.col-sm-2.col-sm-pull-10 -->
205
    </div>
206
    <!-- /.row -->
207
208
    [% MACRO jsinclude BLOCK %]
209
        [% INCLUDE 'calendar.inc' %]
210
        [% INCLUDE 'datatables.inc' %]
211
        <script>
212
            function separateData ( ColumnData ){
213
                var cD = ColumnData;
214
                var new_array = new Array();
215
                for ( j=0 ; j<cD.length ; j++ ) {
216
                    var split_array = cD[j].split(/\n/gi);
217
                    for ( k=0 ; k<split_array.length ; k++ ){
218
                        var str = $.trim(split_array[k].replace(/[\n\r]/g, ''));
219
                        if ($.inArray(str, new_array) == -1 && str.length > 0 ) {
220
                            new_array.push(str);
221
                        }
222
                    }
223
                }
224
                new_array.sort();
225
                return new_array;
226
            }
227
228
            function createSelect( data ) {
229
                data = separateData(data);
230
                var r='<select style="width:99%"><option value="">' + _("None") + '</option>', i, len=data.length;
231
                var regex = /(<([^>]+)>)/ig; // Remove html tags
232
                for ( i=0 ; i<len ; i++ ) {
233
                    var cell_val = data[i].replace(regex, '');
234
                    if ( cell_val.length < 1 ) continue;
235
                    r += '<option value="'+cell_val+'">'+cell_val+'</option>';
236
                }
237
                return r+'</select>';
238
            }
239
240
            $(document).ready(function() {
241
                const all_libraries = [% To.json(Branches.all) | $raw %];
242
                const libraries_filters = all_libraries.map(l => ({
243
                    _id: l.branchcode,
244
                    _str: l.branchname,
245
                }));
246
                const all_ccodes = [% To.json(AuthorisedValues.GetDescriptionsByKohaField({ kohafield => 'items.ccode' })) | $raw %].map( av => ({
247
                    _id: av.authorised_value,
248
                    _str: av.lib,
249
                }));
250
                const all_locations = [% To.json(AuthorisedValues.GetDescriptionsByKohaField({ kohafield => 'items.location' })) | $raw %].map(av => ({
251
                    _id: av.authorised_value,
252
                    _str: av.lib,
253
                }));
254
                const all_item_types = [% To.json(ItemTypes.Get) | $raw %];
255
                const item_types_filters = all_item_types.map(e => ({
256
                    _id: e.itemtype,
257
                    _str: e.translated_description,
258
                }));
259
                const filters_options = {
260
                    2: () => libraries_filters,
261
                    8: () => item_types_filters,
262
                    9: () => all_locations,
263
                    10: () => all_ccodes,
264
                };
265
                var table_settings = [% TablesSettings.GetTableSettings('circ', 'holds', 'closed-stack-requests', 'json') | $raw %];
266
                $('#holdst, #printed_slip_holds').each(function (i, el) {
267
                    const holdst = $(el).kohaTable({
268
                        "sPaginationType": "full_numbers",
269
                        autoWidth: false,
270
                    }, table_settings, true, {}, filters_options);
271
                });
272
            });
273
        </script>
274
        <script>
275
            $(document).ready(function () {
276
                // Printing slip will change the reserve's status
277
                // Reload the page to make the status change visible
278
                $("#holdst .print-closed-stack-request-slip").on("click", function (ev) {
279
                    const reserve_id = $(this).data("reserve-id");
280
                    const form = document.getElementById("print-closed-stack-request-slip-" + reserve_id);
281
                    if (form) {
282
                        form.submit();
283
                        setTimeout(() => {
284
                            location.reload();
285
                        }, 1000);
286
                    }
287
                });
288
                $("#holdst .set-waiting").on("click", function (ev) {
289
                    const reserve_id = $(this).data("reserve-id");
290
                    const form = document.getElementById("set-waiting-" + reserve_id);
291
                    if (form) {
292
                        form.submit();
293
                    }
294
                });
295
            });
296
        </script>
297
    [% END %]
298
299
    [% INCLUDE 'intranet-bottom.inc' %]
300
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt (-1 / +8 lines)
Lines 186-192 Link Here
186
            <div class="row">
186
            <div class="row">
187
                <div class="col-sm-12">
187
                <div class="col-sm-12">
188
                    [%# Following statement must be in one line for translatability %]
188
                    [%# Following statement must be in one line for translatability %]
189
                    [% IF ( CAN_user_tools_moderate_comments  && pendingcomments ) || ( CAN_user_tools_moderate_tags && pendingtags ) || ( CAN_user_borrowers_edit_borrowers && pending_borrower_modifications ) || ( CAN_user_suggestions_suggestions_manage && ( pendingsuggestions || all_pendingsuggestions )) || ( CAN_user_borrowers_edit_borrowers && pending_discharge_requests ) || pending_article_requests || ( Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count ) || ( ( Koha.Preference('OpacCatalogConcerns') || Koha.Preference('CatalogConcerns') ) && pending_biblio_tickets && CAN_user_editcatalogue_edit_catalogue ) || ( Koha.Preference('OPACReportProblem') && CAN_user_problem_reports && pending_problem_reports.count ) || already_ran_jobs || new_curbside_pickups.count || ( holds_with_cancellation_requests && CAN_user_circulate_circulate_remaining_permissions ) || self_registered_count %]
189
                    [% IF ( CAN_user_tools_moderate_comments  && pendingcomments ) || ( CAN_user_tools_moderate_tags && pendingtags ) || ( CAN_user_borrowers_edit_borrowers && pending_borrower_modifications ) || ( CAN_user_suggestions_suggestions_manage && ( pendingsuggestions || all_pendingsuggestions )) || ( CAN_user_borrowers_edit_borrowers && pending_discharge_requests ) || pending_article_requests || ( Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count ) || ( ( Koha.Preference('OpacCatalogConcerns') || Koha.Preference('CatalogConcerns') ) && pending_biblio_tickets && CAN_user_editcatalogue_edit_catalogue ) || ( Koha.Preference('OPACReportProblem') && CAN_user_problem_reports && pending_problem_reports.count ) || already_ran_jobs || new_curbside_pickups.count || ( holds_with_cancellation_requests && CAN_user_circulate_circulate_remaining_permissions ) || self_registered_count || pending_closed_stack_requests.count %]
190
                        <div id="area-pending" class="page-section">
190
                        <div id="area-pending" class="page-section">
191
                            [% IF pending_article_requests %]
191
                            [% IF pending_article_requests %]
192
                                <div class="pending-info" id="article_requests_pending">
192
                                <div class="pending-info" id="article_requests_pending">
Lines 298-303 Link Here
298
                                    [% END %]
298
                                    [% END %]
299
                                </div>
299
                                </div>
300
                            [% END %]
300
                            [% END %]
301
302
                            [% IF pending_closed_stack_requests.count %]
303
                                <div class="pending-info" id="pending_closed_stack_requests">
304
                                    <a href="/cgi-bin/koha/circ/closed-stack-requests.pl">Pending closed stack requests</a>:
305
                                    <span class="pending-number-link">[% pending_closed_stack_requests.count | html %]</span>
306
                                </div>
307
                            [% END %]
301
                        </div>
308
                        </div>
302
                    [% END %]
309
                    [% END %]
303
                </div>
310
                </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-139 / +161 lines)
Lines 137-143 Link Here
137
                [% INCLUDE 'biblio-title.inc' link =1 %]
137
                [% INCLUDE 'biblio-title.inc' link =1 %]
138
            [% END %]
138
            [% END %]
139
            [% WRAPPER breadcrumb_item bc_active= 1 %]
139
            [% WRAPPER breadcrumb_item bc_active= 1 %]
140
                <span>Place a hold</span>
140
                [% IF closed_stack_request %]
141
                    <span>Closed stack request</span>
142
                [% ELSE %]
143
                    <span>Place a hold</span>
144
                [% END %]
141
            [% END %]
145
            [% END %]
142
        [% ELSE %]
146
        [% ELSE %]
143
            [% IF ( patron ) %]
147
            [% IF ( patron ) %]
Lines 286-292 Link Here
286
    [% END %]
290
    [% END %]
287
291
288
    [% UNLESS ( multi_hold ) %]
292
    [% UNLESS ( multi_hold ) %]
289
        <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
293
        [% IF closed_stack_request %]
294
            <h2>Closed stack request on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
295
        [% ELSE %]
296
            <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
297
        [% END %]
290
    [% ELSE %]
298
    [% ELSE %]
291
        <h2>
299
        <h2>
292
            [% IF ( patron ) %]
300
            [% IF ( patron ) %]
Lines 626-631 Link Here
626
                    <input type="hidden" name="title" value="[% biblio.title | html %]" />
634
                    <input type="hidden" name="title" value="[% biblio.title | html %]" />
627
                    <input type="hidden" name="rank-request" value="[% fixedRank | html %]" />
635
                    <input type="hidden" name="rank-request" value="[% fixedRank | html %]" />
628
636
637
                    [% IF closed_stack_request %]
638
                        <input type="hidden" name="closed_stack_request" value="1" />
639
                    [% END %]
640
629
                    <ol>
641
                    <ol>
630
                        <li>
642
                        <li>
631
                            <span class="label">Patron:</span>
643
                            <span class="label">Patron:</span>
Lines 677-848 Link Here
677
                        </li>
689
                        </li>
678
                    </ol>
690
                    </ol>
679
                </fieldset>
691
                </fieldset>
680
                <fieldset class="rows any_specific">
681
                    <legend>
682
                        [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
683
                            <input type="radio" id="requestany" name="request" disabled="true" />
684
                        [% ELSIF force_hold_level == 'record' %]
685
                            <input type="radio" id="requestany" checked="checked" value="Any" disabled="true" />
686
                            <input type="hidden" name="request" value="Any" />
687
                            <span class="error"><i>(Required)</i></span>
688
                        [% ELSE %]
689
                            <input type="radio" id="requestany" name="request" checked="checked" value="Any" />
690
                        [% END %]
691
                        <label for="requestany" class="inline"> Hold next available item: </label>
692
                    </legend>
693
                    <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
694
                    <fieldset class="enable_request_any disable_request_group disable_request_specific">
695
                        [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
696
                            <span class="error">
697
                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
698
                                Hold must be item level
699
                            </span>
700
                        [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
701
                            <span class="error">
702
                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
703
                                Hold must be item group level
704
                            </span>
705
                        [% ELSE %]
706
                            <ol>
707
                                <li>
708
                                    <label for="pickup">Pickup at:</label>
709
                                    <select name="pickup" id="pickup-next-avail" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
710
                                        [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
711
                                    </select>
712
                                </li>
713
714
                                [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
715
                                    <li>
716
                                        <label for="itemtype">Request specific item type:</label>
717
                                        <select name="itemtype" id="itemtype">
718
                                            <option value="">Any item type</option>
719
                                            [%- FOREACH itemtype IN available_itemtypes %]
720
                                                <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
721
                                            [%- END %]
722
                                        </select>
723
                                    </li>
724
                                [% END %]
725
                                [% UNLESS remaining_holds_for_record == 1 %]
726
                                    <li>
727
                                        <label for="holds_to_place_count">Holds to place (count): </label>
728
                                        <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
729
                                    </li>
730
                                [% ELSE %]
731
                                    <input type="hidden" name="holds_to_place_count" value="1" />
732
                                [% END %]
733
                            </ol>
734
                        [% END %]
735
736
                        <fieldset class="action">
737
                            [% IF ( patron.borrowernumber ) %]
738
                                [% IF ( override_required ) %]
739
                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
740
                                [% ELSIF ( none_available ) %]
741
                                    <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
742
                                [% ELSE %]
743
                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
744
                                [% END %]
745
                            [% END %]
746
                        </fieldset>
747
                    </fieldset>
748
                </fieldset>
749
750
                <hr />
751
752
                [% biblio_info = biblioloop.0 %]
692
                [% biblio_info = biblioloop.0 %]
753
                <!-- ItemGroup level holds -->
693
754
                [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
694
                [% UNLESS closed_stack_request %]
755
                    <fieldset class="rows any_specific">
695
                    <fieldset class="rows any_specific">
756
                        <legend>
696
                        <legend>
757
                            [% IF force_hold_level == 'item_group' %]
697
                            [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
758
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
698
                                <input type="radio" id="requestany" name="request" disabled="true" />
699
                            [% ELSIF force_hold_level == 'record' %]
700
                                <input type="radio" id="requestany" checked="checked" value="Any" disabled="true" />
701
                                <input type="hidden" name="request" value="Any" />
759
                                <span class="error"><i>(Required)</i></span>
702
                                <span class="error"><i>(Required)</i></span>
760
                            [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
761
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
762
                            [% ELSE %]
703
                            [% ELSE %]
763
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" />
704
                                <input type="radio" id="requestany" name="request" checked="checked" value="Any" />
764
                            [% END %]
705
                            [% END %]
765
                            <label for="requestgrp" class="inline"> Hold next available item from an item group </label>
706
                            <label for="requestany" class="inline"> Hold next available item: </label>
766
                        </legend>
707
                        </legend>
767
708
                        <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
768
                        <fieldset class="enable_request_group disable_request_any disable_request_specific">
709
                        <fieldset class="enable_request_any disable_request_group disable_request_specific">
769
                            [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
710
                            [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
770
                                <span class="error">
771
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
772
                                    Hold must be record level
773
                                </span>
774
                            [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
775
                                <span class="error">
711
                                <span class="error">
776
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
712
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
777
                                    Hold must be item level
713
                                    Hold must be item level
778
                                </span>
714
                                </span>
715
                            [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
716
                                <span class="error">
717
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
718
                                    Hold must be item group level
719
                                </span>
779
                            [% ELSE %]
720
                            [% ELSE %]
780
                                <ul>
721
                                <ol>
781
                                    <li>
722
                                    <li>
782
                                        <label for="pickup">Pickup at:</label>
723
                                        <label for="pickup">Pickup at:</label>
783
                                        <select name="pickup" id="pickup-item-group" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
724
                                        <select name="pickup" id="pickup-next-avail" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
784
                                            [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
725
                                            [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
785
                                        </select>
726
                                        </select>
786
                                    </li>
727
                                    </li>
787
                                    <li>
728
788
                                        <table id="requestgroup">
729
                                    [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
789
                                            <thead>
730
                                        <li>
790
                                                <tr>
731
                                            <label for="itemtype">Request specific item type:</label>
791
                                                    <th>Hold</th>
732
                                            <select name="itemtype" id="itemtype">
792
                                                    <th>Item group</th>
733
                                                <option value="">Any item type</option>
793
                                                    <th>Holdable items</th>
734
                                                [%- FOREACH itemtype IN available_itemtypes %]
794
                                                </tr>
735
                                                    <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
795
                                            </thead>
736
                                                [%- END %]
796
                                            <tbody>
737
                                            </select>
797
                                                [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
738
                                        </li>
798
                                                    [% IF g.items.count %]
739
                                    [% END %]
799
                                                        <tr>
740
                                    [% UNLESS remaining_holds_for_record == 1 %]
800
                                                            <td>
741
                                        <li>
801
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
742
                                            <label for="holds_to_place_count">Holds to place (count): </label>
802
                                                            </td>
743
                                            <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
803
                                                            <td>
744
                                        </li>
804
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
745
                                    [% ELSE %]
805
                                                            </td>
746
                                        <input type="hidden" name="holds_to_place_count" value="1" />
806
                                                            <td>
747
                                    [% END %]
807
                                                                [% FOREACH i IN g.items %]
748
                                </ol>
808
                                                                    <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
809
                                                                [% END %]
810
                                                            </td>
811
                                                        </tr>
812
                                                    [% ELSE %]
813
                                                        <tr>
814
                                                            <td>
815
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
816
                                                            </td>
817
                                                            <td>
818
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
819
                                                            </td>
820
                                                            <td>
821
                                                                <div class="error">No holdable items in this item group.</div>
822
                                                            </td>
823
                                                        </tr>
824
                                                    [% END %]
825
                                                [% END %]
826
                                            </tbody>
827
                                        </table>
828
                                    </li>
829
                                </ul>
830
                            [% END %]
749
                            [% END %]
750
831
                            <fieldset class="action">
751
                            <fieldset class="action">
832
                                [% IF ( patron.borrowernumber ) %]
752
                                [% IF ( patron.borrowernumber ) %]
833
                                    [% IF ( override_required ) %]
753
                                    [% IF ( override_required ) %]
834
                                        <button type="submit" id="hold_any_btn" class="btn btn-primary"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
754
                                        <button type="submit" id="hold_grp_btn" class="btn btn-primary"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
835
                                    [% ELSIF ( none_available ) %]
755
                                    [% ELSIF ( none_available ) %]
836
                                        <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
756
                                        <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
837
                                    [% ELSE %]
757
                                    [% ELSE %]
838
                                        <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
758
                                        <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
839
                                    [% END %]
759
                                    [% END %]
840
                                [% END %]
760
                                [% END %]
841
                            </fieldset>
761
                            </fieldset>
842
                        </fieldset>
762
                        </fieldset>
843
                    </fieldset>
763
                    </fieldset>
764
765
                    <hr />
766
767
                    <!-- ItemGroup level holds -->
768
                    [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
769
                        <fieldset class="rows any_specific">
770
                            <legend>
771
                                [% IF force_hold_level == 'item_group' %]
772
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
773
                                    <span class="error"><i>(Required)</i></span>
774
                                [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
775
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
776
                                [% ELSE %]
777
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" />
778
                                [% END %]
779
                                <label for="requestgrp" class="inline"> Hold next available item from an item group </label>
780
                            </legend>
781
782
                            <fieldset class="enable_request_group disable_request_any disable_request_specific">
783
                                [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
784
                                    <span class="error">
785
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
786
                                        Hold must be record level
787
                                    </span>
788
                                [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
789
                                    <span class="error">
790
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
791
                                        Hold must be item level
792
                                    </span>
793
                                [% ELSE %]
794
                                    <ul>
795
                                        <li>
796
                                            <label for="pickup">Pickup at:</label>
797
                                            <select name="pickup" id="pickup-item-group" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
798
                                                [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
799
                                            </select>
800
                                        </li>
801
                                        <li>
802
                                            <table id="requestgroup">
803
                                                <thead>
804
                                                    <tr>
805
                                                        <th>Hold</th>
806
                                                        <th>Item group</th>
807
                                                        <th>Holdable items</th>
808
                                                    </tr>
809
                                                </thead>
810
                                                <tbody>
811
                                                    [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
812
                                                        [% IF g.items.count %]
813
                                                            <tr>
814
                                                                <td>
815
                                                                    <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
816
                                                                </td>
817
                                                                <td>
818
                                                                    <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
819
                                                                </td>
820
                                                                <td>
821
                                                                    [% FOREACH i IN g.items %]
822
                                                                        <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
823
                                                                    [% END %]
824
                                                                </td>
825
                                                            </tr>
826
                                                        [% ELSE %]
827
                                                            <tr>
828
                                                                <td>
829
                                                                    <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
830
                                                                </td>
831
                                                                <td>
832
                                                                    <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
833
                                                                </td>
834
                                                                <td>
835
                                                                    <div class="error">No holdable items in this item group.</div>
836
                                                                </td>
837
                                                            </tr>
838
                                                        [% END %]
839
                                                    [% END %]
840
                                                </tbody>
841
                                            </table>
842
                                        </li>
843
                                    </ul>
844
                                [% END %]
845
                                <fieldset class="action">
846
                                    [% IF ( patron.borrowernumber ) %]
847
                                        [% IF ( override_required ) %]
848
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
849
                                        [% ELSIF ( none_available ) %]
850
                                            <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
851
                                        [% ELSE %]
852
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
853
                                        [% END %]
854
                                    [% END %]
855
                                </fieldset>
856
                            </fieldset>
857
                        </fieldset>
858
                    [% END %]
859
                    <!-- /ItemGroup level holds -->
844
                [% END %]
860
                [% END %]
845
                <!-- /ItemGroup level holds -->
846
861
847
                <fieldset class="rows any_specific">
862
                <fieldset class="rows any_specific">
848
                    <legend>
863
                    <legend>
Lines 1238-1243 Link Here
1238
                                            ><li> <strong>No items are available</strong> to be placed on hold</li></ul
1253
                                            ><li> <strong>No items are available</strong> to be placed on hold</li></ul
1239
                                        >
1254
                                        >
1240
                                    [% END %]
1255
                                    [% END %]
1256
1257
                                    [% IF itemloo.is_closed_stack %]
1258
                                        <br /><span>Closed stack</span>
1259
                                    [% END %]
1241
                                </td>
1260
                                </td>
1242
                            </tr>
1261
                            </tr>
1243
                        [% END # /FOREACH biblioloo %]
1262
                        [% END # /FOREACH biblioloo %]
Lines 1579-1584 Link Here
1579
    [% IF multi_hold %]
1598
    [% IF multi_hold %]
1580
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1599
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1581
    [% END %]
1600
    [% END %]
1601
    [% IF closed_stack_request %]
1602
        [% SET url_biblio_params = url_biblio_params _ "&closed_stack_request=1" %]
1603
    [% END %]
1582
    <script>
1604
    <script>
1583
        $(document).ready(function () {
1605
        $(document).ready(function () {
1584
            hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %];
1606
            hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %];
(-)a/koha-tmpl/intranet-tmpl/prog/js/holds.js (+265 lines)
Lines 518-523 $(document).ready(function () { Link Here
518
                        url: "/cgi-bin/koha/svc/holds",
518
                        url: "/cgi-bin/koha/svc/holds",
519
                        data: function (d) {
519
                        data: function (d) {
520
                            d.borrowernumber = borrowernumber;
520
                            d.borrowernumber = borrowernumber;
521
                            d.closed_stack_request = 0;
521
                        },
522
                        },
522
                    },
523
                    },
523
                    bKohaAjaxSVC: true,
524
                    bKohaAjaxSVC: true,
Lines 1148-1151 $(document).ready(function () { Link Here
1148
            '<input type="hidden" name="reserve_id" value="' + hold.hold + '">'
1149
            '<input type="hidden" name="reserve_id" value="' + hold.hold + '">'
1149
        );
1150
        );
1150
    }
1151
    }
1152
1153
    // Don't load holds table unless it is clicked on
1154
    $("#closed-stack-requests-tab").on("click", function () {
1155
        load_closed_stack_requests_table();
1156
    });
1157
1158
    // If the holds tab is preselected on load, we need to load the table
1159
    if ($("#closed-stack-requests-tab").parent().hasClass("active")) {
1160
        load_closed_stack_requests_table();
1161
    }
1162
1163
    function load_closed_stack_requests_table() {
1164
        var holds = new Array();
1165
        if (!$.fn.DataTable.isDataTable($("#closed-stack-requests-table"))) {
1166
            var title;
1167
            const table = $("#closed-stack-requests-table").dataTable(
1168
                $.extend(true, {}, dataTablesDefaults, {
1169
                    bAutoWidth: false,
1170
                    sDom: "rt",
1171
                    columns: [
1172
                        {
1173
                            data: {
1174
                                _: "reservedate_formatted",
1175
                                sort: "reservedate",
1176
                            },
1177
                        },
1178
                        {
1179
                            mDataProp: function (oObj) {
1180
                                title =
1181
                                    "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" +
1182
                                    oObj.biblionumber +
1183
                                    "'>" +
1184
                                    (oObj.title ? oObj.title.escapeHtml() : "");
1185
1186
                                $.each(oObj.subtitle, function (index, value) {
1187
                                    title += " " + value.escapeHtml();
1188
                                });
1189
1190
                                title +=
1191
                                    " " +
1192
                                    oObj.part_number +
1193
                                    " " +
1194
                                    oObj.part_name;
1195
1196
                                if (oObj.enumchron) {
1197
                                    title +=
1198
                                        " (" +
1199
                                        oObj.enumchron.escapeHtml() +
1200
                                        ")";
1201
                                }
1202
1203
                                title += "</a>";
1204
1205
                                if (oObj.author) {
1206
                                    title +=
1207
                                        " " +
1208
                                        __("by _AUTHOR_").replace(
1209
                                            "_AUTHOR_",
1210
                                            oObj.author.escapeHtml()
1211
                                        );
1212
                                }
1213
1214
                                if (oObj.itemnotes) {
1215
                                    var span_class = "";
1216
                                    if (
1217
                                        flatpickr.formatDate(
1218
                                            new Date(oObj.issuedate),
1219
                                            "Y-m-d"
1220
                                        ) == ymd
1221
                                    ) {
1222
                                        span_class = "circ-hlt";
1223
                                    }
1224
                                    title +=
1225
                                        " - <span class='" +
1226
                                        span_class +
1227
                                        "'>" +
1228
                                        oObj.itemnotes.escapeHtml() +
1229
                                        "</span>";
1230
                                }
1231
1232
                                return title;
1233
                            },
1234
                        },
1235
                        {
1236
                            mDataProp: function (oObj) {
1237
                                return (
1238
                                    (oObj.itemcallnumber &&
1239
                                        oObj.itemcallnumber.escapeHtml()) ||
1240
                                    ""
1241
                                );
1242
                            },
1243
                        },
1244
                        {
1245
                            mDataProp: function (oObj) {
1246
                                var data = "";
1247
                                if (oObj.itemtype) {
1248
                                    data += oObj.itemtype_description;
1249
                                }
1250
                                return data;
1251
                            },
1252
                        },
1253
                        {
1254
                            mDataProp: function (oObj) {
1255
                                var data = "";
1256
                                if (oObj.barcode) {
1257
                                    data +=
1258
                                        " <a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=" +
1259
                                        oObj.biblionumber +
1260
                                        "&itemnumber=" +
1261
                                        oObj.itemnumber +
1262
                                        "#item" +
1263
                                        oObj.itemnumber +
1264
                                        "'>" +
1265
                                        oObj.barcode.escapeHtml() +
1266
                                        "</a>";
1267
                                }
1268
                                return data;
1269
                            },
1270
                        },
1271
                        {
1272
                            data: {
1273
                                _: "expirationdate_formatted",
1274
                                sort: "expirationdate",
1275
                            },
1276
                        },
1277
                        {
1278
                            mDataProp: function (oObj) {
1279
                                if (
1280
                                    oObj.priority &&
1281
                                    parseInt(oObj.priority) &&
1282
                                    parseInt(oObj.priority) > 0
1283
                                ) {
1284
                                    return oObj.priority;
1285
                                } else {
1286
                                    return "";
1287
                                }
1288
                            },
1289
                        },
1290
                        {
1291
                            bSortable: false,
1292
                            mDataProp: function (oObj) {
1293
                                return (
1294
                                    "<select name='rank-request'>" +
1295
                                    "<option value='n'>" +
1296
                                    __("No") +
1297
                                    "</option>" +
1298
                                    "<option value='del'>" +
1299
                                    __("Yes") +
1300
                                    "</option>" +
1301
                                    "</select>" +
1302
                                    "<input type='hidden' name='biblionumber' value='" +
1303
                                    oObj.biblionumber +
1304
                                    "'>" +
1305
                                    "<input type='hidden' name='borrowernumber' value='" +
1306
                                    borrowernumber +
1307
                                    "'>" +
1308
                                    "<input type='hidden' name='reserve_id' value='" +
1309
                                    oObj.reserve_id +
1310
                                    "'>"
1311
                                );
1312
                            },
1313
                        },
1314
                        {
1315
                            mDataProp: function (oObj) {
1316
                                var data = "";
1317
1318
                                if (oObj.suspend == 1) {
1319
                                    data +=
1320
                                        "<p>" +
1321
                                        __(
1322
                                            "Hold is <strong>suspended</strong>"
1323
                                        );
1324
                                    if (oObj.suspend_until) {
1325
                                        data +=
1326
                                            " " +
1327
                                            __("until %s").format(
1328
                                                oObj.suspend_until_formatted
1329
                                            );
1330
                                    }
1331
                                    data += "</p>";
1332
                                }
1333
1334
                                if (oObj.itemtype_limit) {
1335
                                    data += __("Next available %s item").format(
1336
                                        oObj.itemtype_limit
1337
                                    );
1338
                                }
1339
1340
                                if (oObj.item_group_id) {
1341
                                    data += __(
1342
                                        "Next available item group <strong>%s</strong> item"
1343
                                    ).format(oObj.item_group_description);
1344
                                }
1345
1346
                                if (oObj.barcode) {
1347
                                    data += "<em>";
1348
                                    if (oObj.found == "W") {
1349
                                        if (oObj.waiting_here) {
1350
                                            data += __(
1351
                                                "Item is <strong>waiting here</strong>"
1352
                                            );
1353
                                            if (oObj.desk_name) {
1354
                                                data +=
1355
                                                    ", " +
1356
                                                    __("at %s").format(
1357
                                                        oObj.desk_name.escapeHtml()
1358
                                                    );
1359
                                            }
1360
                                        } else {
1361
                                            data += __(
1362
                                                "Item is <strong>waiting</strong>"
1363
                                            );
1364
                                            data +=
1365
                                                " " +
1366
                                                __("at %s").format(
1367
                                                    oObj.waiting_at
1368
                                                );
1369
                                            if (oObj.desk_name) {
1370
                                                data +=
1371
                                                    ", " +
1372
                                                    __("at %s").format(
1373
                                                        oObj.desk_name.escapeHtml()
1374
                                                    );
1375
                                            }
1376
                                        }
1377
                                    } else if (oObj.transferred) {
1378
                                        data += __(
1379
                                            "Item is <strong>in transit</strong> from %s since %s"
1380
                                        ).format(
1381
                                            oObj.from_branch,
1382
                                            oObj.date_sent
1383
                                        );
1384
                                    } else if (oObj.not_transferred) {
1385
                                        data += __(
1386
                                            "Item hasn't been transferred yet from %s"
1387
                                        ).format(oObj.not_transferred_by);
1388
                                    }
1389
                                    data += "</em>";
1390
                                }
1391
                                return data;
1392
                            },
1393
                        },
1394
                    ],
1395
                    bPaginate: false,
1396
                    bProcessing: true,
1397
                    bServerSide: false,
1398
                    ajax: {
1399
                        url: "/cgi-bin/koha/svc/holds",
1400
                        data: function (d) {
1401
                            d.borrowernumber = borrowernumber;
1402
                            d.closed_stack_request = 1;
1403
                        },
1404
                    },
1405
                })
1406
            );
1407
1408
            if ($("#closed-stack-requests-table").length) {
1409
                $("#closed-stack-requests-table_processing").position({
1410
                    of: $("#closed-stack-requests-table"),
1411
                    collision: "none",
1412
                });
1413
            }
1414
        }
1415
    }
1151
});
1416
});
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc (-1 / +2 lines)
Lines 4-10 Link Here
4
[% USE KohaDates %]
4
[% USE KohaDates %]
5
[% PROCESS 'i18n.inc' %]
5
[% PROCESS 'i18n.inc' %]
6
6
7
<table id="holdst" class="table table-bordered table-striped">
7
[% DEFAULT table_id = 'holdst' %]
8
<table id="[% table_id | html %]" class="table table-bordered table-striped">
8
    <caption>Holds <span class="count">([% HOLDS.count | html %] total)</span></caption>
9
    <caption>Holds <span class="count">([% HOLDS.count | html %] total)</span></caption>
9
    <!-- HOLDS TABLE ROWS -->
10
    <!-- HOLDS TABLE ROWS -->
10
    <thead>
11
    <thead>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc (+8 lines)
Lines 10-15 Link Here
10
                    ><a class="reserve btn btn-link btn-lg" href="/cgi-bin/koha/opac-reserve.pl?biblionumber=[% biblio.biblionumber | html %]"><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> Place hold</a></li
10
                    ><a class="reserve btn btn-link btn-lg" href="/cgi-bin/koha/opac-reserve.pl?biblionumber=[% biblio.biblionumber | html %]"><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> Place hold</a></li
11
                >
11
                >
12
            [% END %]
12
            [% END %]
13
14
            [% IF biblio.items.filter_by_closed_stack.count > 0 %]
15
                <li
16
                    ><a class="btn btn-link btn-lg" href="/cgi-bin/koha/opac-reserve.pl?biblionumber=[% biblio.biblionumber | html %]&closed_stack_request=1"
17
                        ><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> Closed stack request</a
18
                    ></li
19
                >
20
            [% END %]
13
        [% END %]
21
        [% END %]
14
    [% END %]
22
    [% END %]
15
23
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-reserve.tt (-7 / +24 lines)
Lines 14-20 Link Here
14
[% INCLUDE 'doc-head-open.inc' %]
14
[% INCLUDE 'doc-head-open.inc' %]
15
<title
15
<title
16
    >[% FILTER collapse %]
16
    >[% FILTER collapse %]
17
        [% t("Placing a hold") | html %]
17
        [% closed_stack_request ? t("Closed stack request") : t("Placing a hold") | html %]
18
        &rsaquo; [% PROCESS 'library_name_title' %]
18
        &rsaquo; [% PROCESS 'library_name_title' %]
19
    [% END %]</title
19
    [% END %]</title
20
>
20
>
Lines 29-41 Link Here
29
<main class="main">
29
<main class="main">
30
    [% WRAPPER breadcrumbs %]
30
    [% WRAPPER breadcrumbs %]
31
        [% WRAPPER breadcrumb_item bc_active= 1 %]
31
        [% WRAPPER breadcrumb_item bc_active= 1 %]
32
            <span>Placing a hold</span>
32
            [% IF closed_stack_request %]
33
                <span>Closed stack request</span>
34
            [% ELSE %]
35
                <span>Placing a hold</span>
36
            [% END %]
33
        [% END %]
37
        [% END %]
34
    [% END #/ WRAPPER breadcrumbs %]
38
    [% END #/ WRAPPER breadcrumbs %]
35
39
36
    <div class="container-fluid">
40
    <div class="container-fluid">
37
        <div id="holds" class="maincontent">
41
        <div id="holds" class="maincontent">
38
            <h1>Placing a hold</h1>
42
            [% IF closed_stack_request %]
43
                <h1>Closed stack request</h1>
44
            [% ELSE %]
45
                <h1>Placing a hold</h1>
46
            [% END %]
39
            [% IF ( message ) %]
47
            [% IF ( message ) %]
40
                <div id="holdmessages" class="alert alert-warning">
48
                <div id="holdmessages" class="alert alert-warning">
41
                    <h2>Sorry, you cannot place holds.</h2>
49
                    <h2>Sorry, you cannot place holds.</h2>
Lines 106-112 Link Here
106
114
107
            [% UNLESS ( message ) %]
115
            [% UNLESS ( message ) %]
108
                [% UNLESS ( none_available ) %]
116
                [% UNLESS ( none_available ) %]
109
                    <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
117
                    [% IF closed_stack_request %]
118
                        <h2>Confirm closed stack request for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
119
                    [% ELSE %]
120
                        <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
121
                    [% END %]
110
                [% END # / UNLESS none_available %]
122
                [% END # / UNLESS none_available %]
111
123
112
                [% IF ( new_reserves_allowed ) %]
124
                [% IF ( new_reserves_allowed ) %]
Lines 129-134 Link Here
129
                            </fieldset>
141
                            </fieldset>
130
                        </div>
142
                        </div>
131
                    [% END %]
143
                    [% END %]
144
145
                    [% IF closed_stack_request %]
146
                        <input type="hidden" name="closed_stack_request" value="1" />
147
                    [% END %]
148
132
                    <!-- These values are set dynamically by js -->
149
                    <!-- These values are set dynamically by js -->
133
                    <input type="hidden" name="biblionumbers" id="biblionumbers" />
150
                    <input type="hidden" name="biblionumbers" id="biblionumbers" />
134
                    <input type="hidden" name="selecteditems" id="selections" />
151
                    <input type="hidden" name="selecteditems" id="selections" />
Lines 138-146 Link Here
138
                                [% IF bibitemloo.forced_hold_level %]
155
                                [% IF bibitemloo.forced_hold_level %]
139
                                    <div class="alert alert-info forced_hold_level">
156
                                    <div class="alert alert-info forced_hold_level">
140
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
157
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
141
                                            <span>You already have at least one item level hold on this title. All further holds must be item level.</span>
158
                                            <span>Hold policy requires holds to be item level.</span>
142
                                        [% ELSE %]
159
                                        [% ELSE %]
143
                                            <span>You already have at least one record level hold on this title. All further holds must be record level.</span>
160
                                            <span>Hold policy requires holds to be record level.</span>
144
                                        [% END %]
161
                                        [% END %]
145
                                    </div>
162
                                    </div>
146
                                [% END %]
163
                                [% END %]
Lines 234-240 Link Here
234
                                                </li>
251
                                                </li>
235
                                            [% END %]
252
                                            [% END %]
236
253
237
                                            [% UNLESS ( singleBranchMode ) %]
254
                                            [% UNLESS ( singleBranchMode || closed_stack_request ) %]
238
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
255
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
239
                                                    <li class="branch">
256
                                                    <li class="branch">
240
                                                        <label for="branch_[% bibitemloo.biblionumber | html %]">Pick up location:</label>
257
                                                        <label for="branch_[% bibitemloo.biblionumber | html %]">Pick up location:</label>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (-1 / +14 lines)
Lines 275-280 Link Here
275
                                    <span>Holds ([% reserves_count | html %])</span>
275
                                    <span>Holds ([% reserves_count | html %])</span>
276
                                [% END %]
276
                                [% END %]
277
                            [% END %]
277
                            [% END %]
278
                            [% IF ( closed_stack_requests.count ) %]
279
                                [% WRAPPER tab_item tabname="opac-user-closed-stack-requests" %]
280
                                    <span>Closed stack requests ([% closed_stack_requests.count | html %])</span>
281
                                [% END %]
282
                            [% END %]
278
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
283
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
279
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
284
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
280
                                    <span>Recalls ([% RECALLS.count | html %])</span>
285
                                    <span>Recalls ([% RECALLS.count | html %])</span>
Lines 826-831 Link Here
826
                                [% END # /tab_panel#opac-user-holds %]
831
                                [% END # /tab_panel#opac-user-holds %]
827
                            [% END # / #RESERVES.count %]
832
                            [% END # / #RESERVES.count %]
828
833
834
                            [% IF ( closed_stack_requests.count ) %]
835
                                [% WRAPPER tab_panel tabname="opac-user-closed-stack-requests" %]
836
                                    [% PROCESS 'holds-table.inc' HOLDS = closed_stack_requests, SuspendHoldsOpac = SuspendHoldsOpac, showpriority = showpriority, AutoResumeSuspendedHolds = AutoResumeSuspendedHolds, table_id = 'closed-stack-requests-table' %]
837
                                [% END %]
838
                            [% END %]
839
829
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
840
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
830
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
841
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
831
                                    <table id="recalls-table" class="table table-bordered table-striped">
842
                                    <table id="recalls-table" class="table table-bordered table-striped">
Lines 1067-1072 Link Here
1067
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1078
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1068
            [% ELSIF ( opac_user_overdues ) %]
1079
            [% ELSIF ( opac_user_overdues ) %]
1069
                $("#opac-user-views a[href='#opac-user-overdues_panel']").tab("show");
1080
                $("#opac-user-views a[href='#opac-user-overdues_panel']").tab("show");
1081
            [% ELSIF ( opac_user_closed_stack_requests ) %]
1082
                $("#opac-user-views a[href='#opac-user-closed-stack-requests_panel']").tab("show");
1070
            [% ELSIF ( opac_user_article_requests ) %]
1083
            [% ELSIF ( opac_user_article_requests ) %]
1071
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1084
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1072
            [% END %]
1085
            [% END %]
Lines 1175-1181 Link Here
1175
                );
1188
                );
1176
            });
1189
            });
1177
1190
1178
            var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table");
1191
            var dTables = $("#checkoutst,#holdst,#closed-stack-requests-table,#overduest,#opac-user-relative-issues-table");
1179
            dTables.each(function(){
1192
            dTables.each(function(){
1180
                var thIndex = $(this).find("th.psort").index();
1193
                var thIndex = $(this).find("th.psort").index();
1181
                $(this).on("init.dt", function() {
1194
                $(this).on("init.dt", function() {
(-)a/mainpage.pl (+5 lines)
Lines 153-158 if ( C4::Context->preference('PatronSelfRegistrationAlert') ) { Link Here
153
    );
153
    );
154
}
154
}
155
155
156
my $pending_closed_stack_requests =
157
    Koha::Holds->search( { branchcode => C4::Context->userenv->{branch} } )->filter_by_closed_stack_requests()
158
    ->search( { closed_stack_request_slip_printed => 0 } );
159
156
$template->param(
160
$template->param(
157
    pendingcomments                => $pendingcomments,
161
    pendingcomments                => $pendingcomments,
158
    pendingtags                    => $pendingtags,
162
    pendingtags                    => $pendingtags,
Lines 160-165 $template->param( Link Here
160
    pending_discharge_requests     => $pending_discharge_requests,
164
    pending_discharge_requests     => $pending_discharge_requests,
161
    pending_article_requests       => $pending_article_requests,
165
    pending_article_requests       => $pending_article_requests,
162
    pending_problem_reports        => $pending_problem_reports,
166
    pending_problem_reports        => $pending_problem_reports,
167
    pending_closed_stack_requests  => $pending_closed_stack_requests,
163
);
168
);
164
169
165
output_html_with_http_headers $query, $cookie, $template->output;
170
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/members/moremember.pl (-16 / +16 lines)
Lines 210-216 if ( $patron->is_expired || $patron->is_going_to_expire ) { Link Here
210
my $holds         = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
210
my $holds         = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
211
my $waiting_holds = $holds->waiting;
211
my $waiting_holds = $holds->waiting;
212
$template->param(
212
$template->param(
213
    holds_count  => $holds->count_holds,
214
    WaitingHolds => $waiting_holds,
213
    WaitingHolds => $waiting_holds,
215
);
214
);
216
215
Lines 292-312 my $has_modifications = Koha::Patron::Modifications->search( { borrowernumber = Link Here
292
my $patron_lists_count = $patron->get_lists_with_patron->count();
291
my $patron_lists_count = $patron->get_lists_with_patron->count();
293
292
294
$template->param(
293
$template->param(
295
    patron                 => $patron,
294
    patron                      => $patron,
296
    issuecount             => $patron->checkouts->count,
295
    issuecount                  => $patron->checkouts->count,
297
    holds_count            => $patron->holds->count_holds,
296
    holds_count                 => $patron->holds->filter_out_closed_stack_requests()->count_holds,
298
    fines                  => $patron->account->balance,
297
    closed_stack_requests_count => $patron->holds->filter_by_closed_stack_requests()->count_holds,
299
    translated_language    => $translated_language,
298
    fines                       => $patron->account->balance,
300
    detailview             => 1,
299
    translated_language         => $translated_language,
301
    was_renewed            => scalar $input->param('was_renewed') ? 1 : 0,
300
    detailview                  => 1,
302
    $category_type         => 1,                                           # [% IF ( I ) %] = institutional/organisation
301
    was_renewed                 => scalar $input->param('was_renewed') ? 1 : 0,
303
    housebound_role        => scalar $patron->housebound_role,
302
    $category_type              => 1,                                 # [% IF ( I ) %] = institutional/organisation
304
    relatives_issues_count => $relatives_issues_count,
303
    housebound_role             => scalar $patron->housebound_role,
305
    relatives_borrowernumbers => \@relatives,
304
    relatives_issues_count      => $relatives_issues_count,
306
    logged_in_user            => $logged_in_user,
305
    relatives_borrowernumbers   => \@relatives,
307
    files                     => Koha::Patron::Files->new( borrowernumber => $borrowernumber )->GetFilesInfo(),
306
    logged_in_user              => $logged_in_user,
308
    has_modifications         => $has_modifications,
307
    files                       => Koha::Patron::Files->new( borrowernumber => $borrowernumber )->GetFilesInfo(),
309
    patron_lists_count        => $patron_lists_count,
308
    has_modifications           => $has_modifications,
309
    patron_lists_count          => $patron_lists_count,
310
);
310
);
311
311
312
if ( C4::Context->preference('UseRecalls') ) {
312
if ( C4::Context->preference('UseRecalls') ) {
(-)a/opac/opac-reserve.pl (-8 / +23 lines)
Lines 88-93 if ( !$biblionumbers ) { Link Here
88
    $biblionumbers = $query->param('biblionumber');
88
    $biblionumbers = $query->param('biblionumber');
89
}
89
}
90
90
91
my $closed_stack_request = $query->param('closed_stack_request');
92
$template->param( closed_stack_request => $closed_stack_request );
93
91
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
94
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
92
    $template->param( message => 1, no_biblionumber => 1 );
95
    $template->param( message => 1, no_biblionumber => 1 );
93
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
96
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
Lines 228-233 if ( $op eq 'cud-place_reserve' ) { Link Here
228
            }
231
            }
229
        }
232
        }
230
233
234
        if ( $closed_stack_request && $item ) {
235
            $branch = $item->holdingbranch;
236
        }
237
231
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
238
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
232
        if ($item) {
239
        if ($item) {
233
            $biblioNum = $item->biblionumber;
240
            $biblioNum = $item->biblionumber;
Lines 324-332 if ( $op eq 'cud-place_reserve' ) { Link Here
324
        }
331
        }
325
    }
332
    }
326
333
334
    my $param = $closed_stack_request ? 'opac-user-closed-stack-requests' : 'opac-user-holds';
327
    print $query->redirect( "/cgi-bin/koha/opac-user.pl?"
335
    print $query->redirect( "/cgi-bin/koha/opac-user.pl?"
328
            . ( @failed_holds ? "failed_holds=" . join( '|', @failed_holds ) : q|| )
336
            . ( @failed_holds ? "failed_holds=" . join( '|', @failed_holds ) : q|| )
329
            . "&opac-user-holds=1" );
337
            . "&$param=1" );
330
    exit;
338
    exit;
331
}
339
}
332
340
Lines 432-437 foreach my $biblioNum (@biblionumbers) { Link Here
432
    # it's complicated logic to analyse.
440
    # it's complicated logic to analyse.
433
    # (before this loop was inside that sub loop so it was O(n^2) )
441
    # (before this loop was inside that sub loop so it was O(n^2) )
434
    foreach my $item ( @{ $biblioData->{items} } ) {
442
    foreach my $item ( @{ $biblioData->{items} } ) {
443
        next if ( $closed_stack_request xor $item->is_available_for_closed_stack_request );
435
444
436
        my $item_info = $item->unblessed;
445
        my $item_info = $item->unblessed;
437
        $item_info->{holding_branch} = $item->holding_branch;
446
        $item_info->{holding_branch} = $item->holding_branch;
Lines 569-581 foreach my $biblioNum (@biblionumbers) { Link Here
569
    # patron placed a record level hold, all the holds the patron places must
578
    # patron placed a record level hold, all the holds the patron places must
570
    # be record level. If the patron placed an item level hold, all holds
579
    # be record level. If the patron placed an item level hold, all holds
571
    # the patron places must be item level
580
    # the patron places must be item level
572
    my $forced_hold_level = Koha::Holds->search(
581
    # Unless the biblio itself forces a specific hold level which
573
        {
582
    # supersedes the above rules
574
            borrowernumber => $borrowernumber,
583
    my $forced_hold_level = $biblio->forced_hold_level;
575
            biblionumber   => $biblioNum,
584
    unless ($forced_hold_level) {
576
            found          => undef,
585
        $forced_hold_level = Koha::Holds->search(
577
        }
586
            {
578
    )->forced_hold_level();
587
                borrowernumber => $borrowernumber,
588
                biblionumber   => $biblioNum,
589
                found          => undef,
590
            }
591
        )->forced_hold_level();
592
    }
593
579
    if ($forced_hold_level) {
594
    if ($forced_hold_level) {
580
        $biblioLoopIter{force_hold}        = 1 if $forced_hold_level eq 'item';
595
        $biblioLoopIter{force_hold}        = 1 if $forced_hold_level eq 'item';
581
        $biblioLoopIter{force_hold}        = 0 if $forced_hold_level eq 'item_group';
596
        $biblioLoopIter{force_hold}        = 0 if $forced_hold_level eq 'item_group';
(-)a/opac/opac-user.pl (-4 / +8 lines)
Lines 344-355 if ($show_barcode) { Link Here
344
$template->param( show_barcode => 1 ) if $show_barcode;
344
$template->param( show_barcode => 1 ) if $show_barcode;
345
345
346
# now the reserved items....
346
# now the reserved items....
347
my $reserves = $patron->holds->filter_out_has_cancellation_requests;
347
my $reserves              = $patron->holds->filter_out_has_cancellation_requests->filter_out_closed_stack_requests;
348
my $closed_stack_requests = $patron->holds->filter_out_has_cancellation_requests->filter_by_closed_stack_requests;
348
349
349
$template->param(
350
$template->param(
350
    RESERVES       => $reserves,
351
    RESERVES              => $reserves,
351
    reserves_count => $reserves->count_holds,
352
    reserves_count        => $reserves->count_holds,
352
    showpriority   => $show_priority,
353
    closed_stack_requests => $closed_stack_requests,
354
    showpriority          => $show_priority,
353
);
355
);
354
356
355
if ( C4::Context->preference('UseRecalls') ) {
357
if ( C4::Context->preference('UseRecalls') ) {
Lines 433-438 $template->param( Link Here
433
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
435
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
434
    opac_user_overdues         => scalar $query->param('opac-user-overdues')         || 0,
436
    opac_user_overdues         => scalar $query->param('opac-user-overdues')         || 0,
435
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
437
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
438
439
    opac_user_closed_stack_requests => scalar $query->param('opac-user-closed-stack-requests') || 0,
436
);
440
);
437
441
438
# if not an empty string this indicates to return
442
# if not an empty string this indicates to return
(-)a/reserve/placerequest.pl (-1 / +5 lines)
Lines 177-183 if ( $op eq 'cud-placerequest' && $patron ) { Link Here
177
    foreach my $msg ( keys %failed_holds ) {
177
    foreach my $msg ( keys %failed_holds ) {
178
        push( @failed_hold_msgs, $msg );
178
        push( @failed_hold_msgs, $msg );
179
    }
179
    }
180
    $redirect_url->query_form( biblionumber => [@biblionumbers], failed_holds => \@failed_hold_msgs );
180
    my %params = ( biblionumber => [@biblionumbers], failed_holds => \@failed_hold_msgs );
181
    if ( $input->param('closed_stack_request') ) {
182
        $params{closed_stack_request} = 1;
183
    }
184
    $redirect_url->query_form(%params);
181
    print $input->redirect($redirect_url);
185
    print $input->redirect($redirect_url);
182
} elsif ( $borrowernumber eq '' ) {
186
} elsif ( $borrowernumber eq '' ) {
183
    print $input->header();
187
    print $input->header();
(-)a/reserve/request.pl (-5 / +19 lines)
Lines 64-71 my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user( Link Here
64
    }
64
    }
65
);
65
);
66
66
67
my $showallitems = $input->param('showallitems');
67
my $showallitems         = $input->param('showallitems');
68
my $pickup       = $input->param('pickup');
68
my $pickup               = $input->param('pickup');
69
my $closed_stack_request = $input->param('closed_stack_request');
69
70
70
my $itemtypes = {
71
my $itemtypes = {
71
    map {
72
    map {
Lines 405-410 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
405
            # patron placed a record level hold, all the holds the patron places must
406
            # patron placed a record level hold, all the holds the patron places must
406
            # be record level. If the patron placed an item level hold, all holds
407
            # be record level. If the patron placed an item level hold, all holds
407
            # the patron places must be item level
408
            # the patron places must be item level
409
            # Unless the biblio itself forces a specific hold level which
410
            # supersedes the above rules
408
            my $holds = Koha::Holds->search(
411
            my $holds = Koha::Holds->search(
409
                {
412
                {
410
                    borrowernumber => $patron->borrowernumber,
413
                    borrowernumber => $patron->borrowernumber,
Lines 412-418 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
412
                    found          => undef,
415
                    found          => undef,
413
                }
416
                }
414
            );
417
            );
415
            $template->param( force_hold_level => $holds->forced_hold_level() );
418
419
            my $forced_hold_level = $biblio->forced_hold_level // $holds->forced_hold_level();
420
            $template->param( force_hold_level => $forced_hold_level );
416
421
417
            # For a librarian to be able to place multiple record holds for a patron for a record,
422
            # For a librarian to be able to place multiple record holds for a patron for a record,
418
            # we must find out what the maximum number of holds they can place for the patron is
423
            # we must find out what the maximum number of holds they can place for the patron is
Lines 454-459 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
454
            # (before this loop was inside that sub loop so it was O(n^2) )
459
            # (before this loop was inside that sub loop so it was O(n^2) )
455
460
456
            for my $item_object (@items) {
461
            for my $item_object (@items) {
462
                next if ( $closed_stack_request xor $item_object->is_available_for_closed_stack_request );
463
457
                my $do_check;
464
                my $do_check;
458
                my $item = $item_object->unblessed;
465
                my $item = $item_object->unblessed;
459
                $item->{object} = $item_object;
466
                $item->{object} = $item_object;
Lines 683-690 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
683
                    )->unblessed
690
                    )->unblessed
684
                }
691
                }
685
            };
692
            };
686
            my @reserves =
693
            my $holds_rs = Koha::Holds->search( { 'me.biblionumber' => $biblionumber }, { order_by => 'priority' } );
687
                Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list;
694
            if ($closed_stack_request) {
695
                $holds_rs = $holds_rs->filter_by_closed_stack_requests();
696
            } else {
697
                $holds_rs = $holds_rs->filter_out_closed_stack_requests();
698
            }
699
            my @reserves = $holds_rs->as_list;
688
            foreach my $res (
700
            foreach my $res (
689
                sort {
701
                sort {
690
                    my $a_found = $a->found() || '';
702
                    my $a_found = $a->found() || '';
Lines 806-811 $template->param( borrowernumber => $borrowernumber_hold ); Link Here
806
818
807
$template->param( failed_holds => \@failed_holds );
819
$template->param( failed_holds => \@failed_holds );
808
820
821
$template->param( closed_stack_request => $closed_stack_request );
822
809
# printout the page
823
# printout the page
810
output_html_with_http_headers $input, $cookie, $template->output;
824
output_html_with_http_headers $input, $cookie, $template->output;
811
825
(-)a/svc/holds (-1 / +10 lines)
Lines 55-60 my $sorting_direction = $input->param('sSortDir_0') || 'desc'; Link Here
55
my $iSortCol          = $input->param('iSortCol_0') // 0;
55
my $iSortCol          = $input->param('iSortCol_0') // 0;
56
my $sorting_column    = $sort_columns[$iSortCol]    // 'reservedate';
56
my $sorting_column    = $sort_columns[$iSortCol]    // 'reservedate';
57
57
58
my $closed_stack_request = $input->param('closed_stack_request');
59
58
binmode STDOUT, ":encoding(UTF-8)";
60
binmode STDOUT, ":encoding(UTF-8)";
59
print $input->header( -type => 'application/json', -charset => 'UTF-8' );
61
print $input->header( -type => 'application/json', -charset => 'UTF-8' );
60
62
Lines 63-68 my $holds_rs = Koha::Holds->search( Link Here
63
    { order_by       => { "-$sorting_direction" => $sorting_column } }
65
    { order_by       => { "-$sorting_direction" => $sorting_column } }
64
);
66
);
65
67
68
if ( defined $closed_stack_request ) {
69
    if ($closed_stack_request) {
70
        $holds_rs = $holds_rs->filter_by_closed_stack_requests();
71
    } else {
72
        $holds_rs = $holds_rs->filter_out_closed_stack_requests();
73
    }
74
}
75
66
my @holds;
76
my @holds;
67
while ( my $h = $holds_rs->next() ) {
77
while ( my $h = $holds_rs->next() ) {
68
    my $item       = $h->item();
78
    my $item       = $h->item();
69
- 

Return to bug 38666