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

(-)a/C4/Reserves.pm (-29 / +40 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 833-847 sub CheckReserves { Link Here
833
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
833
    my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') );
834
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
834
    return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue;
835
835
836
    my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
836
    unless ($item->is_closed_stack) {
837
    if ( !$dont_trap ) {
837
        my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan;
838
        my $item_type = $item->effective_itemtype;
838
        if ( !$dont_trap ) {
839
        if ( $item_type ) {
839
            my $item_type = $item->effective_itemtype;
840
            return if Koha::ItemTypes->find( $item_type )->notforloan;
840
            if ( $item_type ) {
841
                return if Koha::ItemTypes->find( $item_type )->notforloan;
842
            }
843
        }
844
        else {
845
            return;
841
        }
846
        }
842
    }
843
    else {
844
        return;
845
    }
847
    }
846
848
847
    # Find this item in the reserves
849
    # Find this item in the reserves
Lines 1316-1325 sub IsAvailableForItemLevelRequest { Link Here
1316
      unless defined $itemtype;
1318
      unless defined $itemtype;
1317
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1319
    my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan;
1318
1320
1321
    # Closed stack items can be requested even if marked as "not for loan"
1322
    unless ($item->is_closed_stack) {
1323
        return 0 if
1324
            $notforloan_per_itemtype ||
1325
            $item->notforloan > 0; # item with negative or zero notforloan value is holdable
1326
    }
1327
1319
    return 0 if
1328
    return 0 if
1320
        $notforloan_per_itemtype ||
1321
        $item->itemlost        ||
1329
        $item->itemlost        ||
1322
        $item->notforloan > 0  || # item with negative or zero notforloan value is holdable
1323
        $item->withdrawn        ||
1330
        $item->withdrawn        ||
1324
        ($item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems'));
1331
        ($item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems'));
1325
1332
Lines 1335-1363 sub IsAvailableForItemLevelRequest { Link Here
1335
        return 0 unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $home_library->validate_hold_sibling( {branchcode => $pickup_branchcode} );
1342
        return 0 unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $home_library->validate_hold_sibling( {branchcode => $pickup_branchcode} );
1336
    }
1343
    }
1337
1344
1338
    my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1345
    unless ($item->is_closed_stack) {
1346
        my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } );
1339
1347
1340
    if ( $on_shelf_holds == 1 ) {
1348
        if ( $on_shelf_holds == 1 ) {
1341
        return 1;
1349
            return 1;
1342
    } elsif ( $on_shelf_holds == 2 ) {
1350
        } elsif ( $on_shelf_holds == 2 ) {
1343
1351
1344
        # These calculations work at the biblio level, and can be expensive
1352
            # These calculations work at the biblio level, and can be expensive
1345
        # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1353
            # we use the in-memory cache to avoid calling once per item when looping items on a biblio
1346
1354
1347
        my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1355
            my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
1348
        my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1356
            my $cache_key    = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber;
1349
1357
1350
        my $any_available = $memory_cache->get_from_cache($cache_key);
1358
            my $any_available = $memory_cache->get_from_cache($cache_key);
1351
        return $any_available ? 0 : 1 if defined($any_available);
1359
            return $any_available ? 0 : 1 if defined($any_available);
1352
1360
1353
        $any_available =
1361
            $any_available =
1354
            ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1362
                ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } );
1355
        $memory_cache->set_in_cache( $cache_key, $any_available );
1363
            $memory_cache->set_in_cache( $cache_key, $any_available );
1356
        return $any_available ? 0 : 1;
1364
            return $any_available ? 0 : 1;
1357
1365
1358
    } else {  # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1366
        } else {  # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved)
1359
        return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1367
            return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count;
1368
        }
1360
    }
1369
    }
1370
1371
    return 1;
1361
}
1372
}
1362
1373
1363
=head2 ItemsAnyAvailableAndNotRestricted
1374
=head2 ItemsAnyAvailableAndNotRestricted
(-)a/Koha/Biblio.pm (+34 lines)
Lines 2295-2300 sub merge_with { Link Here
2295
    return \%results;
2295
    return \%results;
2296
}
2296
}
2297
2297
2298
=head3 forced_hold_level
2299
2300
Returns forced hold level for a biblio
2301
2302
Returns 'item' if item-level hold should be forced, or undef
2303
2304
=cut
2305
2306
sub forced_hold_level {
2307
    my ($self) = @_;
2308
2309
    my $forced_hold_level;
2310
2311
    # If there is at least one closed stack item that is not checked out and
2312
    # not reserved, force item-level hold
2313
    my $available_closed_stack_items = $self->items->search(
2314
        {
2315
            is_closed_stack => 1,
2316
            onloan => undef,
2317
            'reserves.reserve_id' => undef,
2318
        },
2319
        {
2320
            join => 'reserves',
2321
        },
2322
    );
2323
2324
    if ($available_closed_stack_items->count > 0) {
2325
        $forced_hold_level = 'item';
2326
    }
2327
2328
    return $forced_hold_level;
2329
}
2330
2331
2298
=head2 Internal methods
2332
=head2 Internal methods
2299
2333
2300
=head3 type
2334
=head3 type
(-)a/Koha/CirculationRules.pm (+2 lines)
Lines 563-568 sub get_opacitemholds_policy { Link Here
563
563
564
    return unless $item or $patron;
564
    return unless $item or $patron;
565
565
566
    return 'F' if $item->is_closed_stack;
567
566
    my $rule = Koha::CirculationRules->get_effective_rule(
568
    my $rule = Koha::CirculationRules->get_effective_rule(
567
        {
569
        {
568
            categorycode => $patron->categorycode,
570
            categorycode => $patron->categorycode,
(-)a/Koha/Hold.pm (-2 / +4 lines)
Lines 456-463 This is used from the OPAC. Link Here
456
sub is_cancelable_from_opac {
456
sub is_cancelable_from_opac {
457
    my ($self) = @_;
457
    my ($self) = @_;
458
458
459
    return 1 unless $self->is_found();
459
    return 0 if $self->is_found();
460
    return 0; # if ->is_in_transit or if ->is_waiting or ->is_in_processing
460
    return 0 if $self->item && $self->item->is_closed_stack;
461
462
    return 1;
461
}
463
}
462
464
463
=head3 cancellation_requestable_from_opac
465
=head3 cancellation_requestable_from_opac
(-)a/Koha/Holds.pm (+47 lines)
Lines 133-138 Items that are not: Link Here
133
  widthdrawn
133
  widthdrawn
134
  not for loan
134
  not for loan
135
  not on loan
135
  not on loan
136
  in closed stack
136
137
137
=cut
138
=cut
138
139
Lines 167-172 sub get_items_that_can_fill { Link Here
167
            itemnumber   => { -not_in => [ @branchtransfers, @waiting_holds ] },
168
            itemnumber   => { -not_in => [ @branchtransfers, @waiting_holds ] },
168
            onloan       => undef,
169
            onloan       => undef,
169
            notforloan   => 0,
170
            notforloan   => 0,
171
            is_closed_stack => 0,
170
        }
172
        }
171
    )->filter_by_for_hold();
173
    )->filter_by_for_hold();
172
}
174
}
Lines 201-206 sub filter_out_has_cancellation_requests { Link Here
201
        { join => 'cancellation_requests' } );
203
        { join => 'cancellation_requests' } );
202
}
204
}
203
205
206
sub filter_by_closed_stack_requests {
207
    my ($self) = @_;
208
209
    return $self->search($self->_closed_stack_request_filter, { join => 'item' });
210
}
211
212
sub filter_out_closed_stack_requests {
213
    my ($self) = @_;
214
215
    return $self->search({ -not_bool => $self->_closed_stack_request_filter }, { join => 'item' });
216
}
217
218
sub _closed_stack_request_filter {
219
    # This query returns the top priority reserve's id for each item
220
    my $reserve_id_subselect = q{
221
        select reserve_id from (
222
            select reserve_id, itemnumber, row_number() over (partition by itemnumber order by priority) rank
223
            from reserves where itemnumber is not null
224
        ) r
225
        where rank = 1
226
    };
227
228
    my %where = (
229
        'me.reserve_id' => { -in => \$reserve_id_subselect },
230
        'me.suspend'           => 0,
231
        'me.found' => undef,
232
        'me.priority' => { '!=' => 0 },
233
        'item.itemlost'        => 0,
234
        'item.withdrawn'       => 0,
235
        'item.onloan'          => undef,
236
        'item.is_closed_stack' => 1,
237
        'item.itemnumber'      => { -not_in => \'SELECT itemnumber FROM branchtransfers WHERE datearrived IS NULL AND datecancelled IS NULL' },
238
    );
239
240
    if ( !C4::Context->preference('AllowHoldsOnDamagedItems') ) {
241
        $where{'item.damaged'} = 0;
242
    }
243
244
    if ( C4::Context->only_my_library() ) {
245
        $where{'me.branchcode'} = C4::Context->userenv->{'branch'};
246
    }
247
248
    return \%where;
249
}
250
204
=head2 Internal methods
251
=head2 Internal methods
205
252
206
=head3 _type
253
=head3 _type
(-)a/Koha/Item.pm (+31 lines)
Lines 2524-2529 sub analytics_count { Link Here
2524
    return C4::Items::GetAnalyticsCount($self->itemnumber);
2524
    return C4::Items::GetAnalyticsCount($self->itemnumber);
2525
}
2525
}
2526
2526
2527
=head3 is_available_for_closed_stack_request
2528
2529
Returns 1 if item is available for a closed stack request, 0 otherwise
2530
2531
An item is available for a closed stack request if:
2532
2533
=over
2534
2535
=item * it is flagged as "closed stack"
2536
2537
=item * there is no holds on it
2538
2539
=item * it is not checked out
2540
2541
=item * it is not in transfer
2542
2543
=back
2544
2545
=cut
2546
2547
sub is_available_for_closed_stack_request {
2548
    my ($self) = @_;
2549
2550
    return 0 unless $self->is_closed_stack;
2551
    return 0 if $self->holds->count > 0;
2552
    return 0 if $self->checkout;
2553
    return 0 if $self->get_transfers->count > 0;
2554
2555
    return 1;
2556
}
2557
2527
=head3 strings_map
2558
=head3 strings_map
2528
2559
2529
Returns a map of column name to string representations including the string,
2560
Returns a map of column name to string representations including the string,
(-)a/Koha/Items.pm (-1 / +27 lines)
Lines 105-110 sub filter_by_for_hold { Link Here
105
        notforloan => { '<=' => 0 },    # items with negative or zero notforloan value are holdable
105
        notforloan => { '<=' => 0 },    # items with negative or zero notforloan value are holdable
106
        ( C4::Context->preference('AllowHoldsOnDamagedItems')? (): ( damaged => 0 ) ),
106
        ( C4::Context->preference('AllowHoldsOnDamagedItems')? (): ( damaged => 0 ) ),
107
        ( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ),
107
        ( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ),
108
        -or => [
109
            { is_closed_stack => 0 },
110
            {
111
                is_closed_stack => 1,
112
                'reserves.reserve_id' => { '!=', undef },
113
            },
114
        ],
108
    };
