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

(-)a/C4/Reserves.pm (-31 / +42 lines)
Lines 196-206 sub AddReserve { Link Here
196
196
197
    $resdate ||= dt_from_string;
197
    $resdate ||= dt_from_string;
198
198
199
    my $item = $checkitem ? Koha::Items->find($checkitem) : undef;
200
199
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
201
    # if we have an item selectionned, and the pickup branch is the same as the holdingbranch
200
    # of the document, we force the value $priority and $found .
202
    # of the document, we force the value $priority and $found .
201
    if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) {
203
    if ( $item and not C4::Context->preference('ReservesNeedReturns') ) {
202
        my $item = Koha::Items->find($checkitem);    # FIXME Prevent bad calls
203
204
        if (
204
        if (
205
            # If item is already checked out, it cannot be set waiting
205
            # If item is already checked out, it cannot be set waiting
206
            !$item->onloan
206
            !$item->onloan
Lines 862-875 sub CheckReserves { Link Here
862
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
862
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
863
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
863
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
864
864
865
    my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
865
    unless ( $item->is_closed_stack ) {
866
    if ( !$dont_trap ) {
866
        my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
867
        my $item_type = $item->effective_itemtype;
867
        if ( !$dont_trap ) {
868
        if ($item_type) {
868
            my $item_type = $item->effective_itemtype;
869
            return if Koha::ItemTypes->find($item_type)->notforloan;
869
            if ($item_type) {
870
                return if Koha::ItemTypes->find($item_type)->notforloan;
871
            }
872
        } else {
873
            return;
870
        }
874
        }
871
    } else {
872
        return;
873
    }
875
    }
874
876
875
    # Find this item in the reserves
877
    # Find this item in the reserves
Lines 1367-1378 sub IsAvailableForItemLevelRequest { Link Here
1367
        unless defined $itemtype;
1369
        unless defined $itemtype;
1368
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1370
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1369
1371
1372
    # Closed stack items can be requested even if marked as "not for loan"
1373
    unless ( $item->is_closed_stack ) {
1374
        return 0
1375
            if $notforloan_per_itemtype
1376
            || $item->notforloan > 0;    # item with negative or zero notforloan value is holdable
1377
    }
1378
1370
    return 0
1379
    return 0
1371
        if $notforloan_per_itemtype
1380
        if $item->itemlost
1372
        || $item->itemlost
1381
        || $item->withdrawn
1373
        || $item->notforloan > 0
1374
        ||    # item with negative or zero notforloan value is holdable
1375
        $item->withdrawn
1376
        || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
1382
        || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') );
1377
1383
1378
    if ($pickup_branchcode) {
1384
    if ($pickup_branchcode) {
Lines 1388-1416 sub IsAvailableForItemLevelRequest { Link Here
1388
            || $home_library->validate_hold_sibling( { branchcode => $pickup_branchcode } );
1394
            || $home_library->validate_hold_sibling( { branchcode => $pickup_branchcode } );
1389
    }
1395
    }
1390
1396
1391
    my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1397
    unless ( $item->is_closed_stack ) {
1398
        my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1392
1399
1393
    if ( $on_shelf_holds == 1 ) {
1400
        if ( $on_shelf_holds == 1 ) {
1394
        return 1;
1401
            return 1;
1395
    } elsif ( $on_shelf_holds == 2 ) {
1402
        } elsif ( $on_shelf_holds == 2 ) {
1396
1403
1397
        # These calculations work at the biblio level, and can be expensive
1404
            # These calculations work at the biblio level, and can be expensive
1398
        # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1405
            # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1399
1406
1400
        my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1407
            my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1401
        my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1408
            my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1402
1409
1403
        my $any_available = $memory_cache->get_from_cache($cache_key);
1410
            my $any_available = $memory_cache->get_from_cache($cache_key);
1404
        return $any_available ? 0 : 1 if defined($any_available);
1411
            return $any_available ? 0 : 1 if defined($any_available);
1405
1412
1406
        $any_available =
1413
            $any_available =
1407
            ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1414
                ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1408
        $memory_cache->set_in_cache( $cache_key, $any_available );
1415
            $memory_cache->set_in_cache( $cache_key, $any_available );
1409
        return $any_available ? 0 : 1;
1416
            return $any_available ? 0 : 1;
1410
1417
1411
    } else {  # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1418
        } else
1412
        return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1419
        {    # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1420
            return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1421
        }
1413
    }
1422
    }
1423
1424
    return 1;
1414
}
1425
}
1415
1426
1416
=head2 ItemsAnyAvailableAndNotRestricted
1427
=head2 ItemsAnyAvailableAndNotRestricted
(-)a/Koha/Biblio.pm (+33 lines)
Lines 2349-2354 sub merge_with { Link Here
2349
    return \%results;
2349
    return \%results;
2350
}
2350
}
2351
2351
2352
=head3 forced_hold_level
2353
2354
Returns forced hold level for a biblio
2355
2356
Returns 'item' if item-level hold should be forced, or undef
2357
2358
=cut
2359
2360
sub forced_hold_level {
2361
    my ($self) = @_;
2362
2363
    my $forced_hold_level;
2364
2365
    # If there is at least one closed stack item that is not checked out and
2366
    # not reserved, force item-level hold
2367
    my $available_closed_stack_items = $self->items->search(
2368
        {
2369
            is_closed_stack       => 1,
2370
            onloan                => undef,
2371
            'reserves.reserve_id' => undef,
2372
        },
2373
        {
2374
            join => 'reserves',
2375
        },
2376
    );
2377
2378
    if ( $available_closed_stack_items->count > 0 ) {
2379
        $forced_hold_level = 'item';
2380
    }
2381
2382
    return $forced_hold_level;
2383
}
2384
2352
=head2 Internal methods
2385
=head2 Internal methods
2353
2386
2354
=head3 type
2387
=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 435-442 This is used from the OPAC. Link Here
435
sub is_cancelable_from_opac {
435
sub is_cancelable_from_opac {
436
    my ($self) = @_;
436
    my ($self) = @_;
437
437
438
    return 1 unless $self->is_found();
438
    return 0 if $self->is_found();
439
    return 0;    # if ->is_in_transit or if ->is_waiting or ->is_in_processing
439
    return 0 if $self->item && $self->item->is_closed_stack;
440
441
    return 1;
440
}
442
}
441
443
442
=head3 cancellation_requestable_from_opac
444
=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 206-211 sub filter_out_has_cancellation_requests { Link Here
206
    );
208
    );
207
}
209
}
208
210
211
=head3 filter_by_closed_stack_requests
212
213
Apply filter to keep only closed stack requests
214
215
    $holds = Koha::Holds->search($filter)->filter_by_closed_stack_requests();
216
217
Returns a C<Koha::Holds> object
218
219
=cut
220
221
sub filter_by_closed_stack_requests {
222
    my ($self) = @_;
223
224
    return $self->search( $self->_closed_stack_request_filter, { join => 'item' } );
225
}
226
227
=head3 filter_out_closed_stack_requests
228
229
Apply filter to exclude closed stack requests
230
231
    $holds = Koha::Holds->search($filter)->filter_out_closed_stack_requests();
232
233
Returns a C<Koha::Holds> object
234
235
=cut
236
237
sub filter_out_closed_stack_requests {
238
    my ($self) = @_;
239
240
    return $self->search( { -not_bool => $self->_closed_stack_request_filter }, { join => 'item' } );
241
}
242
243
sub _closed_stack_request_filter {
244
245
    # This query returns the top priority reserve's id for each item
246
    my $reserve_id_subselect = q{
247
        select reserve_id from (
248
            select reserve_id, itemnumber, row_number() over (partition by itemnumber order by priority) rank
249
            from reserves where itemnumber is not null
250
        ) r
251
        where rank = 1
252
    };
253
254
    my %where = (
255
        'me.reserve_id'        => { -in => \$reserve_id_subselect },
256
        'me.suspend'           => 0,
257
        'me.found'             => undef,
258
        'me.priority'          => { '!=' => 0 },
259
        'item.itemlost'        => 0,
260
        'item.withdrawn'       => 0,
261
        'item.onloan'          => undef,
262
        'item.is_closed_stack' => 1,
263
        'item.itemnumber'      => {
264
            -not_in => \'SELECT itemnumber FROM branchtransfers WHERE datearrived IS NULL AND datecancelled IS NULL'
265
        },
266
    );
267
268
    if ( !C4::Context->preference('AllowHoldsOnDamagedItems') ) {
269
        $where{'item.damaged'} = 0;
270
    }
271
272
    if ( C4::Context->only_my_library() ) {
273
        $where{'me.branchcode'} = C4::Context->userenv->{'branch'};
274
    }
275
276
    return \%where;
277
}
278
209
=head2 Internal methods
279
=head2 Internal methods
210
280
211
=head3 _type
281
=head3 _type
(-)a/Koha/Item.pm (-2 / +33 lines)
Lines 1397-1404 sub columns_to_str { Link Here
1397
              $subfield
1397
              $subfield
1398
            ? $subfield->{authorised_value}
1398
            ? $subfield->{authorised_value}
1399
                ? C4::Biblio::GetAuthorisedValueDesc(
1399
                ? C4::Biblio::GetAuthorisedValueDesc(
1400
                    $itemtagfield,
1400
                $itemtagfield,
1401
                    $subfield->{tagsubfield}, $value, '', $tagslib
1401
                $subfield->{tagsubfield}, $value, '', $tagslib
1402
                )
1402
                )
1403
                : $value
1403
                : $value
1404
            : $value;
1404
            : $value;
Lines 2536-2541 sub analytics_count { Link Here
2536
    return C4::Items::GetAnalyticsCount( $self->itemnumber );
2536
    return C4::Items::GetAnalyticsCount( $self->itemnumber );
2537
}
2537
}
2538
2538
2539
=head3 is_available_for_closed_stack_request
2540
2541
Returns 1 if item is available for a closed stack request, 0 otherwise
2542
2543
An item is available for a closed stack request if:
2544
2545
=over
2546
2547
=item * it is flagged as "closed stack"
2548
2549
=item * there is no holds on it
2550
2551
=item * it is not checked out
2552
2553
=item * it is not in transfer
2554
2555
=back
2556
2557
=cut
2558
2559
sub is_available_for_closed_stack_request {
2560
    my ($self) = @_;
2561
2562
    return 0 unless $self->is_closed_stack;
2563
    return 0 if $self->holds->count > 0;
2564
    return 0 if $self->checkout;
2565
    return 0 if $self->get_transfers->count > 0;
2566
2567
    return 1;
2568
}
2569
2539
=head3 strings_map
2570
=head3 strings_map
2540
2571
2541
Returns a map of column name to string representations including the string,
2572
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 225-230 sub filter_by_bookable { Link Here
225
    );
235
    );
226
}
236
}
227
237
238
=head3 filter_by_closed_stack
239
240
  my $filterd_items = $items->filter_by_closed_stack;
241
242
Returns a new resultset, containing only those items that are flagged as
243
"closed stack".
244
245
=cut
246
247
sub filter_by_closed_stack {
248
    my ($self) = @_;
249
250
    return $self->search( { is_closed_stack => 1 } );
251
}
252
228
=head3 move_to_biblio
253
=head3 move_to_biblio
229
254
230
 $items->move_to_biblio($to_biblio);
255
 $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 (+20 lines)
Lines 1965-1970 modules: Link Here
1965
            -
1965
            -
1966
              columnname: print_slip
1966
              columnname: print_slip
1967
1967
1968
      closed-stack-requests:
1969
        default_display_length: 20
1970
        default_sort_order: 0
1971
        columns:
1972
            - columnname: patron
1973
            - columnname: title
1974
            - columnname: libraries
1975
            - columnname: barcodes
1976
            - columnname: call_numbers
1977
            - columnname: copy_numbers
1978
            - columnname: stocknumber
1979
            - columnname: enumeration
1980
            - columnname: itemtypes
1981
            - columnname: locations
1982
            - columnname: collection
1983
            - columnname: hold_date
1984
            - columnname: reserve_notes
1985
            - columnname: pickup_location
1986
            - columnname: action
1987
1968
    holdsratios:
1988
    holdsratios:
1969
      holds-ratios:
1989
      holds-ratios:
1970
        default_display_length: 20
1990
        default_display_length: 20
(-)a/api/v1/swagger/definitions/item.yaml (+4 lines)
Lines 207-212 properties: Link Here
207
      - string
207
      - string
208
      - "null"
208
      - "null"
209
    description: Itemtype defining the type for this item
209
    description: Itemtype defining the type for this item
210
  is_closed_stack:
211
    type:
212
      - boolean
213
      - "null"
210
  effective_item_type_id:
214
  effective_item_type_id:
211
    type:
215
    type:
212
      - string
216
      - string