115
    };
109
116
110
    if ( C4::Context->preference("item-level_itypes") ) {
117
    if ( C4::Context->preference("item-level_itypes") ) {
Lines 112-117 sub filter_by_for_hold { Link Here
112
            {
119
            {
113
                %$params,
120
                %$params,
114
                itype        => { -not_in => \@hold_not_allowed_itypes },
121
                itype        => { -not_in => \@hold_not_allowed_itypes },
122
            },
123
            {
124
                join => 'reserves',
115
            }
125
            }
116
        );
126
        );
117
    } else {
127
    } else {
Lines 121-127 sub filter_by_for_hold { Link Here
121
                'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes },
131
                'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes },
122
            },
132
            },
123
            {
133
            {
124
                join => 'biblioitem',
134
                join => ['biblioitem', 'reserves'],
125
            }
135
            }
126
        );
136
        );
127
    }
137
    }
Lines 222-227 sub filter_by_bookable { Link Here
222
    );
232
    );
223
}
233
}
224
234
235
=head3 filter_by_closed_stack
236
237
  my $filterd_items = $items->filter_by_closed_stack;
238
239
Returns a new resultset, containing only those items that are flagged as
240
"closed stack".
241
242
=cut
243
244
sub filter_by_closed_stack {
245
    my ($self) = @_;
246
247
    return $self->search({ is_closed_stack => 1 });
248
}
249
250
225
=head3 move_to_biblio
251
=head3 move_to_biblio
226
252
227
 $items->move_to_biblio($to_biblio);
253
 $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 (+44 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
use Modern::Perl;
19
20
use GD::Barcode;
21
use MIME::Base64;
22
23
use base qw( Template::Plugin );
24
25
sub new {
26
    my ($class) = @_;
27
28
    my $self = {};
29
30
    return bless $self, $class;
31
}
32
33
sub create_as_data_url {
34
    my ($self, $type, $barcode, $args, $plot_args) = @_;
35
36
    $args //= {};
37
    $plot_args //= {};
38
39
    my $data = GD::Barcode->new($type, $barcode, $args)->plot(%$plot_args)->png;
40
41
    return 'data:image/png;base64,' . encode_base64($data);
42
}
43
44
1;
(-)a/admin/columns_settings.yml (+20 lines)
Lines 1891-1896 modules: Link Here
1891
            -
1891
            -
1892
              columnname: print_slip
1892
              columnname: print_slip
1893
1893
1894
      closed-stack-requests:
1895
        default_display_length: 20
1896
        default_sort_order: 0
1897
        columns:
1898
            - columnname: patron
1899
            - columnname: title
1900
            - columnname: libraries
1901
            - columnname: barcodes
1902
            - columnname: call_numbers
1903
            - columnname: copy_numbers
1904
            - columnname: stocknumber
1905
            - columnname: enumeration
1906
            - columnname: itemtypes
1907
            - columnname: locations
1908
            - columnname: collection
1909
            - columnname: hold_date
1910
            - columnname: reserve_notes
1911
            - columnname: pickup_location
1912
            - columnname: action
1913
1894
    holdsratios:
1914
    holdsratios:
1895
      holds-ratios:
1915
      holds-ratios:
1896
        default_display_length: 20
1916
        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 488-494 if (C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pref Link Here
488
488
489
#we only need to pass the number of holds to the template
489
#we only need to pass the number of holds to the template
490
my $holds = $biblio->holds;
490
my $holds = $biblio->holds;
491
$template->param( holdcount => $holds->count );
491
$template->param(
492
    holdcount => $holds->filter_out_closed_stack_requests->count,
493
    closed_stack_request_count => $holds->filter_by_closed_stack_requests->count,
494
);
492
495
493
# Check if there are any ILL requests connected to the biblio
496
# Check if there are any ILL requests connected to the biblio
494
my $illrequests =
497
my $illrequests =
(-)a/catalogue/updateitem.pl (+3 lines)
Lines 37-42 my $itemlost=$cgi->param('itemlost'); Link Here
37
my $itemnotes=$cgi->param('itemnotes');
37
my $itemnotes=$cgi->param('itemnotes');
38
my $itemnotes_nonpublic=$cgi->param('itemnotes_nonpublic');
38
my $itemnotes_nonpublic=$cgi->param('itemnotes_nonpublic');
39
my $withdrawn=$cgi->param('withdrawn');
39
my $withdrawn=$cgi->param('withdrawn');
40
my $is_closed_stack = $cgi->param('is_closed_stack');
40
my $damaged=$cgi->param('damaged');
41
my $damaged=$cgi->param('damaged');
41
my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority');
42
my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority');
42
my $bookable = $cgi->param('bookable') // q{};
43
my $bookable = $cgi->param('bookable') // q{};
Lines 73-78 elsif ( $op eq "cud-set_public_note" ) { # i.e., itemnotes parameter passed from Link Here
73
    $item->itemlost($itemlost);
74
    $item->itemlost($itemlost);
74
} elsif ( $op eq "cud-set_withdrawn" && $withdrawn ne $item_data_hashref->{'withdrawn'}) {
75
} elsif ( $op eq "cud-set_withdrawn" && $withdrawn ne $item_data_hashref->{'withdrawn'}) {
75
    $item->withdrawn($withdrawn);
76
    $item->withdrawn($withdrawn);
77
} elsif ( $op eq "cud-set_is_closed_stack") {
78
    $item->is_closed_stack($is_closed_stack);
76
} elsif ( $op eq "cud-set_exclude_priority" && $exclude_from_local_holds_priority ne $item_data_hashref->{'exclude_from_local_holds_priority'}) {
79
} elsif ( $op eq "cud-set_exclude_priority" && $exclude_from_local_holds_priority ne $item_data_hashref->{'exclude_from_local_holds_priority'}) {
77
    $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority);
80
    $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority);
78
    $messages = "updated_exclude_from_local_holds_priority=$exclude_from_local_holds_priority&";
81
    $messages = "updated_exclude_from_local_holds_priority=$exclude_from_local_holds_priority&";
(-)a/circ/circulation.pl (-1 / +2 lines)
Lines 594-600 if ($patron) { Link Here
594
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
594
    my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } );    # FIXME must be Koha::Patron->holds
595
    my $waiting_holds = $holds->waiting;
595
    my $waiting_holds = $holds->waiting;
596
    $template->param(
596
    $template->param(
597
        holds_count  => $holds->count(),
597
        holds_count  => $holds->filter_out_closed_stack_requests()->count(),
598
        closed_stack_requests_count => $holds->filter_by_closed_stack_requests()->count(),
598
        WaitingHolds => $waiting_holds,
599
        WaitingHolds => $waiting_holds,
599
    );
600
    );
600
601
(-)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 (+39 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("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`");
12
            say $out "Column items.is_closed_stack created";
13
        } else {
14
            say $out "Column items.is_closed_stack not created (already exists)";
15
        }
16
17
        unless (column_exists('deleteditems', 'is_closed_stack')) {
18
            $dbh->do("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`");
19
            say $out "Column deleteditems.is_closed_stack created";
20
        } else {
21
            say $out "Column deleteditems.is_closed_stack not created (already exists)";
22
        }
23
24
        unless (column_exists('reserves', 'closed_stack_request_slip_printed')) {
25
            $dbh->do("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`");
26
            say $out "Column reserves.closed_stack_request_slip_printed created";
27
        } else {
28
            say $out "Column reserves.closed_stack_request_slip_printed not created (already exists)";
29
        }
30
31
        unless (column_exists('old_reserves', 'closed_stack_request_slip_printed')) {
32
            $dbh->do("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`");
33
            say $out "Column old_reserves.closed_stack_request_slip_printed created";
34
        } else {
35
            say $out "Column old_reserves.closed_stack_request_slip_printed not created (already exists)";
36
        }
37
38
    },
39
};
(-)a/installer/data/mysql/kohastructure.sql (+3 lines)
Lines 4064-4069 CREATE TABLE `items` ( Link Here
4064
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4064
  `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)',
4065
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4065
  `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)',
4066
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4066
  `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)',
4067
  `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item',
4067
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4068
  `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format',
4068
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4069
  `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)',
4069
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
4070
  `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)',
Lines 5044-5049 CREATE TABLE `old_reserves` ( Link Here
5044
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5045
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5045
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5046
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5046
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5047
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5048
  `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)',
5047
  PRIMARY KEY (`reserve_id`),
5049
  PRIMARY KEY (`reserve_id`),
5048
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5050
  KEY `old_reserves_borrowernumber` (`borrowernumber`),
5049
  KEY `old_reserves_biblionumber` (`biblionumber`),
5051
  KEY `old_reserves_biblionumber` (`biblionumber`),
Lines 5614-5619 CREATE TABLE `reserves` ( Link Here
5614
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5616
  `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting',
5615
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5617
  `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level',
5616
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5618
  `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold',
5619
  `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)',
5617
  PRIMARY KEY (`reserve_id`),
5620
  PRIMARY KEY (`reserve_id`),
5618
  KEY `priorityfoundidx` (`priority`,`found`),
5621
  KEY `priorityfoundidx` (`priority`,`found`),
5619
  KEY `borrowernumber` (`borrowernumber`),
5622
  KEY `borrowernumber` (`borrowernumber`),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc (-2 / +9 lines)
Lines 61-74 Link Here
61
        [% END %]
61
        [% END %]
62
62
63
        [%- IF ( CAN_user_reserveforothers ) -%]
63
        [%- IF ( CAN_user_reserveforothers ) -%]
64
        [%- IF ( holdsview ) -%]
64
        [%- IF ( holdsview && !closed_stack_request ) -%]
65
        <li class="active">
65
        <li class="active">
66
        [%- ELSE -%]
66
        [%- ELSE -%]
67
        <li>
67
        <li>
68
        [%- END -%]
68
        [%- END -%]
69
            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio_object_id | url  %]">Holds ([% biblio.holds.count | html %])</a>
69
            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio_object_id | url  %]">Holds ([% biblio.holds.filter_out_closed_stack_requests.count | html %])</a>
70
        </li>
70
        </li>
71
        [%- END -%]
71
        [%- END -%]
72
        [%- IF ( holdsview && closed_stack_request ) -%]
73
            <li class="active">
74
        [%- ELSE -%]
75
            <li>
76
        [%- END -%]
77
            <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>
78
        </li>
72
79
73
        [%- IF ( EasyAnalyticalRecords ) -%]
80
        [%- IF ( EasyAnalyticalRecords ) -%]
74
        [%- IF ( analyze ) -%]
81
        [%- IF ( analyze ) -%]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc (+4 lines)
Lines 270-275 Link Here
270
            <div class="btn-group"><a id="placehold" class="btn btn-default" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | html %]" role="button"><i class="fa-solid fa-bookmark"></i> Place hold</a></div>
270
            <div class="btn-group"><a id="placehold" class="btn btn-default" href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblionumber | html %]" role="button"><i class="fa-solid fa-bookmark"></i> Place hold</a></div>
271
        [% END %]
271
        [% END %]
272
    [% END %]
272
    [% END %]
273
274
    [% IF items.filter_by_closed_stack.count %]
275
        <div class="btn-group"><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>
276
    [% END %]
273
[% END %]
277
[% END %]
274
278
275
[% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
279
[% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc (+3 lines)
Lines 54-59 Link Here
54
            <li>
54
            <li>
55
                <a href="/cgi-bin/koha/circ/pendingreserves.pl">Holds to pull</a>
55
                <a href="/cgi-bin/koha/circ/pendingreserves.pl">Holds to pull</a>
56
            </li>
56
            </li>
57
            <li>
58
                <a href="/cgi-bin/koha/circ/closed-stack-requests.pl">Closed stack requests</a>
59
            </li>
57
            <li>
60
            <li>
58
                <a href="/cgi-bin/koha/circ/waitingreserves.pl">Holds awaiting pickup</a>
61
                <a href="/cgi-bin/koha/circ/waitingreserves.pl">Holds awaiting pickup</a>
59
            </li>
62
            </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 215-220 Link Here
215
                [% END %]
220
                [% END %]
216
            [% END # /tab_panel#holds %]
221
            [% END # /tab_panel#holds %]
217
222
223
            [% IF closed_stack_requests_count > 0 %]
224
                [% WRAPPER tab_panel tabname="closed-stack-requests" %]
225
                    <div id="closed-stack-requests" role="tabpanel" class="tab-pane">
226
                        <form action="/cgi-bin/koha/reserve/modrequest.pl" method="post">
227
                            <input type="hidden" name="from" value="circ" />
228
229
                            <table id="closed-stack-requests-table">
230
                                <thead>
231
                                    <tr>
232
                                        <th>Hold date</th>
233
                                        <th>Title</th>
234
                                        <th>Call number</th>
235
                                        <th>Item type</th>
236
                                        <th>Barcode</th>
237
                                        <th>Expiration</th>
238
                                        <th>Priority</th>
239
                                        <th>Delete?</th>
240
                                        <th>Status</th>
241
                                    </tr>
242
                                </thead>
243
                            </table>
244
245
                            <fieldset class="action">
246
                                <input type="submit" class="cancel" name="submit" value="Cancel marked holds" />
247
248
                                [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
249
                                [% IF hold_cancellation.count %]
250
                                    <label for="cancellation-reason">Cancellation reason:</label>
251
                                    <select name="cancellation-reason">
252
                                        <option value="">No reason given</option>
253
                                        [% FOREACH reason IN hold_cancellation %]
254
                                            <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
255
                                        [% END %]
256
                                    </select>
257
                                [% END %]
258
                            </fieldset>
259
                        </form>
260
                    </div>
261
                [% END %]
262
            [% END %]
263
218
            [% WRAPPER tab_panel tabname="bookings" %]
264
            [% WRAPPER tab_panel tabname="bookings" %]
219
                [% IF ( bookings_count ) %]
265
                [% IF ( bookings_count ) %]
220
                    <fieldset class="action filters" style="cursor:pointer;">
266
                    <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 (+27 lines)
Lines 315-320 Link Here
315
                                            [% END %]
315
                                            [% END %]
316
                                        [% END %]
316
                                        [% END %]
317
317
318
                                        <li>
319
                                            <span class="label">Closed stack:</span>
320
                                            [% IF ( CAN_user_circulate ) %]
321
                                                <form action="updateitem.pl" method="post">
322
                                                    [% INCLUDE 'csrf-token.inc' %]
323
                                                    <input type="hidden" name="biblionumber" value="[% ITEM_DAT.biblionumber | html %]" />
324
                                                    <input type="hidden" name="biblioitemnumber" value="[% ITEM_DAT.biblioitemnumber | html %]" />
325
                                                    <input type="hidden" name="itemnumber" value="[% ITEM_DAT.itemnumber | html %]" />
326
                                                    <select name="is_closed_stack" >
327
                                                        <option value="0">No</option>
328
                                                        [% IF ITEM_DAT.is_closed_stack %]
329
                                                            <option value="1" selected>Yes</option>
330
                                                        [% ELSE %]
331
                                                            <option value="1">Yes</option>
332
                                                        [% END %]
333
                                                    </select>
334
                                                    <input type="hidden" name="op" value="cud-set_is_closed_stack" />
335
                                                    <input type="submit" name="submit" class="btn btn-primary btn-xs" value="Set status" />
336
                                                </form>
337
                                            [% ELSE %]
338
                                                [% IF ITEM_DAT.is_closed_stack %]
339
                                                    <span>Yes</span>
340
                                                [% ELSE %]
341
                                                    <span>No</span>
342
                                                [% END %]
343
                                            [% END %]
344
                                        </li>
318
                                </ol> <!-- /.bibliodetails -->
345
                                </ol> <!-- /.bibliodetails -->
319
                                    </div> <!-- /.rows -->
346
                                    </div> <!-- /.rows -->
320
                            </div> <!-- /.listgroup -->
347
                            </div> <!-- /.listgroup -->
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt (+3 lines)
Lines 87-92 Link Here
87
                            <li>
87
                            <li>
88
                                <a class="circ-button" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
88
                                <a class="circ-button" href="/cgi-bin/koha/circ/pendingreserves.pl"><i class="fa-solid fa-hand-back-fist"></i> Holds to pull</a>
89
                            </li>
89
                            </li>
90
                            <li>
91
                                <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>
92
                            </li>
90
                            <li>
93
                            <li>
91
                                <a class="circ-button" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
94
                                <a class="circ-button" href="/cgi-bin/koha/circ/waitingreserves.pl"><i class="fa-solid fa-calendar-days"></i> Holds awaiting pickup</a>
92
                            </li>
95
                            </li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt (+327 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Branches %]
4
[% USE Koha %]
5
[% USE KohaDates %]
6
[% USE TablesSettings %]
7
[% USE AuthorisedValues %]
8
[%- USE Branches -%]
9
[%- USE ItemTypes -%]
10
[% SET footerjs = 1 %]
11
[% INCLUDE 'doc-head-open.inc' %]
12
<title>Closed stack requests &rsaquo; Circulation &rsaquo; Koha</title>
13
[% INCLUDE 'doc-head-close.inc' %]
14
</head>
15
16
<body id="circ_closed_stack_requests" class="circ">
17
[% WRAPPER 'header.inc' %]
18
    [% INCLUDE 'circ-search.inc' %]
19
[% END %]
20
21
[% WRAPPER 'sub-header.inc' %]
22
    [% WRAPPER breadcrumbs %]
23
        [% WRAPPER breadcrumb_item %]
24
            <a href="/cgi-bin/koha/circ/circulation-home.pl">Circulation</a>
25
        [% END %]
26
        [% WRAPPER breadcrumb_item bc_active= 1 %]
27
            <span>Closed stack requests</span>
28
        [% END %]
29
    [% END #/ WRAPPER breadcrumbs %]
30
[% END #/ WRAPPER sub-header.inc %]
31
32
<div class="main container-fluid">
33
    <div class="row">
34
        <div class="col-md-10 order-md-2 order-sm-1">
35
            <main>
36
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>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>
83
                                [% INCLUDE 'biblio-title.inc' biblio=biblio link = 1 %]
84
                            </p>
85
                            [% IF ( biblio.author ) %]<p> by [% biblio.author | html %]</p>[% END %]
86
                            [% IF ( biblio.biblioitem.editionstatement ) %]<p>[% biblio.biblioitem.editionstatement | html %]</p>[% END %]
87
                            [% IF ( Koha.Preference('marcflavour') == 'MARC21' ) %]
88
                                [% IF ( biblio.copyrightdate ) %]<p>[% biblio.copyrightdate | html %]</p>[% END %]
89
                            [% ELSE %]
90
                                [% IF ( biblio.biblioitem.publicationyear ) %]<p>[% biblio.biblioitem.publicationyear | html %]</p>[% END %]
91
                            [% END %]
92
                        </td>
93
94
                        <td>[% Branches.GetName(item.holdingbranch) | html %]</td>
95
96
                        <td>[% item.barcode | html %]</td>
97
98
                        <td>[% item.itemcallnumber | html %]</td>
99
100
                        <td>[% item.copynumber | html %]</td>
101
102
                        <td>[% item.stocknumber | html %]</td>
103
104
                        <td>[% item.enumchron | html %]</td>
105
106
                        <td>[% ItemTypes.GetDescription(item.itype) | html %]</td>
107
108
                        <td>[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => item.location ) | html %]
109
110
                        <td>[% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => item.ccode ) | html %]</td>
111
112
                        <td data-order="[% hold.reservedate | html %]">
113
                            [% hold.reservedate | $KohaDates %] in [% Branches.GetName ( hold.branchcode ) | html %]
114
                        </td>
115
116
                        <td>[% hold.reservenotes | html %]</td>
117
118
                        <td>[% Branches.GetName ( hold.branchcode ) | html %]</td>
119
120
                        <td>
121
                            <form method="post" id="print-closed-stack-request-slip-[% hold.reserve_id | html %]" target="_blank">
122
                                [% INCLUDE 'csrf-token.inc' %]
123
                                <input type="hidden" name="op" value="cud-print_slip">
124
                                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
125
                                <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>
126
                            </form>
127
128
                            <form name="cancelReserve" action="/cgi-bin/koha/circ/closed-stack-requests.pl" method="post">
129
                                [% INCLUDE 'csrf-token.inc' %]
130
                                <input type="hidden" name="op" value="cud-cancel_reserve" />
131
                                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
132
133
                                [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
134
                                [% IF hold_cancellation.count %]
135
                                    <div class="form-group">
136
                                        <label for="cancellation-reason">Cancellation reason:</label>
137
                                        <select class="cancellation-reason" name="cancellation-reason" id="cancellation-reason">
138
                                            <option value="">No reason given</option>
139
                                            [% FOREACH reason IN hold_cancellation %]
140
                                                <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
141
                                            [% END %]
142
                                        </select>
143
                                    </div>
144
                                [% END %]
145
146
                                [% IF item.holdingbranch != item.homebranch %]
147
                                    <button class="btn btn-default btn-sm" type="submit">Cancel hold and return to : [% Branches.GetName( item.homebranch ) | html %]</button>
148
                                [% ELSE %]
149
                                    <button class="btn btn-default btn-sm" type="submit">Cancel hold</button>
150
                                [% END %]
151
                            </form>
152
                        </td>
153
                    </tr>
154
                [% END %]
155
            </tbody>
156
157
            <tfoot>
158
                <tr>
159
                    <td><input type="text" class="filter" data-column_num="0" placeholder="Patron name" style="width:95%"/></td>
160
                    <td><input type="text" class="filter" data-column_num="1" placeholder="Title" style="width:95%"/></td>
161
                    <td class="homebranchfilter"></td>
162
                    <td></td>
163
                    <td><input type="text" class="filter" data-column_num="4" placeholder="Call number" style="width:95%"/></td>
164
                    <td><input type="text" class="filter" data-column_num="5" placeholder="Copy number" style="width:95%"/></td>
165
                    <td><input type="text" class="filter" data-column_num="6" placeholder="Stocknumber" style="width:95%"/></td>
166
                    <td><input type="text" class="filter" data-column_num="7" placeholder="Available enumeration" style="width:95%"/></td>
167
                    <td class="itemtype-filter"></td>
168
                    <td class="locationfilter"></td>
169
                    <td></td>
170
                    <td></td>
171
                    <td></td>
172
                    <td class="pickup-location"></td>
173
                    <td></td>
174
                </tr>
175
            </tfoot>
176
        </table>
177
    [% ELSE %]
178
        <strong>No items found.</strong>
179
    [% END %]
180
[% END %]
181
182
[% PROCESS 'html_helpers.inc' %]
183
[% WRAPPER tabs %]
184
    [% WRAPPER tabs_nav %]
185
        [% WRAPPER tab_item tabname="pending" bt_active=1 %]<span>Pending</span> ([% pending_holds.size || 0 | html %])[% END %]
186
        [% WRAPPER tab_item tabname="printed" %]<span>Slip printed</span> ([% printed_slip_holds.size || 0 | html %])[% END %]
187
    [% END %]
188
    [% WRAPPER tab_panels %]
189
        [% WRAPPER tab_panel tabname="pending" bt_active=1 %]
190
            <p>The following holds have not been filled. Please retrieve them and check them in.</p>
191
192
            [% INCLUDE holds_table id="holdst" holds=pending_holds %]
193
        [% END %]
194
195
        [% WRAPPER tab_panel tabname="printed" %]
196
            [% INCLUDE holds_table id="printed_slip_holds" holds=printed_slip_holds %]
197
        [% END %]
198
    [% END %]
199
[% END %]
200
201
202
            </main>
203
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
204
                        <div class="col-sm-2 col-sm-pull-10">
205
                        <aside>
206
                            <form>
207
                                <fieldset class="brief">
208
                                    <h4>Filters</h4>
209
                                    <label for="library">Library</label>
210
                                        <select id="library" name="branchcode">
211
                                            <option value="">All</option>
212
                                            [% FOREACH library IN Branches.all() %]
213
                                                [% IF branchcode && branchcode == library.branchcode %]
214
                                                    <option value="[% library.branchcode | html %]" selected>[% library.branchname %]</option>
215
                                                [% ELSE %]
216
                                                    <option value="[% library.branchcode | html %]">[% library.branchname %]</option>
217
                                                [% END %]
218
                                            [% END %]
219
                                        </select>
220
                                </fieldset>
221
                                <fieldset class="action">
222
                                    <button class="btn btn-primary" type="submit">Filter</button>
223
                                </fieldset>
224
                            </form>
225
                        </aside>
226
                    [% IF Koha.Preference('CircSidebar') %]
227
                            <aside>
228
                                [% INCLUDE 'circ-nav.inc' %]
229
                            </aside>
230
                    [% END %]
231
                        </div> <!-- /.col-sm-2.col-sm-pull-10 -->
232
233
     </div> <!-- /.row -->
234
235
[% MACRO jsinclude BLOCK %]
236
    [% INCLUDE 'calendar.inc' %]
237
    [% INCLUDE 'datatables.inc' %]
238
    [% INCLUDE 'columns_settings.inc' %]
239
    <script>
240
        function separateData ( ColumnData ){
241
            var cD = ColumnData;
242
            var new_array = new Array();
243
            for ( j=0 ; j<cD.length ; j++ ) {
244
                var split_array = cD[j].split(/\n/gi);
245
                for ( k=0 ; k<split_array.length ; k++ ){
246
                    var str = $.trim(split_array[k].replace(/[\n\r]/g, ''));
247
                    if ($.inArray(str, new_array) == -1 && str.length > 0 ) {
248
                        new_array.push(str);
249
                    }
250
                }
251
            }
252
            new_array.sort();
253
            return new_array;
254
        }
255
256
        function createSelect( data ) {
257
            data = separateData(data);
258
            var r='<select style="width:99%"><option value="">' + _("None") + '</option>', i, len=data.length;
259
            var regex = /(<([^>]+)>)/ig; // Remove html tags
260
            for ( i=0 ; i<len ; i++ ) {
261
                var cell_val = data[i].replace(regex, '');
262
                if ( cell_val.length < 1 ) continue;
263
                r += '<option value="'+cell_val+'">'+cell_val+'</option>';
264
            }
265
            return r+'</select>';
266
        }
267
268
        $(document).ready(function() {
269
            var table_settings = [% TablesSettings.GetTableSettings('circ', 'holds', 'closed-stack-requests', 'json') | $raw %];
270
            $('#holdst, #printed_slip_holds').each(function (i, el) {
271
                const holdst = KohaTable(el.id, {
272
                    "sPaginationType": "full_numbers",
273
                    autoWidth: false,
274
                }, table_settings);
275
                holdst.fnAddFilters("filter");
276
277
                $(".homebranchfilter", el).each( function () {
278
                    $(this).html( createSelect( holdst.fnGetColumnData(2) ) );
279
                    $('select', this).change( function () {
280
                        holdst.fnFilter( $(this).val(), 2 );
281
                    });
282
                });
283
                $(".itemtype-filter", el).each( function () {
284
                    $(this).html( createSelect( holdst.fnGetColumnData(8) ) );
285
                    $('select', this).change( function () {
286
                        holdst.fnFilter( $(this).val(), 7 );
287
                    });
288
                });
289
                $(".locationfilter", el).each( function () {
290
                    $(this).html( createSelect( holdst.fnGetColumnData(9) ) );
291
                    $('select', this).change( function () {
292
                        holdst.fnFilter( $(this).val(), 8 );
293
                    });
294
                });
295
                $(".pickup-location", el).each( function () {
296
                    $(this).html( createSelect( holdst.fnGetColumnData(13) ) );
297
                    $('select', this).change( function () {
298
                        holdst.fnFilter( $(this).val(), 12 );
299
                    });
300
                });
301
            });
302
        });
303
    </script>
304
    <script>
305
        $(document).ready(function() {
306
            // Printing slip will change the reserve's status
307
            // Reload the page to make the status change visible
308
            $('#holdst .print-closed-stack-request-slip').on('click', function (ev) {
309
                const reserve_id = $(this).data('reserve-id');
310
                const form = document.getElementById('print-closed-stack-request-slip-' + reserve_id);
311
                if (form) {
312
                    form.submit();
313
                    setTimeout(() => { location.reload() }, 1000);
314
                }
315
            });
316
            $('#holdst .set-waiting').on('click', function (ev) {
317
                const reserve_id = $(this).data('reserve-id');
318
                const form = document.getElementById('set-waiting-' + reserve_id);
319
                if (form) {
320
                    form.submit();
321
                }
322
            });
323
        });
324
    </script>
325
[% END %]
326
327
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt (-1 / +8 lines)
Lines 180-186 Link Here
180
                <div class="row">
180
                <div class="row">
181
                    <div class="col-sm-12">
181
                    <div class="col-sm-12">
182
                        [%# Following statement must be in one line for translatability %]
182
                        [%# Following statement must be in one line for translatability %]
183
                        [% 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 ) %]
183
                        [% 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 ) || pending_closed_stack_requests.count %]
184
                            <div id="area-pending" class="page-section">
184
                            <div id="area-pending" class="page-section">
185
                                [% IF pending_article_requests %]
185
                                [% IF pending_article_requests %]
186
                                <div class="pending-info" id="article_requests_pending">
186
                                <div class="pending-info" id="article_requests_pending">
Lines 275-280 Link Here
275
                                        <span class="pending-number-link">[% holds_with_cancellation_requests | html %]</span>
275
                                        <span class="pending-number-link">[% holds_with_cancellation_requests | html %]</span>
276
                                    </div>
276
                                    </div>
277
                                [% END %]
277
                                [% END %]
278
279
                                [% IF pending_closed_stack_requests.count %]
280
                                    <div class="pending-info" id="pending_closed_stack_requests">
281
                                        <a href="/cgi-bin/koha/circ/closed-stack-requests.pl">Pending closed stack requests</a>:
282
                                        <span class="pending-number-link">[% pending_closed_stack_requests.count | html %]</span>
283
                                    </div>
284
                                [% END %]
278
                            </div>
285
                            </div>
279
286
280
                        [% END %]
287
                        [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-166 / +190 lines)
Lines 122-128 Link Here
122
                [% INCLUDE 'biblio-title.inc' link =1 %]
122
                [% INCLUDE 'biblio-title.inc' link =1 %]
123
            [% END %]
123
            [% END %]
124
            [% WRAPPER breadcrumb_item bc_active= 1 %]
124
            [% WRAPPER breadcrumb_item bc_active= 1 %]
125
                <span>Place a hold</span>
125
                [% IF closed_stack_request %]
126
                    <span>Closed stack request</span>
127
                [% ELSE %]
128
                    <span>Place a hold</span>
129
                [% END %]
126
            [% END %]
130
            [% END %]
127
        [% ELSE %]
131
        [% ELSE %]
128
            [% IF ( patron ) %]
132
            [% IF ( patron ) %]
Lines 226-232 Link Here
226
            [% END %]
230
            [% END %]
227
231
228
            [% UNLESS ( multi_hold ) %]
232
            [% UNLESS ( multi_hold ) %]
229
                <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]</h2>
233
                [% IF closed_stack_request %]
234
                    <h2>Closed stack request on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]</h2>
235
                [% ELSE %]
236
                    <h2>Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]</h2>
237
                [% END %]
230
            [% ELSE %]
238
            [% ELSE %]
231
                <h2>
239
                <h2>
232
                    [% IF ( patron ) %]
240
                    [% 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-796 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">
628
                                        Hold next available item
629
                                    </label>
630
                                </legend>
631
                                <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
632
                                <fieldset class="enable_request_any disable_request_group disable_request_specific">
633
                                [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
634
                                    <span class="error">
635
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
636
                                        Hold must be item level
637
                                    </span>
638
                                [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
639
                                    <span class="error">
640
                                        <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
641
                                        Hold must be item group level
642
                                    </span>
643
                                [% ELSE %]
644
                                    <ol>
645
646
                                        <li>
647
                                            <label for="pickup">Pickup at:</label>
648
                                            <select name="pickup" id="pickup-next-avail"
649
                                                    data-biblio-id="[% biblio.biblionumber | html %]"
650
                                                    data-patron-id="[% patron.borrowernumber | html %]"
651
                                                    data-pickup-location-source="biblio">
652
                                                [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
653
                                            </select>
654
                                        </li>
655
628
656
                                        [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
629
                            [% biblio_info = biblioloop.0 %]
657
                                            <li>
630
658
                                                <label for="itemtype">Request specific item type:</label>
631
                            [% UNLESS closed_stack_request %]
659
                                                <select name="itemtype" id="itemtype">
632
                                <fieldset class="rows any_specific">
660
                                                    <option value="">Any item type</option>
633
                                    <legend>
661
                                                    [%- FOREACH itemtype IN available_itemtypes %]
634
                                        [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %]
662
                                                        <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
635
                                            <input type="radio" id="requestany" name="request" disabled="true" />
663
                                                    [%- END %]
636
                                        [% ELSIF force_hold_level == 'record' %]
664
                                                </select>
637
                                            <input type="radio" id="requestany" checked="checked" value="Any" disabled="true"/>
665
                                            </li>
638
                                            <input type="hidden" name="request" value="Any"/>
666
                                        [% END %]
639
                                            <span class="error"><i>(Required)</i></span>
667
                                        [% UNLESS remaining_holds_for_record == 1 %]
668
                                            <li>
669
                                                <label for="holds_to_place_count">Holds to place (count)</label>
670
                                                <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
671
                                            </li>
672
                                        [% ELSE %]
640
                                        [% ELSE %]
673
                                            <input type="hidden" name="holds_to_place_count" value="1" />
641
                                            <input type="radio" id="requestany" name="request" checked="checked" value="Any" />
674
                                        [% END %]
642
                                        [% END %]
675
                                    </ol>
643
                                        <label for="requestany" class="inline">
676
                                [% END %]
644
                                            Hold next available item
645
                                        </label>
646
                                    </legend>
647
                                    <input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
648
                                    <fieldset class="enable_request_any disable_request_group disable_request_specific">
649
                                        [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %]
650
                                            <span class="error">
651
                                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
652
                                                Hold must be item level
653
                                            </span>
654
                                        [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %]
655
                                            <span class="error">
656
                                                <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
657
                                                Hold must be item group level
658
                                            </span>
659
                                        [% ELSE %]
660
                                            <ol>
677
661
662
                                                <li>
663
                                                    <label for="pickup">Pickup at:</label>
664
                                                    <select name="pickup" id="pickup-next-avail"
665
                                                            data-biblio-id="[% biblio.biblionumber | html %]"
666
                                                            data-patron-id="[% patron.borrowernumber | html %]"
667
                                                            data-pickup-location-source="biblio">
668
                                                        [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
669
                                                    </select>
670
                                                </li>
678
671
679
                                <fieldset class="action">
672
                                                [% IF Koha.Preference('AllowHoldItemTypeSelection') %]
680
                                    [% IF ( patron.borrowernumber ) %]
673
                                                    <li>
681
                                        [% IF ( override_required ) %]
674
                                                        <label for="itemtype">Request specific item type:</label>
682
                                            <button type="submit" id="hold_grp_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
675
                                                        <select name="itemtype" id="itemtype">
683
                                        [% ELSIF ( none_available ) %]
676
                                                            <option value="">Any item type</option>
684
                                            <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
677
                                                            [%- FOREACH itemtype IN available_itemtypes %]
685
                                        [% ELSE %]
678
                                                                <option value="[% itemtype | html %]">[% ItemTypes.GetDescription( itemtype ) | html %]</option>
686
                                            <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
679
                                                            [%- END %]
680
                                                        </select>
681
                                                    </li>
682
                                                [% END %]
683
                                                [% UNLESS remaining_holds_for_record == 1 %]
684
                                                    <li>
685
                                                        <label for="holds_to_place_count">Holds to place (count)</label>
686
                                                        <input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
687
                                                    </li>
688
                                                [% ELSE %]
689
                                                    <input type="hidden" name="holds_to_place_count" value="1" />
690
                                                [% END %]
691
                                            </ol>
687
                                        [% END %]
692
                                        [% END %]
688
                                    [% END %]
689
                                </fieldset>
690
                            </fieldset>
691
                        </fieldset>
692
693
                        <hr/>
694
693
695
                        [% biblio_info = biblioloop.0 %]
696
                        <!-- ItemGroup level holds -->
697
                        [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
698
                        <fieldset class="rows any_specific">
699
                            <legend>
700
                                [% IF force_hold_level == 'item_group' %]
701
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
702
                                    <span class="error"><i>(Required)</i></span>
703
                                [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
704
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
705
                                [% ELSE %]
706
                                    <input type="radio" class="requestgrp" id="requestgrp" name="request" />
707
                                [% END %]
708
                                <label for="requestgrp" class="inline">
709
                                    Hold next available item from an item group
710
                                </label>
711
                            </legend>
712
694
713
                            <fieldset class="enable_request_group disable_request_any disable_request_specific">
695
                                        <fieldset class="action">
714
                            [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
696
                                            [% IF ( patron.borrowernumber ) %]
715
                                <span class="error">
697
                                                [% IF ( override_required ) %]
716
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
698
                                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
717
                                    Hold must be record level
699
                                                [% ELSIF ( none_available ) %]
718
                                </span>
700
                                                    <button type="submit" id="hold_grp_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
719
                            [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
701
                                                [% ELSE %]
720
                                <span class="error">
702
                                                    <button type="submit" id="hold_grp_btn" class="btn btn-primary">Place hold</button>
721
                                    <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
722
                                    Hold must be item level
723
                                </span>
724
                            [% ELSE %]
725
                                <ul>
726
                                    <li>
727
                                        <label for="pickup">Pickup at:</label>
728
                                        <select name="pickup" id="pickup-item-group"
729
                                                data-biblio-id="[% biblio.biblionumber | html %]"
730
                                                data-patron-id="[% patron.borrowernumber | html %]"
731
                                                data-pickup-location-source="biblio">
732
                                            [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
733
                                        </select>
734
                                    </li>
735
                                    <li>
736
                                        <table id="requestgroup">
737
                                            <thead>
738
                                                <tr>
739
                                                    <th>Hold</th>
740
                                                    <th>Item group</th>
741
                                                    <th>Holdable items</th>
742
                                                </tr>
743
                                            </thead>
744
                                            <tbody>
745
                                                [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
746
                                                    [% IF g.items.count %]
747
                                                        <tr>
748
                                                            <td>
749
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
750
                                                            </td>
751
                                                            <td>
752
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
753
                                                            </td>
754
                                                            <td>
755
                                                                [% FOREACH i IN g.items %]
756
                                                                    <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
757
                                                                [% END %]
758
                                                            </td>
759
                                                        </tr>
760
                                                    [% ELSE %]
761
                                                        <tr>
762
                                                            <td>
763
                                                                <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
764
                                                            </td>
765
                                                            <td>
766
                                                                <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
767
                                                            </td>
768
                                                            <td>
769
                                                                <div class="error">No holdable items in this item group.</div>
770
                                                            </td>
771
                                                        </tr>
772
                                                    [% END %]
773
                                                [% END %]
703
                                                [% END %]
774
                                            </tbody>
704
                                            [% END %]
775
                                        </table>
705
                                        </fieldset>
776
                                    </li>
706
                                    </fieldset>
777
                                </ul>
707
                                </fieldset>
778
                            [% END %]
708
779
                                <fieldset class="action">
709
                                <hr/>
780
                                    [% IF ( patron.borrowernumber ) %]
710
781
                                        [% IF ( override_required ) %]
711
                                <!-- ItemGroup level holds -->
782
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
712
                                [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %]
783
                                        [% ELSIF ( none_available ) %]
713
                                <fieldset class="rows any_specific">
784
                                            <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
714
                                    <legend>
715
                                        [% IF force_hold_level == 'item_group' %]
716
                                            <input type="radio" class="requestgrp" id="requestgrp" name="request" checked="checked" disabled="true" />
717
                                            <span class="error"><i>(Required)</i></span>
718
                                        [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %]
719
                                            <input type="radio" class="requestgrp" id="requestgrp" name="request" disabled="true" />
785
                                        [% ELSE %]
720
                                        [% ELSE %]
786
                                            <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
721
                                            <input type="radio" class="requestgrp" id="requestgrp" name="request" />
787
                                        [% END %]
722
                                        [% END %]
723
                                        <label for="requestgrp" class="inline">
724
                                            Hold next available item from an item group
725
                                        </label>
726
                                    </legend>
727
728
                                    <fieldset class="enable_request_group disable_request_any disable_request_specific">
729
                                    [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %]
730
                                        <span class="error">
731
                                            <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
732
                                            Hold must be record level
733
                                        </span>
734
                                    [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %]
735
                                        <span class="error">
736
                                            <i class="fa fa-times fa-lg" title="Cannot be put on hold"></i>
737
                                            Hold must be item level
738
                                        </span>
739
                                    [% ELSE %]
740
                                        <ul>
741
                                            <li>
742
                                                <label for="pickup">Pickup at:</label>
743
                                                <select name="pickup" id="pickup-item-group"
744
                                                        data-biblio-id="[% biblio.biblionumber | html %]"
745
                                                        data-patron-id="[% patron.borrowernumber | html %]"
746
                                                        data-pickup-location-source="biblio">
747
                                                    [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
748
                                                </select>
749
                                            </li>
750
                                            <li>
751
                                                <table id="requestgroup">
752
                                                    <thead>
753
                                                        <tr>
754
                                                            <th>Hold</th>
755
                                                            <th>Item group</th>
756
                                                            <th>Holdable items</th>
757
                                                        </tr>
758
                                                    </thead>
759
                                                    <tbody>
760
                                                        [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %]
761
                                                            [% IF g.items.count %]
762
                                                                <tr>
763
                                                                    <td>
764
                                                                        <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" />
765
                                                                    </td>
766
                                                                    <td>
767
                                                                        <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
768
                                                                    </td>
769
                                                                    <td>
770
                                                                        [% FOREACH i IN g.items %]
771
                                                                            <div><a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% i.biblionumber | uri %]#item[% i.itemnumber | uri %]">[% i.barcode | html %]</a></div>
772
                                                                        [% END %]
773
                                                                    </td>
774
                                                                </tr>
775
                                                            [% ELSE %]
776
                                                                <tr>
777
                                                                    <td>
778
                                                                        <input id="item_group_id_[% g.id | html %]" class="requestgrp" type="radio" name="item_group_id" value="[% g.id | html %]" disabled="disabled" />
779
                                                                    </td>
780
                                                                    <td>
781
                                                                        <label for="item_group_id_[% g.id | html %]">[% g.description | html %]</label>
782
                                                                    </td>
783
                                                                    <td>
784
                                                                        <div class="error">No holdable items in this item group.</div>
785
                                                                    </td>
786
                                                                </tr>
787
                                                            [% END %]
788
                                                        [% END %]
789
                                                    </tbody>
790
                                                </table>
791
                                            </li>
792
                                        </ul>
788
                                    [% END %]
793
                                    [% END %]
794
795
                                        <fieldset class="action">
796
                                            [% IF ( patron.borrowernumber ) %]
797
                                                [% IF ( override_required ) %]
798
                                                    <button type="submit" id="hold_any_btn" class="btn btn-primary warning"><i class="fa fa-exclamation-triangle "></i> Place hold</button>
799
                                                [% ELSIF ( none_available ) %]
800
                                                    <button type="submit" id="hold_any_btn" disabled="disabled" class="btn btn-primary btn-disabled">Place hold</button>
801
                                                [% ELSE %]
802
                                                    <button type="submit" id="hold_any_btn" class="btn btn-primary">Place hold</button>
803
                                                [% END %]
804
                                            [% END %]
805
                                        </fieldset>
806
                                    </fieldset>
789
                                </fieldset>
807
                                </fieldset>
790
                            </fieldset>
808
                            [% END # UNLESS closed_stack_request %]
791
                        </fieldset>
809
                            <!-- /ItemGroup level holds -->
792
                        [% END %]
810
                        [% END %]
793
                        <!-- /ItemGroup level holds -->
794
811
795
                        <fieldset class="rows any_specific">
812
                        <fieldset class="rows any_specific">
796
                            <legend>
813
                            <legend>
Lines 1188-1193 Link Here
1188
                                                            </ul>
1205
                                                            </ul>
1189
                                                        [% END %]
1206
                                                        [% END %]
1190
                                                    [% END %]
1207
                                                    [% END %]
1208
1209
                                                    [% IF itemloo.is_closed_stack %]
1210
                                                        <br><span>Closed stack</span>
1211
                                                    [% END %]
1191
                                                </td>
1212
                                                </td>
1192
                                            </tr>
1213
                                            </tr>
1193
                                        [% END # /FOREACH biblioloo %]
1214
                                        [% END # /FOREACH biblioloo %]
Lines 1490-1495 Link Here
1490
    [% IF multi_hold %]
1511
    [% IF multi_hold %]
1491
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1512
        [% SET url_biblio_params = url_biblio_params _ "&amp;multi_hold=1" %]
1492
    [% END %]
1513
    [% END %]
1514
    [% IF closed_stack_request %]
1515
        [% SET url_biblio_params = url_biblio_params _ "&closed_stack_request=1" %]
1516
    [% END %]
1493
1517
1494
    <script>
1518
    <script>
1495
        $(document).ready(function () {
1519
        $(document).ready(function () {
(-)a/koha-tmpl/intranet-tmpl/prog/js/holds.js (+175 lines)
Lines 326-331 $(document).ready(function() { Link Here
326
                    "url": '/cgi-bin/koha/svc/holds',
326
                    "url": '/cgi-bin/koha/svc/holds',
327
                    "data": function ( d ) {
327
                    "data": function ( d ) {
328
                        d.borrowernumber = borrowernumber;
328
                        d.borrowernumber = borrowernumber;
329
                        d.closed_stack_request = 0;
329
                    }
330
                    }
330
                },
331
                },
331
            }, table_settings_holds_table );
332
            }, table_settings_holds_table );
Lines 497-500 $(document).ready(function() { Link Here
497
        return toggle_suspend(this, inputs);
498
        return toggle_suspend(this, inputs);
498
    });
499
    });
499
500
501
    // Don't load holds table unless it is clicked on
502
    $("#closed-stack-requests-tab").on( "click", function(){ load_closed_stack_requests_table() } );
503
504
    // If the holds tab is preselected on load, we need to load the table
505
    if ( $("#closed-stack-requests-tab").parent().hasClass('active') ) { load_closed_stack_requests_table() }
506
507
    function load_closed_stack_requests_table() {
508
509
        var holds = new Array();
510
        if ( ! $.fn.DataTable.isDataTable($('#closed-stack-requests-table')) ) {
511
            var title;
512
            const table = $("#closed-stack-requests-table").dataTable($.extend(true, {}, dataTablesDefaults, {
513
                "bAutoWidth": false,
514
                "sDom": "rt",
515
                "columns": [
516
                    {
517
                        "data": { _: "reservedate_formatted", "sort": "reservedate" }
518
                    },
519
                    {
520
                        "mDataProp": function ( oObj ) {
521
                            title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber="
522
                                  + oObj.biblionumber
523
                                  + "'>"
524
                                  + (oObj.title ? oObj.title.escapeHtml() : '');
525
526
                            $.each(oObj.subtitle, function( index, value ) {
527
                                title += " " + value.escapeHtml();
528
                            });
529
530
                            title += " " + oObj.part_number + " " + oObj.part_name;
531
532
                            if ( oObj.enumchron ) {
533
                                title += " (" + oObj.enumchron.escapeHtml() + ")";
534
                            }
535
536
                            title += "</a>";
537
538
                            if ( oObj.author ) {
539
                                title += " " + __("by _AUTHOR_").replace("_AUTHOR_", oObj.author.escapeHtml());
540
                            }
541
542
                            if ( oObj.itemnotes ) {
543
                                var span_class = "";
544
                                if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
545
                                    span_class = "circ-hlt";
546
                                }
547
                                title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
548
                            }
549
550
                            return title;
551
                        }
552
                    },
553
                    {
554
                        "mDataProp": function( oObj ) {
555
                            return oObj.itemcallnumber && oObj.itemcallnumber.escapeHtml() || "";
556
                        }
557
                    },
558
                    {
559
                        "mDataProp": function( oObj ) {
560
                            var data = "";
561
                            if ( oObj.itemtype ) {
562
                                data += oObj.itemtype_description;
563
                            }
564
                            return data;
565
                        }
566
                    },
567
                    {
568
                        "mDataProp": function( oObj ) {
569
                            var data = "";
570
                            if ( oObj.barcode ) {
571
                                data += " <a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
572
                                  + oObj.biblionumber
573
                                  + "&itemnumber="
574
                                  + oObj.itemnumber
575
                                  + "#item"
576
                                  + oObj.itemnumber
577
                                  + "'>"
578
                                  + oObj.barcode.escapeHtml()
579
                                  + "</a>";
580
                            }
581
                            return data;
582
                        }
583
                    },
584
                    { "data": { _: "expirationdate_formatted", "sort": "expirationdate" } },
585
                    {
586
                        "mDataProp": function( oObj ) {
587
                            if ( oObj.priority && parseInt( oObj.priority ) && parseInt( oObj.priority ) > 0 ) {
588
                                return oObj.priority;
589
                            } else {
590
                                return "";
591
                            }
592
                        }
593
                    },
594
                    {
595
                        "bSortable": false,
596
                        "mDataProp": function( oObj ) {
597
                            return "<select name='rank-request'>"
598
                                 +"<option value='n'>" + __("No") + "</option>"
599
                                 +"<option value='del'>" + __("Yes") + "</option>"
600
                                 + "</select>"
601
                                 + "<input type='hidden' name='biblionumber' value='" + oObj.biblionumber + "'>"
602
                                 + "<input type='hidden' name='borrowernumber' value='" + borrowernumber + "'>"
603
                                 + "<input type='hidden' name='reserve_id' value='" + oObj.reserve_id + "'>";
604
                        }
605
                    },
606
                    {
607
                        "mDataProp": function( oObj ) {
608
                            var data = "";
609
610
                            if ( oObj.suspend == 1 ) {
611
                                data += "<p>" + __("Hold is <strong>suspended</strong>");
612
                                if ( oObj.suspend_until ) {
613
                                    data += " " + __("until %s").format(oObj.suspend_until_formatted);
614
                                }
615
                                data += "</p>";
616
                            }
617
618
                            if ( oObj.itemtype_limit ) {
619
                                data += __("Next available %s item").format(oObj.itemtype_limit);
620
                            }
621
622
                            if ( oObj.item_group_id ) {
623
                                data += __("Next available item group <strong>%s</strong> item").format( oObj.item_group_description );
624
                            }
625
626
                            if ( oObj.barcode ) {
627
                                data += "<em>";
628
                                if ( oObj.found == "W" ) {
629
630
                                    if ( oObj.waiting_here ) {
631
                                        data += __("Item is <strong>waiting here</strong>");
632
                                        if (oObj.desk_name) {
633
                                            data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
634
                                        }
635
                                    } else {
636
                                        data += __("Item is <strong>waiting</strong>");
637
                                        data += " " + __("at %s").format(oObj.waiting_at);
638
                                        if (oObj.desk_name) {
639
                                            data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
640
                                        }
641
642
                                    }
643
644
                                } else if ( oObj.transferred ) {
645
                                    data += __("Item is <strong>in transit</strong> from %s since %s").format(oObj.from_branch, oObj.date_sent);
646
                                } else if ( oObj.not_transferred ) {
647
                                    data += __("Item hasn't been transferred yet from %s").format(oObj.not_transferred_by);
648
                                }
649
                                data += "</em>";
650
                            }
651
                            return data;
652
                        }
653
                    }
654
                ],
655
                "bPaginate": false,
656
                "bProcessing": true,
657
                "bServerSide": false,
658
                "ajax": {
659
                    "url": '/cgi-bin/koha/svc/holds',
660
                    "data": function ( d ) {
661
                        d.borrowernumber = borrowernumber;
662
                        d.closed_stack_request = 1;
663
                    }
664
                },
665
            }));
666
667
            if ( $("#closed-stack-requests-table").length ) {
668
                $("#closed-stack-requests-table_processing").position({
669
                    of: $( "#closed-stack-requests-table" ),
670
                    collision: "none"
671
                });
672
            }
673
        }
674
    }
500
});
675
});
(-)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 (+4 lines)
Lines 8-13 Link Here
8
            [% IF ( ReservableItems ) %]
8
            [% IF ( ReservableItems ) %]
9
                <li><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>
9
                <li><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
            [% END %]
10
            [% END %]
11
12
            [% IF biblio.items.filter_by_closed_stack.count > 0 %]
13
                <li><a class="btn btn-link btn-lg" href="/cgi-bin/koha/opac-reserve.pl?biblionumber=[% biblio.biblionumber | html %]&closed_stack_request=1"><i class="fa fa-fw fa-bookmark" aria-hidden="true"></i> Closed stack request</a></li>
14
            [% END %]
11
        [% END %]
15
        [% END %]
12
    [% END %]
16
    [% END %]
13
17
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-reserve.tt (-9 / +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 %][% END %]
19
[% BLOCK cssinclude %][% END %]
16
</head>
20
</head>
Lines 21-33 Link Here
21
<div class="main">
25
<div class="main">
22
    [% WRAPPER breadcrumbs %]
26
    [% WRAPPER breadcrumbs %]
23
        [% WRAPPER breadcrumb_item bc_active= 1 %]
27
        [% WRAPPER breadcrumb_item bc_active= 1 %]
24
            <span>Placing a hold</span>
28
            [% IF closed_stack_request %]
29
                <span>Closed stack request</span>
30
            [% ELSE %]
31
                <span>Placing a hold</span>
32
            [% END %]
25
        [% END %]
33
        [% END %]
26
    [% END #/ WRAPPER breadcrumbs %]
34
    [% END #/ WRAPPER breadcrumbs %]
27
35
28
    <div class="container">
36
    <div class="container">
29
        <div id="holds" class="maincontent">
37
        <div id="holds" class="maincontent">
30
            <h1>Placing a hold</h1>
38
            [% IF closed_stack_request %]
39
                <h1>Closed stack request</h1>
40
            [% ELSE %]
41
                <h1>Placing a hold</h1>
42
            [% END %]
31
            [% IF ( message ) %]
43
            [% IF ( message ) %]
32
                <div id="holdmessages" class="alert">
44
                <div id="holdmessages" class="alert">
33
                    <p>Sorry, you cannot place holds.</p>
45
                    <p>Sorry, you cannot place holds.</p>
Lines 150-156 Link Here
150
162
151
            [% UNLESS ( message ) %]
163
            [% UNLESS ( message ) %]
152
                [% UNLESS ( none_available ) %]
164
                [% UNLESS ( none_available ) %]
153
                    <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
165
                    [% IF closed_stack_request %]
166
                        <h2>Confirm closed stack request for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
167
                    [% ELSE %]
168
                        <h2>Confirm holds for:[% INCLUDE 'patron-title.inc' patron = logged_in_user %] ([% logged_in_user.cardnumber | html %])</h2>
169
                    [% END %]
154
                [% END # / UNLESS none_available %]
170
                [% END # / UNLESS none_available %]
155
171
156
                [% IF ( new_reserves_allowed ) %]
172
                [% IF ( new_reserves_allowed ) %]
Lines 163-168 Link Here
163
                [% INCLUDE 'csrf-token.inc' %]
179
                [% INCLUDE 'csrf-token.inc' %]
164
                <legend class="sr-only">Hold requests</legend>
180
                <legend class="sr-only">Hold requests</legend>
165
                <input type="hidden" name="op" value="cud-place_reserve" />
181
                <input type="hidden" name="op" value="cud-place_reserve" />
182
                [% IF closed_stack_request %]
183
                    <input type="hidden" name="closed_stack_request" value="1"/>
184
                [% END %]
166
                <!-- These values are set dynamically by js -->
185
                <!-- These values are set dynamically by js -->
167
                    <input type="hidden" name="biblionumbers" id="biblionumbers"/>
186
                    <input type="hidden" name="biblionumbers" id="biblionumbers"/>
168
                    <input type="hidden" name="selecteditems" id="selections"/>
187
                    <input type="hidden" name="selecteditems" id="selections"/>
Lines 173-183 Link Here
173
                                [% IF bibitemloo.forced_hold_level %]
192
                                [% IF bibitemloo.forced_hold_level %]
174
                                    <div class="alert alert-info forced_hold_level">
193
                                    <div class="alert alert-info forced_hold_level">
175
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
194
                                        [% IF bibitemloo.forced_hold_level == 'item' %]
176
                                            <span>You already have at least one item level hold on this title.
195
                                            <span>Hold policy requires holds to be item level.</span>
177
                                            All further holds must be item level.</span>
178
                                        [% ELSE %]
196
                                        [% ELSE %]
179
                                            <span>You already have at least one record level hold on this title.
197
                                            <span>Hold policy requires holds to be record level.</span>
180
                                            All further holds must be record level.</span>
181
                                        [% END %]
198
                                        [% END %]
182
                                    </div>
199
                                    </div>
183
                                [% END %]
200
                                [% END %]
Lines 264-270 Link Here
264
                                                </li>
281
                                                </li>
265
                                            [% END %]
282
                                            [% END %]
266
283
267
                                            [% UNLESS ( singleBranchMode ) %]
284
                                            [% UNLESS ( singleBranchMode || closed_stack_request ) %]
268
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
285
                                                [% IF ( bibitemloo.holdable && Koha.Preference('OPACAllowUserToChooseBranch')) %]
269
                                                    <li class="branch">
286
                                                    <li class="branch">
270
                                                        <label for="branch_[% bibitemloo.biblionumber | html %]">Pick up location:</label>
287
                                                        <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 299-304 Link Here
299
                                    <span>Holds ([% RESERVES.count | html %])</span>
299
                                    <span>Holds ([% RESERVES.count | html %])</span>
300
                                [% END %]
300
                                [% END %]
301
                            [% END %]
301
                            [% END %]
302
                            [% IF ( closed_stack_requests.count ) %]
303
                                [% WRAPPER tab_item tabname="opac-user-closed-stack-requests" %]
304
                                    <span>Closed stack requests ([% closed_stack_requests.count | html %])</span>
305
                                [% END %]
306
                            [% END %]
302
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
307
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
303
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
308
                                [% WRAPPER tab_item tabname= "opac-user-recalls" %]
304
                                    <span>Recalls ([% RECALLS.count | html %])</span>
309
                                    <span>Recalls ([% RECALLS.count | html %])</span>
Lines 844-849 Link Here
844
                                [% END # /tab_panel#opac-user-holds %]
849
                                [% END # /tab_panel#opac-user-holds %]
845
                            [% END # / #RESERVES.count %]
850
                            [% END # / #RESERVES.count %]
846
851
852
                            [% IF ( closed_stack_requests.count ) %]
853
                                [% WRAPPER tab_panel tabname="opac-user-closed-stack-requests" %]
854
                                    [% PROCESS 'holds-table.inc' HOLDS = closed_stack_requests, SuspendHoldsOpac = SuspendHoldsOpac, showpriority = showpriority, AutoResumeSuspendedHolds = AutoResumeSuspendedHolds, table_id = 'closed-stack-requests-table' %]
855
                                [% END %]
856
                            [% END %]
857
847
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
858
                            [% IF Koha.Preference('UseRecalls') && RECALLS.count %]
848
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
859
                                [% WRAPPER tab_panel tabname="opac-user-recalls" %]
849
                                    <table id="recalls-table" class="table table-bordered table-striped">
860
                                    <table id="recalls-table" class="table table-bordered table-striped">
Lines 1107-1112 Link Here
1107
            [% IF ( opac_user_holds ) %]
1118
            [% IF ( opac_user_holds ) %]
1108
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1119
                $("#opac-user-views a[href='#opac-user-holds_panel']").tab("show");
1109
            [% END %]
1120
            [% END %]
1121
            [% IF ( opac_user_closed_stack_requests ) %]
1122
                $("#opac-user-views a[href='#opac-user-closed-stack-requests_panel']").tab("show");
1123
            [% END %]
1110
            [% IF ( opac_user_article_requests ) %]
1124
            [% IF ( opac_user_article_requests ) %]
1111
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1125
                $("#opac-user-views a[href='#opac-user-article-requests_panel']").tab("show");
1112
            [% END %]
1126
            [% END %]
Lines 1218-1224 Link Here
1218
                );
1232
                );
1219
            });
1233
            });
1220
1234
1221
            var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table");
1235
            var dTables = $("#checkoutst,#holdst,#closed-stack-requests-table,#overduest,#opac-user-relative-issues-table");
1222
            dTables.each(function(){
1236
            dTables.each(function(){
1223
                var thIndex = $(this).find("th.psort").index();
1237
                var thIndex = $(this).find("th.psort").index();
1224
                $(this).on("init.dt", function() {
1238
                $(this).on("init.dt", function() {
(-)a/mainpage.pl (+5 lines)
Lines 138-143 if ( C4::Context->preference('CurbsidePickup') ) { Link Here
138
    );
138
    );
139
}
139
}
140
140
141
my $pending_closed_stack_requests =
142
    Koha::Holds->search( { branchcode => C4::Context->userenv->{branch} } )->filter_by_closed_stack_requests()
143
    ->search( { closed_stack_request_slip_printed => 0 } );
144
141
$template->param(
145
$template->param(
142
    pendingcomments                => $pendingcomments,
146
    pendingcomments                => $pendingcomments,
143
    pendingtags                    => $pendingtags,
147
    pendingtags                    => $pendingtags,
Lines 145-150 $template->param( Link Here
145
    pending_discharge_requests     => $pending_discharge_requests,
149
    pending_discharge_requests     => $pending_discharge_requests,
146
    pending_article_requests       => $pending_article_requests,
150
    pending_article_requests       => $pending_article_requests,
147
    pending_problem_reports        => $pending_problem_reports,
151
    pending_problem_reports        => $pending_problem_reports,
152
    pending_closed_stack_requests  => $pending_closed_stack_requests,
148
);
153
);
149
154
150
output_html_with_http_headers $query, $cookie, $template->output;
155
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/members/moremember.pl (-2 / +2 lines)
Lines 209-215 if ( $patron->is_expired || $patron->is_going_to_expire ) { Link Here
209
my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } ); # FIXME must be Koha::Patron->holds
209
my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } ); # FIXME must be Koha::Patron->holds
210
my $waiting_holds = $holds->waiting;
210
my $waiting_holds = $holds->waiting;
211
$template->param(
211
$template->param(
212
    holds_count  => $holds->count(),
213
    WaitingHolds => $waiting_holds,
212
    WaitingHolds => $waiting_holds,
214
);
213
);
215
214
Lines 290-296 my $patron_lists_count = $patron->get_lists_with_patron->count(); Link Here
290
$template->param(
289
$template->param(
291
    patron          => $patron,
290
    patron          => $patron,
292
    issuecount      => $patron->checkouts->count,
291
    issuecount      => $patron->checkouts->count,
293
    holds_count     => $patron->holds->count,
292
    holds_count     => $patron->holds->filter_out_closed_stack_requests()->count,
293
    closed_stack_requests_count => $patron->holds->filter_by_closed_stack_requests()->count,
294
    fines           => $patron->account->balance,
294
    fines           => $patron->account->balance,
295
    translated_language => $translated_language,
295
    translated_language => $translated_language,
296
    detailview      => 1,
296
    detailview      => 1,
(-)a/opac/opac-reserve.pl (-8 / +23 lines)
Lines 88-93 if (! $biblionumbers) { Link Here
88
    $biblionumbers = $query->param('biblionumber');
88
    $biblionumbers = $query->param('biblionumber');
89
}
89
}
90
90
91
my $closed_stack_request = $query->param('closed_stack_request');
92
$template->param(closed_stack_request => $closed_stack_request);
93
91
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
94
if ( !$biblionumbers && $op ne 'cud-place_reserve' ) {
92
    $template->param( message => 1, no_biblionumber => 1 );
95
    $template->param( message => 1, no_biblionumber => 1 );
93
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
96
    output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
Lines 246-251 if ( $op eq 'cud-place_reserve' ) { Link Here
246
            }
249
            }
247
        }
250
        }
248
251
252
        if ($closed_stack_request && $item) {
253
            $branch = $item->holdingbranch;
254
        }
255
249
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
256
        # if we have an item, we are placing the hold on the item's bib, in case of analytics
250
        if ( $item ) {
257
        if ( $item ) {
251
            $biblioNum = $item->biblionumber;
258
            $biblioNum = $item->biblionumber;
Lines 332-338 if ( $op eq 'cud-place_reserve' ) { Link Here
332
        }
339
        }
333
    }
340
    }
334
341
335
    print $query->redirect("/cgi-bin/koha/opac-user.pl?" . ( @failed_holds ? "failed_holds=" . join('|',@failed_holds) : q|| ) . "&opac-user-holds=1");
342
    my $param = $closed_stack_request ? 'opac-user-closed-stack-requests' : 'opac-user-holds';
343
    print $query->redirect("/cgi-bin/koha/opac-user.pl?" . ( @failed_holds ? "failed_holds=" . join('|',@failed_holds) : q|| ) . "&$param=1");
336
    exit;
344
    exit;
337
}
345
}
338
346
Lines 445-450 foreach my $biblioNum (@biblionumbers) { Link Here
445
    # it's complicated logic to analyse.
453
    # it's complicated logic to analyse.
446
    # (before this loop was inside that sub loop so it was O(n^2) )
454
    # (before this loop was inside that sub loop so it was O(n^2) )
447
    foreach my $item (@{$biblioData->{items}}) {
455
    foreach my $item (@{$biblioData->{items}}) {
456
        next if ($closed_stack_request xor $item->is_available_for_closed_stack_request);
448
457
449
        my $item_info = $item->unblessed;
458
        my $item_info = $item->unblessed;
450
        $item_info->{holding_branch} = $item->holding_branch;
459
        $item_info->{holding_branch} = $item->holding_branch;
Lines 573-585 foreach my $biblioNum (@biblionumbers) { Link Here
573
    # patron placed a record level hold, all the holds the patron places must
582
    # patron placed a record level hold, all the holds the patron places must
574
    # be record level. If the patron placed an item level hold, all holds
583
    # be record level. If the patron placed an item level hold, all holds
575
    # the patron places must be item level
584
    # the patron places must be item level
576
    my $forced_hold_level = Koha::Holds->search(
585
    # Unless the biblio itself forces a specific hold level which
577
        {
586
    # supersedes the above rules
578
            borrowernumber => $borrowernumber,
587
    my $forced_hold_level = $biblio->forced_hold_level;
579
            biblionumber   => $biblioNum,
588
    unless ($forced_hold_level) {
580
            found          => undef,
589
        $forced_hold_level = Koha::Holds->search(
581
        }
590
            {
582
    )->forced_hold_level();
591
                borrowernumber => $borrowernumber,
592
                biblionumber   => $biblioNum,
593
                found          => undef,
594
            }
595
        )->forced_hold_level();
596
    }
597
583
    if ($forced_hold_level) {
598
    if ($forced_hold_level) {
584
        $biblioLoopIter{force_hold}   = 1 if $forced_hold_level eq 'item';
599
        $biblioLoopIter{force_hold}   = 1 if $forced_hold_level eq 'item';
585
        $biblioLoopIter{force_hold}   = 0 if $forced_hold_level eq 'item_group';
600
        $biblioLoopIter{force_hold}   = 0 if $forced_hold_level eq 'item_group';
(-)a/opac/opac-user.pl (-1 / +5 lines)
Lines 333-342 if ($show_barcode) { Link Here
333
$template->param( show_barcode => 1 ) if $show_barcode;
333
$template->param( show_barcode => 1 ) if $show_barcode;
334
334
335
# now the reserved items....
335
# now the reserved items....
336
my $reserves = $patron->holds->filter_out_has_cancellation_requests;
336
my $reserves = $patron->holds->filter_out_has_cancellation_requests->filter_out_closed_stack_requests;
337
my $closed_stack_requests = $patron->holds->filter_out_has_cancellation_requests->filter_by_closed_stack_requests;
337
338
338
$template->param(
339
$template->param(
339
    RESERVES       => $reserves,
340
    RESERVES       => $reserves,
341
    closed_stack_requests => $closed_stack_requests,
340
    showpriority   => $show_priority,
342
    showpriority   => $show_priority,
341
);
343
);
342
344
Lines 418-423 $template->param( Link Here
418
    failed_holds               => scalar $query->param('failed_holds'),
420
    failed_holds               => scalar $query->param('failed_holds'),
419
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
421
    opac_user_holds            => scalar $query->param('opac-user-holds')            || 0,
420
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
422
    opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0,
423
424
    opac_user_closed_stack_requests => scalar $query->param('opac-user-closed-stack-requests') || 0,
421
);
425
);
422
426
423
# if not an empty string this indicates to return
427
# 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 (-2 / +17 lines)
Lines 66-71 my ( $template, $borrowernumber, $cookie, $flags ) = get_template_and_user( Link Here
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 353-358 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
353
            # patron placed a record level hold, all the holds the patron places must
354
            # patron placed a record level hold, all the holds the patron places must
354
            # be record level. If the patron placed an item level hold, all holds
355
            # be record level. If the patron placed an item level hold, all holds
355
            # the patron places must be item level
356
            # the patron places must be item level
357
            # Unless the biblio itself forces a specific hold level which
358
            # supersedes the above rules
356
            my $holds = Koha::Holds->search(
359
            my $holds = Koha::Holds->search(
357
                {
360
                {
358
                    borrowernumber => $patron->borrowernumber,
361
                    borrowernumber => $patron->borrowernumber,
Lines 360-366 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
360
                    found          => undef,
363
                    found          => undef,
361
                }
364
                }
362
            );
365
            );
363
            $template->param( force_hold_level => $holds->forced_hold_level() );
366
367
            my $forced_hold_level = $biblio->forced_hold_level // $holds->forced_hold_level();
368
            $template->param( force_hold_level => $forced_hold_level );
364
369
365
            # For a librarian to be able to place multiple record holds for a patron for a record,
370
            # For a librarian to be able to place multiple record holds for a patron for a record,
366
            # we must find out what the maximum number of holds they can place for the patron is
371
            # we must find out what the maximum number of holds they can place for the patron is
Lines 400-405 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
400
            # (before this loop was inside that sub loop so it was O(n^2) )
405
            # (before this loop was inside that sub loop so it was O(n^2) )
401
406
402
            for my $item_object ( @items ) {
407
            for my $item_object ( @items ) {
408
                next if ($closed_stack_request xor $item_object->is_available_for_closed_stack_request);
409
403
                my $do_check;
410
                my $do_check;
404
                my $item = $item_object->unblessed;
411
                my $item = $item_object->unblessed;
405
                $item->{object} = $item_object;
412
                $item->{object} = $item_object;
Lines 610-616 if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) Link Here
610
                    )->unblessed
617
                    )->unblessed
611
                }
618
                }
612
            };
619
            };
613
            my @reserves = Koha::Holds->search( { biblionumber => $biblionumber }, { order_by => 'priority' } )->as_list;
620
            my $holds_rs = Koha::Holds->search( { 'me.biblionumber' => $biblionumber }, { order_by => 'priority' } );
621
            if ($closed_stack_request) {
622
                $holds_rs = $holds_rs->filter_by_closed_stack_requests();
623
            } else {
624
                $holds_rs = $holds_rs->filter_out_closed_stack_requests();
625
            }
626
            my @reserves = $holds_rs->as_list;
614
            foreach my $res (
627
            foreach my $res (
615
                sort {
628
                sort {
616
                    my $a_found = $a->found() || '';
629
                    my $a_found = $a->found() || '';
Lines 730-735 $template->param(borrowernumber => $borrowernumber_hold); Link Here
730
743
731
$template->param( failed_holds => \@failed_holds );
744
$template->param( failed_holds => \@failed_holds );
732
745
746
$template->param(closed_stack_request => $closed_stack_request);
747
733
# printout the page
748
# printout the page
734
output_html_with_http_headers $input, $cookie, $template->output;
749
output_html_with_http_headers $input, $cookie, $template->output;
735
750
(-)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 65-70 my $holds_rs = Koha::Holds->search( Link Here
65
    }
67
    }
66
);
68
);
67
69
70
if (defined $closed_stack_request) {
71
    if ($closed_stack_request) {
72
        $holds_rs = $holds_rs->filter_by_closed_stack_requests();
73
    } else {
74
        $holds_rs = $holds_rs->filter_out_closed_stack_requests();
75
    }
76
}
77
68
my @holds;
78
my @holds;
69
while ( my $h = $holds_rs->next() ) {
79
while ( my $h = $holds_rs->next() ) {
70
    my $item = $h->item();
80
    my $item = $h->item();
71
- 

Return to bug 38666