(-)a/catalogue/detail.pl (-1 / +4 lines)
Lines 499-505 if ( C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pre Link Here
499
499
500
#we only need to pass the number of holds to the template
500
#we only need to pass the number of holds to the template
501
my $holds = $biblio->holds;
501
my $holds = $biblio->holds;
502
$template->param( holdcount => $holds->count );
502
$template->param(
503
    holdcount                  => $holds->filter_out_closed_stack_requests->count,
504
    closed_stack_request_count => $holds->filter_by_closed_stack_requests->count,
505
);
503
506
504
# Check if there are any ILL requests connected to the biblio
507
# Check if there are any ILL requests connected to the biblio
505
my $illrequests =
508
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 595-602 if ($patron) { Link Here
595
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
595
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
596
    my $waiting_holds = $holds->waiting;
596
    my $waiting_holds = $holds->waiting;
597
    $template->param(
597
    $template->param(
598
        holds_count  => $holds->count(),
598
        holds_count                 => $holds->filter_out_closed_stack_requests()->count(),
599
        WaitingHolds => $waiting_holds,
599
        closed_stack_requests_count => $holds->filter_by_closed_stack_requests()->count(),
600
        WaitingHolds                => $waiting_holds,
600
    );
601
    );
601
602
602
    if ( C4::Context->preference('UseRecalls') ) {
603
    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 2825-2830 CREATE TABLE `deleteditems` ( Link Here
2825
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
2825
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
2826
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
2826
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
2827
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
2827
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
2828
  `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item',
2828
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
2829
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
2829
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
2830
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
2830
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
2831
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
Lines 4146-4151 CREATE TABLE `items` ( Link Here
4146
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4147
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4147
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4148
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4148
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4149
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4150
  `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item',
4149
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4151
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4150
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4152
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4151
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
4153
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
Lines 5090-5095 CREATE TABLE `old_reserves` ( Link Here
5090
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5092
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5091
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5093
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5092
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5094
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5095
  `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)',
5093
  PRIMARY KEY (`reserve_id`),
5096
  PRIMARY KEY (`reserve_id`),
5094
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5097
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5095
  KEY `old_reserves_biblionumber` (`biblionumber`),
5098
  KEY `old_reserves_biblionumber` (`biblionumber`),
Lines 5660-5665 CREATE TABLE `reserves` ( Link Here
5660
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5663
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5661
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5664
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5662
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5665
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5666
  `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)',
5663
  PRIMARY KEY (`reserve_id`),
5667
  PRIMARY KEY (`reserve_id`),
5664
  KEY `priorityfoundidx` (`priority`,`found`),
5668
  KEY `priorityfoundidx` (`priority`,`found`),
5665
  KEY `borrowernumber` (`borrowernumber`),
5669
  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 (+6 lines)
Lines 284-289 Link Here
284
        [% END %]
284
        [% END %]
285
    [% END %]
285
    [% END %]
286
286
287
    [% IF items.filter_by_closed_stack.count %]
288
        <div class="btn-group"
289
            ><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
290
        >
291
    [% END %]
292
287
    [% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
293
    [% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
288
        <div class="btn-group"
294
        <div class="btn-group"
289
            ><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
295
            ><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 23-28 Link Here
23
            [% WRAPPER tab_item tabname= "holds" %]
23
            [% WRAPPER tab_item tabname= "holds" %]
24
                <span>Holds</span> ([% holds_count || 0 | html %])
24
                <span>Holds</span> ([% holds_count || 0 | html %])
25
            [% END %]
25
            [% END %]
26
            [% IF closed_stack_requests_count > 0 %]
27
                [% WRAPPER tab_item tabname="closed-stack-requests" %]
28
                    <span>Closed stack requests</span> ([% closed_stack_requests_count | html %])
29
                [% END %]
30
            [% END %]
26
            [% WRAPPER tab_item tabname="bookings" %]
31
            [% WRAPPER tab_item tabname="bookings" %]
27
                [% SET bookings_count = patron.bookings.filter_by_active.count %]
32
                [% SET bookings_count = patron.bookings.filter_by_active.count %]
28
                [% SET expired_bookings_count = patron.bookings.count - bookings_count %]
33
                [% SET expired_bookings_count = patron.bookings.count - bookings_count %]
Lines 214-219 Link Here
214
                [% END %]
219
                [% END %]
215
            [% END # /tab_panel#holds %]
220
            [% END # /tab_panel#holds %]
216
221
222
            [% IF closed_stack_requests_count > 0 %]
223
                [% WRAPPER tab_panel tabname="closed-stack-requests" %]
224
                    <div id="closed-stack-requests" role="tabpanel" class="tab-pane">
225
                        <form action="/cgi-bin/koha/reserve/modrequest.pl" method="post">
226
                            <input type="hidden" name="from" value="circ" />
227
228
                            <table id="closed-stack-requests-table">
229
                                <thead>
230
                                    <tr>
231
                                        <th>Hold date</th>
232
                                        <th>Title</th>
233
                                        <th>Call number</th>
234
                                        <th>Item type</th>
235
                                        <th>Barcode</th>
236
                                        <th>Expiration</th>
237
                                        <th>Priority</th>
238
                                        <th>Delete?</th>
239
                                        <th>Status</th>
240
                                    </tr>
241
                                </thead>
242
                            </table>
243
244
                            <fieldset class="action">
245
                                <input type="submit" class="cancel" name="submit" value="Cancel marked holds" />
246
247
                                [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
248
                                [% IF hold_cancellation.count %]
249
                                    <label for="cancellation-reason">Cancellation reason:</label>
250
                                    <select name="cancellation-reason">
251
                                        <option value="">No reason given</option>
252
                                        [% FOREACH reason IN hold_cancellation %]
253
                                            <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
254
                                        [% END %]
255
                                    </select>
256
                                [% END %]
257
                            </fieldset>
258
                        </form>
259
                    </div>
260
                [% END %]
261
            [% END %]
262
217
            [% WRAPPER tab_panel tabname="bookings" %]
263
            [% WRAPPER tab_panel tabname="bookings" %]
218
                [% IF ( bookings_count ) %]
264
                [% IF ( bookings_count ) %]
219
                    <fieldset class="action filters" style="cursor:pointer;">
265
                    <fieldset class="action filters" style="cursor:pointer;">
(-)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 355-360 Link Here
355
                                        <li><span class="label">Withdrawn on:</span>[% ITEM_DAT.withdrawn_on | $KohaDates %]</li>
355
                                        <li><span class="label">Withdrawn on:</span>[% ITEM_DAT.withdrawn_on | $KohaDates %]</li>
356
                                    [% END %]
356
                                    [% END %]
357
                                [% END %]
357
                                [% END %]
358
359
                                <li>
360
                                    <span class="label">Closed stack:</span>
361
                                    [% IF ( CAN_user_circulate ) %]
362
                                        <form action="updateitem.pl" method="post">
363
                                            [% INCLUDE 'csrf-token.inc' %]
364
                                            <input type="hidden" name="biblionumber" value="[% ITEM_DAT.biblionumber | html %]" />
365
                                            <input type="hidden" name="biblioitemnumber" value="[% ITEM_DAT.biblioitemnumber | html %]" />
366
                                            <input type="hidden" name="itemnumber" value="[% ITEM_DAT.itemnumber | html %]" />
367
                                            <select name="is_closed_stack">
368
                                                <option value="0">No</option>
369
                                                [% IF ITEM_DAT.is_closed_stack %]
370
                                                    <option value="1" selected>Yes</option>
371
                                                [% ELSE %]
372
                                                    <option value="1">Yes</option>
373
                                                [% END %]
374
                                            </select>
375
                                            <input type="hidden" name="op" value="cud-set_is_closed_stack" />
376
                                            <input type="submit" name="submit" class="btn btn-primary btn-xs" value="Set status" />
377
                                        </form>
378
                                    [% ELSE %]
379
                                        [% IF ITEM_DAT.is_closed_stack %]
380
                                            <span>Yes</span>
381
                                        [% ELSE %]
382
                                            <span>No</span>
383
                                        [% END %]
384
                                    [% END %]
385
                                </li>
358
                            </ol>
386
                            </ol>
359
                            <!-- /.bibliodetails -->
387
                            <!-- /.bibliodetails -->
360
                        </div>
388
                        </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt (+3 lines)
Lines 84-89 Link Here
84
                <li>
84
                <li>
85
                    <a class="circ-button" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
85
                    <a class="circ-button" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
86
                </li>
86
                </li>
87
                <li>
88
                    <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>
89
                </li>
87
                <li>
90
                <li>
88
                    <a class="circ-button" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
91
                    <a class="circ-button" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
89
                </li>
92
                </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 182-188 Link Here
182
            <div class="row">
182
            <div class="row">
183
                <div class="col-sm-12">
183
                <div class="col-sm-12">
184
                    [%# Following statement must be in one line for translatability %]
184
                    [%# Following statement must be in one line for translatability %]
185
                    [% 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 %]
185
                    [% 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 %]
186
                        <div id="area-pending" class="page-section">
186
                        <div id="area-pending" class="page-section">
187
                            [% IF pending_article_requests %]
187
                            [% IF pending_article_requests %]
188
                                <div class="pending-info" id="article_requests_pending">
188
                                <div class="pending-info" id="article_requests_pending">
Lines 294-299 Link Here
294
                                    [% END %]
294
                                    [% END %]
295
                                </div>
295
                                </div>
296
                            [% END %]
296
                            [% END %]
297
298
                            [% IF pending_closed_stack_requests.count %]
299
                                <div class="pending-info" id="pending_closed_stack_requests">
300
                                    <a href="/cgi-bin/koha/circ/closed-stack-requests.pl">Pending closed stack requests</a>:
301
                                    <span class="pending-number-link">[% pending_closed_stack_requests.count | html %]</span>
302
                                </div>
303
                            [% END %]
297
                        </div>
304
                        </div>
298
                    [% END %]
305
                    [% END %]
299
                </div>
306
                </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-139 / +162 lines)
Lines 125-131 Link Here
125
                [% INCLUDE 'biblio-title.inc' link =1 %]
125
                [% INCLUDE 'biblio-title.inc' link =1 %]
126
            [% END %]
126
            [% END %]
127
            [% WRAPPER breadcrumb_item bc_active= 1 %]
127
            [% WRAPPER breadcrumb_item bc_active= 1 %]
128
                <span>Place a hold</span>
128
                [% IF closed_stack_request %]
129
                    <span>Closed stack request</span>
130
                [% ELSE %]
131
                    <span>Place a hold</span>
132
                [% END %]
129
            [% END %]
133
            [% END %]
130
        [% ELSE %]
134
        [% ELSE %]
131
            [% IF ( patron ) %]
135
            [% IF ( patron ) %]
Lines 222-228 Link Here
222
    [% END %]
226
    [% END %]
223
227
224
    [% UNLESS ( multi_hold ) %]
228
    [% UNLESS ( multi_hold ) %]
225
        <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
229
        [% IF closed_stack_request %]
230
            <h2>Closed stack request on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
231
        [% ELSE %]
232
            <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %]by [% biblio.author | html %][% END %]</h2>
233
        [% END %]
226
    [% ELSE %]
234
    [% ELSE %]
227
        <h2>
235
        <h2>
228
            [% IF ( patron ) %]
236
            [% IF ( patron ) %]
Lines 562-567 Link Here
562
                    <input type="hidden" name="title" value="[% biblio.title | html %]" />
570
                    <input type="hidden" name="title" value="[% biblio.title | html %]" />
563
                    <input type="hidden" name="rank-request" value="[% fixedRank | html %]" />
571
                    <input type="hidden" name="rank-request" value="[% fixedRank | html %]" />
564
572
573
                    [% IF closed_stack_request %]
574
                        <input type="hidden" name="closed_stack_request" value="1" />
575
                    [% END %]
576
565
                    <ol>
577
                    <ol>
566
                        <li>
578
                        <li>
567
                            <span class="label">Patron:</span>
579
                            <span class="label">Patron:</span>
Lines 613-784 Link Here
613
                        </li>
625
                        </li>
614
                    </ol>
626
                    </ol>
615
                </fieldset>
627
                </fieldset>
616
                <fieldset class="rows any_specific">
617
                    <legend>
618
                        [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
619
                            <input type="radio" id="requestany" name="request" disabled="true" />
620
                        [% ELSIF force_hold_level == 'record' %]
621
                            <input type="radio" id="requestany" checked="checked" value="Any" disabled="true" />
622
                            <input type="hidden" name="request" value="Any" />
623
                            <span class="error"><i>(Required)</i></span>
624
                        [% ELSE %]
625
                            <input type="radio" id="requestany" name="request" checked="checked" value="Any" />
626
                        [% END %]
627
                        <label for="requestany" class="inline"> Hold next available item </label>
628
                    </legend>
629
                    <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
630
                    <fieldset class="enable_request_any disable_request_group disable_request_specific">
631
                        [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
632
                            <span class="error">
633
                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
634
                                Hold must be item level
635
                            </span>
636
                        [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
637
                            <span class="error">
638
                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
639
                                Hold must be item group level
640
                            </span>
641
                        [% ELSE %]
642
                            <ol>
643
                                <li>
644
                                    <label for="pickup">Pickup at:</label>
645
                                    <select name="pickup" id="pickup-next-avail" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
646
                                        [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
647
                                    </select>
648
                                </li>
649
650
                                [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
651
                                    <li>
652
                                        <label for="itemtype">Request specific item type:</label>
653
                                        <select name="itemtype" id="itemtype">
654
                                            <option value="">Any item type</option>
655
                                            [%- FOREACH itemtype IN available_itemtypes %]
656
                                                <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
657
                                            [%- END %]
658
                                        </select>
659
                                    </li>
660
                                [% END %]
661
                                [% UNLESS remaining_holds_for_record == 1 %]
662
                                    <li>
663
                                        <label for="holds_to_place_count">Holds to place (count)</label>
664
                                        <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
665
                                    </li>
666
                                [% ELSE %]
667
                                    <input type="hidden" name="holds_to_place_count" value="1" />
668
                                [% END %]
669
                            </ol>
670
                        [% END %]
671
672
                        <fieldset class="action">
673
                            [% IF ( patron.borrowernumber ) %]
674
                                [% IF ( override_required ) %]
675
                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
676
                                [% ELSIF ( none_available ) %]
677
                                    <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
678
                                [% ELSE %]
679
                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
680
                                [% END %]
681
                            [% END %]
682
                        </fieldset>
683
                    </fieldset>
684
                </fieldset>
685
686
                <hr />
687
688
                [% biblio_info = biblioloop.0 %]
628
                [% biblio_info = biblioloop.0 %]
689
                <!-- ItemGroup level holds -->
629
690
                [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
630
                [% UNLESS closed_stack_request %]
691
                    <fieldset class="rows any_specific">
631
                    <fieldset class="rows any_specific">
692
                        <legend>
632
                        <legend>
693
                            [% IF force_hold_level == 'item_group' %]
633
                            [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
694
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
634
                                <input type="radio" id="requestany" name="request" disabled="true" />
635
                            [% ELSIF force_hold_level == 'record' %]
636
                                <input type="radio" id="requestany" checked="checked" value="Any" disabled="true" />
637
                                <input type="hidden" name="request" value="Any" />
695
                                <span class="error"><i>(Required)</i></span>
638
                                <span class="error"><i>(Required)</i></span>
696
                            [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
697
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
698
                            [% ELSE %]
639
                            [% ELSE %]
699
                                <input type="radio" class="requestgrp" id="requestgrp" name="request" />
640
                                <input type="radio" id="requestany" name="request" checked="checked" value="Any" />
700
                            [% END %]
641
                            [% END %]
701
                            <label for="requestgrp" class="inline"> Hold next available item from an item group </label>
642
                            <label for="requestany" class="inline"> Hold next available item </label>
702
                        </legend>
643
                        </legend>
703
644
                        <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
704
                        <fieldset class="enable_request_group disable_request_any disable_request_specific">
645
                        <fieldset class="enable_request_any disable_request_group disable_request_specific">
705
                            [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
646
                            [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
706
                                <span class="error">
707
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
708
                                    Hold must be record level
709
                                </span>
710
                            [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
711
                                <span class="error">
647
                                <span class="error">
712
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
648
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
713
                                    Hold must be item level
649
                                    Hold must be item level
714
                                </span>
650
                                </span>
651
                            [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
652
                                <span class="error">
653
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
654
                                    Hold must be item group level
655
                                </span>
715
                            [% ELSE %]
656
                            [% ELSE %]
716
                                <ul>
657
                                <ol>
717
                                    <li>
658
                                    <li>
718
                                        <label for="pickup">Pickup at:</label>
659
                                        <label for="pickup">Pickup at:</label>
719
                                        <select name="pickup" id="pickup-item-group" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
660
                                        <select name="pickup" id="pickup-next-avail" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
720
                                            [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
661
                                            [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
721
                                        </select>
662
                                        </select>
722
                                    </li>
663
                                    </li>
723
                                    <li>
664
724
                                        <table id="requestgroup">
665
                                    [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
725
                                            <thead>
666
                                        <li>
726
                                                <tr>
667
                                            <label for="itemtype">Request specific item type:</label>
727
                                                    <th>Hold</th>
668
                                            <select name="itemtype" id="itemtype">
728
                                                    <th>Item group</th>
669
                                                <option value="">Any item type</option>
729
                                                    <th>Holdable items</th>
670
                                                [%- FOREACH itemtype IN available_itemtypes %]
730
                                                </tr>
671
                                                    <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
731
                                            </thead>
672
                                                [%- END %]
732
                                            <tbody>
673
                                            </select>
733
                                                [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
674
                                        </li>
734
                                                    [% IF g.items.count %]
675
                                    [% END %]
735
                                                        <tr>
676
                                    [% UNLESS remaining_holds_for_record == 1 %]
736
                                                            <td>
677
                                        <li>
737
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
678
                                            <label for="holds_to_place_count">Holds to place (count)</label>
738
                                                            </td>
679
                                            <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
739
                                                            <td>
680
                                        </li>
740
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
681
                                    [% ELSE %]
741
                                                            </td>
682
                                        <input type="hidden" name="holds_to_place_count" value="1" />
742
                                                            <td>
683
                                    [% END %]
743
                                                                [% FOREACH i IN g.items %]
684
                                </ol>
744
                                                                    <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
745
                                                                [% END %]
746
                                                            </td>
747
                                                        </tr>
748
                                                    [% ELSE %]
749
                                                        <tr>
750
                                                            <td>
751
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
752
                                                            </td>
753
                                                            <td>
754
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
755
                                                            </td>
756
                                                            <td>
757
                                                                <div class="error">No holdable items in this item group.</div>
758
                                                            </td>
759
                                                        </tr>
760
                                                    [% END %]
761
                                                [% END %]
762
                                            </tbody>
763
                                        </table>
764
                                    </li>
765
                                </ul>
766
                            [% END %]
685
                            [% END %]
686
767
                            <fieldset class="action">
687
                            <fieldset class="action">
768
                                [% IF ( patron.borrowernumber ) %]
688
                                [% IF ( patron.borrowernumber ) %]
769
                                    [% IF ( override_required ) %]
689
                                    [% IF ( override_required ) %]
770
                                        <button type="submit" id="hold_any_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
690
                                        <button type="submit" id="hold_grp_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
771
                                    [% ELSIF ( none_available ) %]
691
                                    [% ELSIF ( none_available ) %]
772
                                        <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
692
                                        <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
773
                                    [% ELSE %]
693
                                    [% ELSE %]
774
                                        <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
694
                                        <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
775
                                    [% END %]
695
                                    [% END %]
776
                                [% END %]
696
                                [% END %]
777
                            </fieldset>
697
                            </fieldset>
778
                        </fieldset>
698
                        </fieldset>
779
                    </fieldset>
699
                    </fieldset>
700
701
                    <hr />
702
703
                    [% biblio_info = biblioloop.0 %]
704
                    <!-- ItemGroup level holds -->
705
                    [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
706
                        <fieldset class="rows any_specific">
707
                            <legend>
708
                                [% IF force_hold_level == 'item_group' %]
709
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
710
                                    <span class="error"><i>(Required)</i></span>
711
                                [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
712
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
713
                                [% ELSE %]
714
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" />
715
                                [% END %]
716
                                <label for="requestgrp" class="inline"> Hold next available item from an item group </label>
717
                            </legend>
718
719
                            <fieldset class="enable_request_group disable_request_any disable_request_specific">
720
                                [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
721
                                    <span class="error">
722
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
723
                                        Hold must be record level
724
                                    </span>
725
                                [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
726
                                    <span class="error">
727
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
728
                                        Hold must be item level
729
                                    </span>
730
                                [% ELSE %]
731
                                    <ul>
732
                                        <li>
733
                                            <label for="pickup">Pickup at:</label>
734
                                            <select name="pickup" id="pickup-item-group" data-biblio-id="[% biblio.biblionumber | html %]" data-patron-id="[% patron.borrowernumber | html %]" data-pickup-location-source="biblio">
735
                                                [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
736
                                            </select>
737
                                        </li>
738
                                        <li>
739
                                            <table id="requestgroup">
740
                                                <thead>
741
                                                    <tr>
742
                                                        <th>Hold</th>
743
                                                        <th>Item group</th>
744
                                                        <th>Holdable items</th>
745
                                                    </tr>
746
                                                </thead>
747
                                                <tbody>
748
                                                    [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
749
                                                        [% IF g.items.count %]
750
                                                            <tr>
751
                                                                <td>
752
                                                                    <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
753
                                                                </td>
754
                                                                <td>
755
                                                                    <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
756
                                                                </td>
757
                                                                <td>
758
                                                                    [% FOREACH i IN g.items %]
759
                                                                        <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
760
                                                                    [% END %]
761
                                                                </td>
762
                                                            </tr>
763
                                                        [% ELSE %]
764
                                                            <tr>
765
                                                                <td>
766
                                                                    <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
767
                                                                </td>
768
                                                                <td>
769
                                                                    <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
770
                                                                </td>
771
                                                                <td>
772
                                                                    <div class="error">No holdable items in this item group.</div>
773
                                                                </td>
774
                                                            </tr>
775
                                                        [% END %]
776
                                                    [% END %]
777
                                                </tbody>
778
                                            </table>
779
                                        </li>
780
                                    </ul>
781
                                [% END %]
782
                                <fieldset class="action">
783
                                    [% IF ( patron.borrowernumber ) %]
784
                                        [% IF ( override_required ) %]
785
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
786
                                        [% ELSIF ( none_available ) %]
787
                                            <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
788
                                        [% ELSE %]
789
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
790
                                        [% END %]
791
                                    [% END %]
792
                                </fieldset>
793
                            </fieldset>
794
                        </fieldset>
795
                    [% END %]
796
                    <!-- /ItemGroup level holds -->
780
                [% END %]
797
                [% END %]
781
                <!-- /ItemGroup level holds -->
782
798
783
                <fieldset class="rows any_specific">
799
                <fieldset class="rows any_specific">
784
                    <legend>
800
                    <legend>
Lines 1158-1163 Link Here
1158
                                            ><li> <strong>No items are available</strong> to be placed on hold</li></ul
1174
                                            ><li> <strong>No items are available</strong> to be placed on hold</li></ul
1159
                                        >
1175
                                        >
1160
                                    [% END %]
1176
                                    [% END %]
1177
1178
                                    [% IF itemloo.is_closed_stack %]
1179
                                        <br /><span>Closed stack</span>
1180
                                    [% END %]
1161
                                </td>
1181
                                </td>
1162
                            </tr>
1182
                            </tr>
1163
                        [% END # /FOREACH biblioloo %]
1183
                        [% END # /FOREACH biblioloo %]
Lines 1431-1436 Link Here
1431
    [% IF multi_hold %]
1451
    [% IF multi_hold %]
1432
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1452
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1433
    [% END %]
1453
    [% END %]
1454
    [% IF closed_stack_request %]
1455
        [% SET url_biblio_params = url_biblio_params _ "&closed_stack_request=1" %]
1456
    [% END %]
1434
    <script>
1457
    <script>
1435
        $(document).ready(function () {
1458
        $(document).ready(function () {
1436
            hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %];
1459
            hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %];
(-)a/koha-tmpl/intranet-tmpl/prog/js/holds.js (+265 lines)
Lines 481-486 $(document).ready(function () { Link Here
481
                        url: "/cgi-bin/koha/svc/holds",
481
                        url: "/cgi-bin/koha/svc/holds",
482
                        data: function (d) {
482
                        data: function (d) {
483
                            d.borrowernumber = borrowernumber;
483
                            d.borrowernumber = borrowernumber;
484
                            d.closed_stack_request = 0;
484
                        },
485
                        },
485
                    },
486
                    },
486
                    bKohaAjaxSVC: true,
487
                    bKohaAjaxSVC: true,
Lines 698-701 $(document).ready(function () { Link Here
698
        ];
699
        ];
699
        return toggle_suspend(this, inputs);
700
        return toggle_suspend(this, inputs);
700
    });
701
    });
702
703
    // Don't load holds table unless it is clicked on
704
    $("#closed-stack-requests-tab").on("click", function () {
705
        load_closed_stack_requests_table();
706
    });
707
708
    // If the holds tab is preselected on load, we need to load the table
709
    if ($("#closed-stack-requests-tab").parent().hasClass("active")) {
710
        load_closed_stack_requests_table();
711
    }
712
713
    function load_closed_stack_requests_table() {
714
        var holds = new Array();
715
        if (!$.fn.DataTable.isDataTable($("#closed-stack-requests-table"))) {
716
            var title;
717
            const table = $("#closed-stack-requests-table").dataTable(
718
                $.extend(true, {}, dataTablesDefaults, {
719
                    bAutoWidth: false,
720
                    sDom: "rt",
721
                    columns: [
722
                        {
723
                            data: {
724
                                _: "reservedate_formatted",
725
                                sort: "reservedate",
726
                            },
727
                        },
728
                        {
729
                            mDataProp: function (oObj) {
730
                                title =
731
                                    "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" +
732
                                    oObj.biblionumber +
733
                                    "'>" +
734
                                    (oObj.title ? oObj.title.escapeHtml() : "");
735
736
                                $.each(oObj.subtitle, function (index, value) {
737
                                    title += " " + value.escapeHtml();
738
                                });
739
740
                                title +=
741
                                    " " +
742
                                    oObj.part_number +
743
                                    " " +
744
                                    oObj.part_name;
745
746
                                if (oObj.enumchron) {
747
                                    title +=
748
                                        " (" +
749
                                        oObj.enumchron.escapeHtml() +
750
                                        ")";
751
                                }
752
753
                                title += "</a>";
754
755
                                if (oObj.author) {
756
                                    title +=
757
                                        " " +
758
                                        __("by _AUTHOR_").replace(
759
                                            "_AUTHOR_",
760
                                            oObj.author.escapeHtml()
761
                                        );
762
                                }
763
764
                                if (oObj.itemnotes) {
765
                                    var span_class = "";
766
                                    if (
767
                                        flatpickr.formatDate(
768
                                            new Date(oObj.issuedate),
769
                                            "Y-m-d"
770
                                        ) == ymd
771
                                    ) {
772
                                        span_class = "circ-hlt";
773
                                    }
774
                                    title +=
775
                                        " - <span class='" +
776
                                        span_class +
777
                                        "'>" +
778
                                        oObj.itemnotes.escapeHtml() +
779
                                        "</span>";
780
                                }
781
782
                                return title;
783
                            },
784
                        },
785
                        {
786
                            mDataProp: function (oObj) {
787
                                return (
788
                                    (oObj.itemcallnumber &&
789
                                        oObj.itemcallnumber.escapeHtml()) ||
790
                                    ""
791
                                );
792
                            },
793
                        },
794
                        {
795
                            mDataProp: function (oObj) {
796
                                var data = "";
797
                                if (oObj.itemtype) {
798
                                    data += oObj.itemtype_description;
799
                                }
800
                                return data;
801
                            },
802
                        },
803
                        {
804
                            mDataProp: function (oObj) {
805
                                var data = "";
806
                                if (oObj.barcode) {
807
                                    data +=
808
                                        " <a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=" +
809
                                        oObj.biblionumber +
810
                                        "&itemnumber=" +
811
                                        oObj.itemnumber +
812
                                        "#item" +
813
                                        oObj.itemnumber +
814
                                        "'>" +
815
                                        oObj.barcode.escapeHtml() +
816
                                        "</a>";
817
                                }
818
                                return data;
819
                            },
820
                        },
821
                        {
822
                            data: {
823
                                _: "expirationdate_formatted",
824
                                sort: "expirationdate",
825
                            },
826
                        },
827
                        {
828
                            mDataProp: function (oObj) {
829
                                if (
830
                                    oObj.priority &&
831
                                    parseInt(oObj.priority) &&
832
                                    parseInt(oObj.priority) > 0
833
                                ) {
834
                                    return oObj.priority;
835
                                } else {
836
                                    return "";
837
                                }
838
                            },
839
                        },
840
                        {
841
                            bSortable: false,
842
                            mDataProp: function (oObj) {
843
                                return (
844
                                    "<select name='rank-request'>" +
845
                                    "<option value='n'>" +
846
                                    __("No") +
847
                                    "</option>" +
848
                                    "<option value='del'>" +
849
                                    __("Yes") +
850
                                    "</option>" +
851
                                    "</select>" +
852
                                    "<input type='hidden' name='biblionumber' value='" +
853
                                    oObj.biblionumber +
854
                                    "'>" +
855
                                    "<input type='hidden' name='borrowernumber' value='" +
856
                                    borrowernumber +
857
                                    "'>" +
858
                                    "<input type='hidden' name='reserve_id' value='" +
859
                                    oObj.reserve_id +
860
                                    "'>"
861
                                );
862
                            },
863
                        },
864
                        {
865
                            mDataProp: function (oObj) {
866
                                var data = "";
867
868
                                if (oObj.suspend == 1) {
869
                                    data +=
870
                                        "<p>" +
871
                                        __(
872
                                            "Hold is <strong>suspended</strong>"
873
                                        );
874
                                    if (oObj.suspend_until) {
875
                                        data +=
876
                                            " " +
877
                                            __("until %s").format(
878
                                                oObj.suspend_until_formatted
879
                                            );
880
                                    }
881
                                    data += "</p>";
882
                                }
883
884
                                if (oObj.itemtype_limit) {
885
                                    data += __("Next available %s item").format(
886
                                        oObj.itemtype_limit
887
                                    );
888
                                }
889
890
                                if (oObj.item_group_id) {
891
                                    data += __(
892
                                        "Next available item group <strong>%s</strong> item"
893
                                    ).format(oObj.item_group_description);
894
                                }
895
896
                                if (oObj.barcode) {
897
                                    data += "<em>";
898
                                    if (oObj.found == "W") {
899
                                        if (oObj.waiting_here) {
900
                                            data += __(
901
                                                "Item is <strong>waiting here</strong>"
902
                                            );
903
                                            if (oObj.desk_name) {
904
                                                data +=
905
                                                    ", " +
906
                                                    __("at %s").format(
907
                                                        oObj.desk_name.escapeHtml()
908
                                                    );
909
                                            }
910
                                        } else {
911
                                            data += __(
912
                                                "Item is <strong>waiting</strong>"
913
                                            );
914
                                            data +=
915
                                                " " +
916
                                                __("at %s").format(
917
                                                    oObj.waiting_at
918
                                                );
919
                                            if (oObj.desk_name) {
920
                                                data +=
921
                                                    ", " +
922
                                                    __("at %s").format(
923
                                                        oObj.desk_name.escapeHtml()
924
                                                    );
925
                                            }
926
                                        }
927
                                    } else if (oObj.transferred) {
928
                                        data += __(
929
                                            "Item is <strong>in transit</strong> from %s since %s"
930
                                        ).format(
931
                                            oObj.from_branch,
932
                                            oObj.date_sent
933
                                        );
934
                                    } else if (oObj.not_transferred) {
935
                                        data += __(
936
                                            "Item hasn't been transferred yet from %s"
937
                                        ).format(oObj.not_transferred_by);
938
                                    }
939
                                    data += "</em>";
940
                                }
941
                                return data;
942
                            },
943
                        },
944
                    ],
945
                    bPaginate: false,
946
                    bProcessing: true,
947
                    bServerSide: false,
948
                    ajax: {
949
                        url: "/cgi-bin/koha/svc/holds",
950
                        data: function (d) {
951
                            d.borrowernumber = borrowernumber;
952
                            d.closed_stack_request = 1;
953
                        },
954
                    },
955
                })
956
            );
957
958
            if ($("#closed-stack-requests-table").length) {
959
                $("#closed-stack-requests-table_processing").position({
960
                    of: $("#closed-stack-requests-table"),
961
                    collision: "none",
962
                });
963
            }
964
        }
965
    }
701
});
966
});
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/holds-table.inc (-1 / +2 lines)
Lines 3-9 Link Here
3
[% USE KohaDates %]
3
[% USE KohaDates %]
4
[% PROCESS 'i18n.inc' %]
4
[% PROCESS 'i18n.inc' %]
5
5
6
<table id="holdst" class="table table-bordered table-striped">
6
[% DEFAULT table_id = 'holdst' %]
7
<table id="[% table_id | html %]" class="table table-bordered table-striped">
7
    <caption>Holds <span class="count">([% HOLDS.count | html %] total)</span></caption>
8
    <caption>Holds <span class="count">([% HOLDS.count | html %] total)</span></caption>
8
    <!-- HOLDS TABLE ROWS -->
9
    <!-- HOLDS TABLE ROWS -->
9
    <thead>
10
    <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 / +26 lines)
Lines 10-16 Link Here
10
    [% SET reserve_input_type = 'checkbox' %]
10
    [% SET reserve_input_type = 'checkbox' %]
11
[% END %]
11
[% END %]
12
[% INCLUDE 'doc-head-open.inc' %]
12
[% INCLUDE 'doc-head-open.inc' %]
13
<title>Placing a hold &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
13
[% IF closed_stack_request %]
14
    <title>Closed stack request &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
15
[% ELSE %]
16
    <title>Placing a hold &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
17
[% END %]
14
[% INCLUDE 'doc-head-close.inc' %]
18
[% INCLUDE 'doc-head-close.inc' %]
15
[% BLOCK cssinclude %]
19
[% BLOCK cssinclude %]
16
[% END %]
20
[% END %]
Lines 22-34 Link Here
22
<div class="main">
26
<div class="main">
23
    [% WRAPPER breadcrumbs %]
27
    [% WRAPPER breadcrumbs %]
24
        [% WRAPPER breadcrumb_item bc_active= 1 %]
28
        [% WRAPPER breadcrumb_item bc_active= 1 %]
25
            <span>Placing a hold</span>
29
            [% IF closed_stack_request %]
30
                <span>Closed stack request</span>
31
            [% ELSE %]
32
                <span>Placing a hold</span>
33
            [% END %]
26
        [% END %]
34
        [% END %]
27
    [% END #/ WRAPPER breadcrumbs %]
35
    [% END #/ WRAPPER breadcrumbs %]
28
36
29
    <div class="container">
37
    <div class="container">
30
        <div id="holds" class="maincontent">
38
        <div id="holds" class="maincontent">
31
            <h1>Placing a hold</h1>
39
            [% IF closed_stack_request %]
40
                <h1>Closed stack request</h1>
41
            [% ELSE %]
42
                <h1>Placing a hold</h1>
43
            [% END %]
32
            [% IF ( message ) %]
44
            [% IF ( message ) %]
33
                <div id="holdmessages" class="alert">
45
                <div id="holdmessages" class="alert">
34
                    <p>Sorry, you cannot place holds.</p>
46
                    <p>Sorry, you cannot place holds.</p>
Lines 133-139 Link Here
133
145
134
            [% UNLESS ( message ) %]
146
            [% UNLESS ( message ) %]
135
                [% UNLESS ( none_available ) %]
147
                [% UNLESS ( none_available ) %]
136
                    <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
148
                    [% IF closed_stack_request %]
149
                        <h2>Confirm closed stack request for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
150
                    [% ELSE %]
151
                        <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
152
                    [% END %]
137
                [% END # / UNLESS none_available %]
153
                [% END # / UNLESS none_available %]
138
154
139
                [% IF ( new_reserves_allowed ) %]
155
                [% IF ( new_reserves_allowed ) %]
Lines 144-149 Link Here
144
                    [% INCLUDE 'csrf-token.inc' %]
160
                    [% INCLUDE 'csrf-token.inc' %]
145
                    <legend class="sr-only">Hold requests</legend>
161
                    <legend class="sr-only">Hold requests</legend>
146
                    <input type="hidden" name="op" value="cud-place_reserve" />
162
                    <input type="hidden" name="op" value="cud-place_reserve" />
163
                    [% IF closed_stack_request %]
164
                        <input type="hidden" name="closed_stack_request" value="1" />
165
                    [% END %]
147
                    <!-- These values are set dynamically by js -->
166
                    <!-- These values are set dynamically by js -->
148
                    <input type="hidden" name="biblionumbers" id="biblionumbers" />
167
                    <input type="hidden" name="biblionumbers" id="biblionumbers" />
149
                    <input type="hidden" name="selecteditems" id="selections" />
168
                    <input type="hidden" name="selecteditems" id="selections" />
Lines 153-161 Link Here
153
                                [% IF bibitemloo.forced_hold_level %]
172
                                [% IF bibitemloo.forced_hold_level %]
154
                                    <div class="alert alert-info forced_hold_level">
173
                                    <div class="alert alert-info forced_hold_level">
155
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
174
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
156
                                            <span>You already have at least one item level hold on this title. All further holds must be item level.</span>
175
                                            <span>Hold policy requires holds to be item level.</span>
157
                                        [% ELSE %]
176
                                        [% ELSE %]
158
                                            <span>You already have at least one record level hold on this title. All further holds must be record level.</span>
177
                                            <span>Hold policy requires holds to be record level.</span>
159
                                        [% END %]
178
                                        [% END %]
160
                                    </div>
179
                                    </div>
161
                                [% END %]
180
                                [% END %]
Lines 249-255 Link Here
249
                                                </li>
268
                                                </li>
250
                                            [% END %]
269
                                            [% END %]
251
270
252
                                            [% UNLESS ( singleBranchMode ) %]
271
                                            [% UNLESS ( singleBranchMode || closed_stack_request ) %]
253
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
272
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
254
                                                    <li class="branch">
273
                                                    <li class="branch">
255
                                                        <label for="branch_[% bibitemloo.biblionumber | html %]">Pick up location:</label>
274
                                                        <label for="branch_[% bibitemloo.biblionumber | html %]">Pick up location:</label>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (-1 / +15 lines)
Lines 304-309 Link Here
304
                                    <span>Holds ([% RESERVES.count | html %])</span>
304
                                    <span>Holds ([% RESERVES.count | html %])</span>
305
                                [% END %]
305
                                [% END %]
306
                            [% END %]
306
                            [% END %]
307
                            [% IF ( closed_stack_requests.count ) %]
308
                                [% WRAPPER tab_item tabname="opac-user-closed-stack-requests" %]
309
                                    <span>Closed stack requests ([% closed_stack_requests.count | html %])</span>
310
                                [% END %]
311
                            [% END %]
307
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
312
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
308
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
313
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
309
                                    <span>Recalls ([% RECALLS.count | html %])</span>
314
                                    <span>Recalls ([% RECALLS.count | html %])</span>
Lines 851-856 Link Here
851
                                [% END # /tab_panel#opac-user-holds %]
856
                                [% END # /tab_panel#opac-user-holds %]
852
                            [% END # / #RESERVES.count %]
857
                            [% END # / #RESERVES.count %]
853
858
859
                            [% IF ( closed_stack_requests.count ) %]
860
                                [% WRAPPER tab_panel tabname="opac-user-closed-stack-requests" %]
861
                                    [% PROCESS 'holds-table.inc' HOLDS = closed_stack_requests, SuspendHoldsOpac = SuspendHoldsOpac, showpriority = showpriority, AutoResumeSuspendedHolds = AutoResumeSuspendedHolds, table_id = 'closed-stack-requests-table' %]
862
                                [% END %]
863
                            [% END %]
864
854
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
865
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
855
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
866
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
856
                                    <table id="recalls-table" class="table table-bordered table-striped">
867
                                    <table id="recalls-table" class="table table-bordered table-striped">
Lines 1092-1097 Link Here
1092
            [% IF ( opac_user_holds ) %]
1103
            [% IF ( opac_user_holds ) %]
1093
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1104
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1094
            [% END %]
1105
            [% END %]
1106
            [% IF ( opac_user_closed_stack_requests ) %]
1107
                $("#opac-user-views a[href='#opac-user-closed-stack-requests_panel']").tab("show");
1108
            [% END %]
1095
            [% IF ( opac_user_article_requests ) %]
1109
            [% IF ( opac_user_article_requests ) %]
1096
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1110
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1097
            [% END %]
1111
            [% END %]
Lines 1200-1206 Link Here
1200
                );
1214
                );
1201
            });
1215
            });
1202
1216
1203
            var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table");
1217
            var dTables = $("#checkoutst,#holdst,#closed-stack-requests-table,#overduest,#opac-user-relative-issues-table");
1204
            dTables.each(function(){
1218
            dTables.each(function(){
1205
                var thIndex = $(this).find("th.psort").index();
1219
                var thIndex = $(this).find("th.psort").index();
1206
                $(this).on("init.dt", function() {
1220
                $(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 205-211 if ( $patron->is_expired || $patron->is_going_to_expire ) { Link Here
205
my $holds         = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
205
my $holds         = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
206
my $waiting_holds = $holds->waiting;
206
my $waiting_holds = $holds->waiting;
207
$template->param(
207
$template->param(
208
    holds_count  => $holds->count(),
209
    WaitingHolds => $waiting_holds,
208
    WaitingHolds => $waiting_holds,
210
);
209
);
211
210
Lines 287-307 my $has_modifications = Koha::Patron::Modifications->search( { borrowernumber = Link Here
287
my $patron_lists_count = $patron->get_lists_with_patron->count();
286
my $patron_lists_count = $patron->get_lists_with_patron->count();
288
287
289
$template->param(
288
$template->param(
290
    patron                 => $patron,
289
    patron                      => $patron,
291
    issuecount             => $patron->checkouts->count,
290
    issuecount                  => $patron->checkouts->count,
292
    holds_count            => $patron->holds->count,
291
    holds_count                 => $patron->holds->filter_out_closed_stack_requests()->count,
293
    fines                  => $patron->account->balance,
292
    closed_stack_requests_count => $patron->holds->filter_by_closed_stack_requests()->count,
294
    translated_language    => $translated_language,
293
    fines                       => $patron->account->balance,
295
    detailview             => 1,
294
    translated_language         => $translated_language,
296
    was_renewed            => scalar $input->param('was_renewed') ? 1 : 0,
295
    detailview                  => 1,
297
    $category_type         => 1,                                           # [% IF ( I ) %] = institutional/organisation
296
    was_renewed                 => scalar $input->param('was_renewed') ? 1 : 0,
298
    housebound_role        => scalar $patron->housebound_role,
297
    $category_type              => 1,                                 # [% IF ( I ) %] = institutional/organisation
299
    relatives_issues_count => $relatives_issues_count,
298
    housebound_role             => scalar $patron->housebound_role,
300
    relatives_borrowernumbers => \@relatives,
299
    relatives_issues_count      => $relatives_issues_count,
301
    logged_in_user            => $logged_in_user,
300
    relatives_borrowernumbers   => \@relatives,
302
    files                     => Koha::Patron::Files->new( borrowernumber => $borrowernumber )->GetFilesInfo(),
301
    logged_in_user              => $logged_in_user,
303
    has_modifications         => $has_modifications,
302
    files                       => Koha::Patron::Files->new( borrowernumber => $borrowernumber )->GetFilesInfo(),
304
    patron_lists_count        => $patron_lists_count,
303
    has_modifications           => $has_modifications,
304
    patron_lists_count          => $patron_lists_count,
305
);
305
);
306
306
307
if ( C4::Context->preference('UseRecalls') ) {
307
if ( C4::Context->preference('UseRecalls') ) {
(-)a/opac/opac-reserve.pl (-8 / +23 lines)
Lines 87-92 if ( !$biblionumbers ) { Link Here
87
    $biblionumbers = $query->param('biblionumber');
87
    $biblionumbers = $query->param('biblionumber');
88
}
88
}
89
89
90
my $closed_stack_request = $query->param('closed_stack_request');
91
$template->param( closed_stack_request => $closed_stack_request );
92
90
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
93
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
91
    $template->param( message => 1, no_biblionumber => 1 );
94
    $template->param( message => 1, no_biblionumber => 1 );
92
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
95
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
Lines 248-253 if ( $op eq 'cud-place_reserve' ) { Link Here
248
            }
251
            }
249
        }
252
        }
250
253
254
        if ( $closed_stack_request && $item ) {
255
            $branch = $item->holdingbranch;
256
        }
257
251
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
258
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
252
        if ($item) {
259
        if ($item) {
253
            $biblioNum = $item->biblionumber;
260
            $biblioNum = $item->biblionumber;
Lines 336-344 if ( $op eq 'cud-place_reserve' ) { Link Here
336
        }
343
        }
337
    }
344
    }
338
345
346
    my $param = $closed_stack_request ? 'opac-user-closed-stack-requests' : 'opac-user-holds';
339
    print $query->redirect( "/cgi-bin/koha/opac-user.pl?"
347
    print $query->redirect( "/cgi-bin/koha/opac-user.pl?"
340
            . ( @failed_holds ? "failed_holds=" . join( '|', @failed_holds ) : q|| )
348
            . ( @failed_holds ? "failed_holds=" . join( '|', @failed_holds ) : q|| )
341
            . "&opac-user-holds=1" );
349
            . "&$param=1" );
342
    exit;
350
    exit;
343
}
351
}
344
352
Lines 444-449 foreach my $biblioNum (@biblionumbers) { Link Here
444
    # it's complicated logic to analyse.
452
    # it's complicated logic to analyse.
445
    # (before this loop was inside that sub loop so it was O(n^2) )
453
    # (before this loop was inside that sub loop so it was O(n^2) )
446
    foreach my $item ( @{ $biblioData->{items} } ) {
454
    foreach my $item ( @{ $biblioData->{items} } ) {
455
        next if ( $closed_stack_request xor $item->is_available_for_closed_stack_request );
447
456
448
        my $item_info = $item->unblessed;
457
        my $item_info = $item->unblessed;
449
        $item_info->{holding_branch} = $item->holding_branch;
458
        $item_info->{holding_branch} = $item->holding_branch;
Lines 581-593 foreach my $biblioNum (@biblionumbers) { Link Here
581
    # patron placed a record level hold, all the holds the patron places must
590
    # patron placed a record level hold, all the holds the patron places must
582
    # be record level. If the patron placed an item level hold, all holds
591
    # be record level. If the patron placed an item level hold, all holds
583
    # the patron places must be item level
592
    # the patron places must be item level
584
    my $forced_hold_level = Koha::Holds->search(
593
    # Unless the biblio itself forces a specific hold level which
585
        {
594
    # supersedes the above rules
586
            borrowernumber => $borrowernumber,
595
    my $forced_hold_level = $biblio->forced_hold_level;
587
            biblionumber   => $biblioNum,
596
    unless ($forced_hold_level) {
588
            found          => undef,
597
        $forced_hold_level = Koha::Holds->search(
589
        }
598
            {
590
    )->forced_hold_level();
599
                borrowernumber => $borrowernumber,
600
                biblionumber   => $biblioNum,
601
                found          => undef,
602
            }
603
        )->forced_hold_level();
604
    }
605
591
    if ($forced_hold_level) {
606
    if ($forced_hold_level) {
592
        $biblioLoopIter{force_hold}        = 1 if $forced_hold_level eq 'item';
607
        $biblioLoopIter{force_hold}        = 1 if $forced_hold_level eq 'item';
593
        $biblioLoopIter{force_hold}        = 0 if $forced_hold_level eq 'item_group';
608
        $biblioLoopIter{force_hold}        = 0 if $forced_hold_level eq 'item_group';
(-)a/opac/opac-user.pl (-3 / +7 lines)
Lines 344-354 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
    showpriority => $show_priority,
352
    closed_stack_requests => $closed_stack_requests,
353
    showpriority          => $show_priority,
352
);
354
);
353
355
354
if ( C4::Context->preference('UseRecalls') ) {
356
if ( C4::Context->preference('UseRecalls') ) {
Lines 431-436 $template->param( Link Here
431
    failed_holds               => scalar $query->param('failed_holds'),
433
    failed_holds               => scalar $query->param('failed_holds'),
432
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
434
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
433
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
435
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
436
437
    opac_user_closed_stack_requests => scalar $query->param('opac-user-closed-stack-requests') || 0,
434
);
438
);
435
439
436
# if not an empty string this indicates to return
440
# if not an empty string this indicates to return
(-)a/reserve/placerequest.pl (-1 / +5 lines)
Lines 167-173 if ( $op eq 'cud-placerequest' && $patron ) { Link Here
167
    foreach my $msg ( keys %failed_holds ) {
167
    foreach my $msg ( keys %failed_holds ) {
168
        push( @failed_hold_msgs, $msg );
168
        push( @failed_hold_msgs, $msg );
169
    }
169
    }
170
    $redirect_url->query_form( biblionumber => [@biblionumbers], failed_holds => \@failed_hold_msgs );
170
    my %params = ( biblionumber => [@biblionumbers], failed_holds => \@failed_hold_msgs );
171
    if ( $input->param('closed_stack_request') ) {
172
        $params{closed_stack_request} = 1;
173
    }
174
    $redirect_url->query_form(%params);
171
    print $input->redirect($redirect_url);
175
    print $input->redirect($redirect_url);
172
} elsif ( $borrowernumber eq '' ) {
176
} elsif ( $borrowernumber eq '' ) {
173
    print $input->header();
177
    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 350-355 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
350
            # patron placed a record level hold, all the holds the patron places must
351
            # patron placed a record level hold, all the holds the patron places must
351
            # be record level. If the patron placed an item level hold, all holds
352
            # be record level. If the patron placed an item level hold, all holds
352
            # the patron places must be item level
353
            # the patron places must be item level
354
            # Unless the biblio itself forces a specific hold level which
355
            # supersedes the above rules
353
            my $holds = Koha::Holds->search(
356
            my $holds = Koha::Holds->search(
354
                {
357
                {
355
                    borrowernumber => $patron->borrowernumber,
358
                    borrowernumber => $patron->borrowernumber,
Lines 357-363 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
357
                    found          => undef,
360
                    found          => undef,
358
                }
361
                }
359
            );
362
            );
360
            $template->param( force_hold_level => $holds->forced_hold_level() );
363
364
            my $forced_hold_level = $biblio->forced_hold_level // $holds->forced_hold_level();
365
            $template->param( force_hold_level => $forced_hold_level );
361
366
362
            # For a librarian to be able to place multiple record holds for a patron for a record,
367
            # For a librarian to be able to place multiple record holds for a patron for a record,
363
            # we must find out what the maximum number of holds they can place for the patron is
368
            # we must find out what the maximum number of holds they can place for the patron is
Lines 398-403 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
398
            # (before this loop was inside that sub loop so it was O(n^2) )
403
            # (before this loop was inside that sub loop so it was O(n^2) )
399
404
400
            for my $item_object (@items) {
405
            for my $item_object (@items) {
406
                next if ( $closed_stack_request xor $item_object->is_available_for_closed_stack_request );
407
401
                my $do_check;
408
                my $do_check;
402
                my $item = $item_object->unblessed;
409
                my $item = $item_object->unblessed;
403
                $item->{object} = $item_object;
410
                $item->{object} = $item_object;
Lines 618-625 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
618
                    )->unblessed
625
                    )->unblessed
619
                }
626
                }
620
            };
627
            };
621
            my @reserves =
628
            my $holds_rs = Koha::Holds->search( { 'me.biblionumber' => $biblionumber }, { order_by => 'priority' } );
622
                Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list;
629
            if ($closed_stack_request) {
630
                $holds_rs = $holds_rs->filter_by_closed_stack_requests();
631
            } else {
632
                $holds_rs = $holds_rs->filter_out_closed_stack_requests();
633
            }
634
            my @reserves = $holds_rs->as_list;
623
            foreach my $res (
635
            foreach my $res (
624
                sort {
636
                sort {
625
                    my $a_found = $a->found() || '';
637
                    my $a_found = $a->found() || '';
Lines 740-745 $template->param( borrowernumber => $borrowernumber_hold ); Link Here
740
752
741
$template->param( failed_holds => \@failed_holds );
753
$template->param( failed_holds => \@failed_holds );
742
754
755
$template->param( closed_stack_request => $closed_stack_request );
756
743
# printout the page
757
# printout the page
744
output_html_with_http_headers $input, $cookie, $template->output;
758
output_html_with_http_headers $input, $cookie, $template->output;
745
759
(-)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