From 5e025f0b940bda6696989624f2de986d2bfdf2d3 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Thu, 18 Jan 2024 16:26:22 +0100 Subject: [PATCH] Bug 38666: Closed stack requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A closed stack request is a special kind of hold that can only be placed on a closed stack item. A closed stack item is an item that is not available publicly in the library but can be made available on demand. When a request is made, a librarian will print a slip then will go retrieve the document and place the slip in place of the document. Internally, a closed stack item is an item that is flagged as "closed stack" (new column items.is_closed_stack). The flag can be modified on the biblio "Items" tab. The new column can also be mapped to an item MARC subfield. An item is available for a closed stack request only if: - it is flagged as "closed stack" - there is no holds on it - it is not checked out - it is not in transfer If any of the above conditions is false, it is not available for a closed stack request but it can be available for a regular hold. In particular this means that once a closed stack request has been made on a particular item, this item can now be reserved using regular holds. Closed stack requests behave like regular holds, with the following exceptions: - They can only be placed on a specific item (item-level hold is forced) - They can be placed on "not for loan" items - They bypass the "onshelfholds" circulation rule - They cannot be cancelled from OPAC There is a new page "Closed stack requests" accessible from the circulation menu that lists all pending closed stack requests. From there it is possible to cancel requests or to print a slip (letter code CLOSED_STACK_SLIP in 'reserves' module) Once the slip is printed, closed stack requests move to another tab in the same page. The patch also adds a template plugin 'GD.Barcode' that allows to include a barcode image inside a letter. Useful to be able to scan the printed closed stack request slip. Example usage: [% USE GD.Barcode %] Test plan: 1. Apply patch, run updatedatabase.pl and update_dbix_class_files.pl, and restart koha 2. Create a biblio with two items. 3. On the biblio detail page, click on the "Items" tab 4. For one of the two items, change the "Closed stack" status from No to Yes 5. Notice there is now a "closed stack requests" tab on the left, and a "closed stack request" button in the toolbar 6. Click on "Place hold" and select a patron. Notice there is only one item displayed in the table (the non-"closed stack" item) 7. Go back to the biblio detail view and click on the "Closed stack request" button. Select a patron. Notice there is only one item displayed in the table (the "closed stack" item). 8. Select this item and click on "Place hold". You should be redirected to the "Closed stack requests" tab where you can see the request that you just made 9. Try to place a regular hold again. This time you should see both items in the table. 10. Go to Tools » Notices and slips 11. Create a new notice in the Holds module with code CLOSED_STACK_SLIP. Add content in the "Print" message. 12. Go to Circulation » Closed stack requests 13. You have two tabs: "Pending" and "Slip printed", you should have one request under the pending tab, and zero under the other one. 14. Click on the "Print closed stack request slip" button, in the "pending" table. A printer dialog should open, close it. The "Closed stack requests" should have been automatically refreshed, showing you an empty "pending" tab, while the "Slip printed" tab now contains the previously pending request. 15. Cancel the closed stack request. 16. Login to OPAC, find your biblio record and place a closed stack request. Like from the staff interface, only the closed stack item should be in the items table, and it should not be possible to place a biblio-level request 17. In your account page, verify that the closed stack request appear under the new tab "Closed stack requests" and that there is no "Cancel" button 18. Go back to the staff interface main page. You should see an alert at the bottom: "Pending closed stack requests: 1". Clicking on it should redirect you to the closed stack requests page seen earlier (step 12) 19. Set the closed stack item as "notforloan", either by editing the item or its item type. Verify that you can still do closed stack requests 20. Change circulation rules to forbid on-shelf holds on the closed stack item and verify that you can still do closed stack requests. Sponsored-by: Université de Lyon 3 --- C4/Reserves.pm | 69 ++-- Koha/Biblio.pm | 34 ++ Koha/CirculationRules.pm | 2 + Koha/Hold.pm | 6 +- Koha/Holds.pm | 47 +++ Koha/Item.pm | 31 ++ Koha/Items.pm | 28 +- Koha/Template/Plugin/Biblio.pm | 2 +- Koha/Template/Plugin/GD/Barcode.pm | 44 +++ admin/columns_settings.yml | 20 + api/v1/swagger/definitions/item.yaml | 4 + catalogue/detail.pl | 5 +- catalogue/updateitem.pl | 3 + circ/circulation.pl | 3 +- circ/closed-stack-requests.pl | 120 ++++++ .../atomicupdate/closed-stack-requests.pl | 39 ++ installer/data/mysql/kohastructure.sql | 3 + .../prog/en/includes/biblio-view-menu.inc | 11 +- .../prog/en/includes/cat-toolbar.inc | 4 + .../prog/en/includes/circ-nav.inc | 3 + .../prog/en/includes/patron-detail-tabs.inc | 46 +++ .../prog/en/modules/catalogue/detail.tt | 13 + .../prog/en/modules/catalogue/moredetail.tt | 27 ++ .../prog/en/modules/circ/circulation-home.tt | 3 + .../en/modules/circ/closed-stack-requests.tt | 327 ++++++++++++++++ .../prog/en/modules/intranet-main.tt | 9 +- .../prog/en/modules/reserve/request.tt | 356 ++++++++++-------- koha-tmpl/intranet-tmpl/prog/js/holds.js | 175 +++++++++ .../bootstrap/en/includes/holds-table.inc | 3 +- .../en/includes/opac-detail-sidebar.inc | 4 + .../bootstrap/en/modules/opac-reserve.tt | 35 +- .../bootstrap/en/modules/opac-user.tt | 16 +- mainpage.pl | 5 + members/moremember.pl | 4 +- opac/opac-reserve.pl | 31 +- opac/opac-user.pl | 6 +- reserve/placerequest.pl | 6 +- reserve/request.pl | 19 +- svc/holds | 10 + 39 files changed, 1344 insertions(+), 229 deletions(-) create mode 100644 Koha/Template/Plugin/GD/Barcode.pm create mode 100755 circ/closed-stack-requests.pl create mode 100644 installer/data/mysql/atomicupdate/closed-stack-requests.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 046bfd0235..432e294d46 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -196,11 +196,11 @@ sub AddReserve { $resdate ||= dt_from_string; + my $item = $checkitem ? Koha::Items->find($checkitem) : undef; + # if we have an item selectionned, and the pickup branch is the same as the holdingbranch # of the document, we force the value $priority and $found . - if ( $checkitem and not C4::Context->preference('ReservesNeedReturns') ) { - my $item = Koha::Items->find( $checkitem ); # FIXME Prevent bad calls - + if ( $item and not C4::Context->preference('ReservesNeedReturns') ) { if ( # If item is already checked out, it cannot be set waiting !$item->onloan @@ -833,15 +833,17 @@ sub CheckReserves { my @SkipHoldTrapOnNotForLoanValue = split( '\|', C4::Context->preference('SkipHoldTrapOnNotForLoanValue') ); return if grep { $_ eq $item->notforloan } @SkipHoldTrapOnNotForLoanValue; - my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan; - if ( !$dont_trap ) { - my $item_type = $item->effective_itemtype; - if ( $item_type ) { - return if Koha::ItemTypes->find( $item_type )->notforloan; + unless ($item->is_closed_stack) { + my $dont_trap = C4::Context->preference('TrapHoldsOnOrder') ? $item->notforloan > 0 : $item->notforloan; + if ( !$dont_trap ) { + my $item_type = $item->effective_itemtype; + if ( $item_type ) { + return if Koha::ItemTypes->find( $item_type )->notforloan; + } + } + else { + return; } - } - else { - return; } # Find this item in the reserves @@ -1316,10 +1318,15 @@ sub IsAvailableForItemLevelRequest { unless defined $itemtype; my $notforloan_per_itemtype = Koha::ItemTypes->find($itemtype)->notforloan; + # Closed stack items can be requested even if marked as "not for loan" + unless ($item->is_closed_stack) { + return 0 if + $notforloan_per_itemtype || + $item->notforloan > 0; # item with negative or zero notforloan value is holdable + } + return 0 if - $notforloan_per_itemtype || $item->itemlost || - $item->notforloan > 0 || # item with negative or zero notforloan value is holdable $item->withdrawn || ($item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems')); @@ -1335,29 +1342,33 @@ sub IsAvailableForItemLevelRequest { return 0 unless $branchitemrule->{hold_fulfillment_policy} ne 'holdgroup' || $home_library->validate_hold_sibling( {branchcode => $pickup_branchcode} ); } - my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } ); + unless ($item->is_closed_stack) { + my $on_shelf_holds = Koha::CirculationRules->get_onshelfholds_policy( { item => $item, patron => $patron } ); - if ( $on_shelf_holds == 1 ) { - return 1; - } elsif ( $on_shelf_holds == 2 ) { + if ( $on_shelf_holds == 1 ) { + return 1; + } elsif ( $on_shelf_holds == 2 ) { - # These calculations work at the biblio level, and can be expensive - # we use the in-memory cache to avoid calling once per item when looping items on a biblio + # These calculations work at the biblio level, and can be expensive + # we use the in-memory cache to avoid calling once per item when looping items on a biblio - my $memory_cache = Koha::Cache::Memory::Lite->get_instance(); - my $cache_key = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber; + my $memory_cache = Koha::Cache::Memory::Lite->get_instance(); + my $cache_key = sprintf "ItemsAnyAvailableAndNotRestricted:%s:%s", $patron->id, $item->biblionumber; - my $any_available = $memory_cache->get_from_cache($cache_key); - return $any_available ? 0 : 1 if defined($any_available); + my $any_available = $memory_cache->get_from_cache($cache_key); + return $any_available ? 0 : 1 if defined($any_available); - $any_available = - ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } ); - $memory_cache->set_in_cache( $cache_key, $any_available ); - return $any_available ? 0 : 1; + $any_available = + ItemsAnyAvailableAndNotRestricted( { biblionumber => $item->biblionumber, patron => $patron } ); + $memory_cache->set_in_cache( $cache_key, $any_available ); + return $any_available ? 0 : 1; - } else { # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved) - return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count; + } else { # on_shelf_holds == 0 "If any unavailable" (the description is rather cryptic and could still be improved) + return $item->notforloan < 0 || $item->onloan || $item->holds->filter_by_found->count; + } } + + return 1; } =head2 ItemsAnyAvailableAndNotRestricted diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm index 927d7b898e..22a3bb3e36 100644 --- a/Koha/Biblio.pm +++ b/Koha/Biblio.pm @@ -2295,6 +2295,40 @@ sub merge_with { return \%results; } +=head3 forced_hold_level + +Returns forced hold level for a biblio + +Returns 'item' if item-level hold should be forced, or undef + +=cut + +sub forced_hold_level { + my ($self) = @_; + + my $forced_hold_level; + + # If there is at least one closed stack item that is not checked out and + # not reserved, force item-level hold + my $available_closed_stack_items = $self->items->search( + { + is_closed_stack => 1, + onloan => undef, + 'reserves.reserve_id' => undef, + }, + { + join => 'reserves', + }, + ); + + if ($available_closed_stack_items->count > 0) { + $forced_hold_level = 'item'; + } + + return $forced_hold_level; +} + + =head2 Internal methods =head3 type diff --git a/Koha/CirculationRules.pm b/Koha/CirculationRules.pm index d558c1e781..1c76eb0636 100644 --- a/Koha/CirculationRules.pm +++ b/Koha/CirculationRules.pm @@ -563,6 +563,8 @@ sub get_opacitemholds_policy { return unless $item or $patron; + return 'F' if $item->is_closed_stack; + my $rule = Koha::CirculationRules->get_effective_rule( { categorycode => $patron->categorycode, diff --git a/Koha/Hold.pm b/Koha/Hold.pm index f5f8ada2b4..a5d8e3195f 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -456,8 +456,10 @@ This is used from the OPAC. sub is_cancelable_from_opac { my ($self) = @_; - return 1 unless $self->is_found(); - return 0; # if ->is_in_transit or if ->is_waiting or ->is_in_processing + return 0 if $self->is_found(); + return 0 if $self->item && $self->item->is_closed_stack; + + return 1; } =head3 cancellation_requestable_from_opac diff --git a/Koha/Holds.pm b/Koha/Holds.pm index d4e0b5cdc6..99e2fef711 100644 --- a/Koha/Holds.pm +++ b/Koha/Holds.pm @@ -133,6 +133,7 @@ Items that are not: widthdrawn not for loan not on loan + in closed stack =cut @@ -167,6 +168,7 @@ sub get_items_that_can_fill { itemnumber => { -not_in => [ @branchtransfers, @waiting_holds ] }, onloan => undef, notforloan => 0, + is_closed_stack => 0, } )->filter_by_for_hold(); } @@ -201,6 +203,51 @@ sub filter_out_has_cancellation_requests { { join => 'cancellation_requests' } ); } +sub filter_by_closed_stack_requests { + my ($self) = @_; + + return $self->search($self->_closed_stack_request_filter, { join => 'item' }); +} + +sub filter_out_closed_stack_requests { + my ($self) = @_; + + return $self->search({ -not_bool => $self->_closed_stack_request_filter }, { join => 'item' }); +} + +sub _closed_stack_request_filter { + # This query returns the top priority reserve's id for each item + my $reserve_id_subselect = q{ + select reserve_id from ( + select reserve_id, itemnumber, row_number() over (partition by itemnumber order by priority) rank + from reserves where itemnumber is not null + ) r + where rank = 1 + }; + + my %where = ( + 'me.reserve_id' => { -in => \$reserve_id_subselect }, + 'me.suspend' => 0, + 'me.found' => undef, + 'me.priority' => { '!=' => 0 }, + 'item.itemlost' => 0, + 'item.withdrawn' => 0, + 'item.onloan' => undef, + 'item.is_closed_stack' => 1, + 'item.itemnumber' => { -not_in => \'SELECT itemnumber FROM branchtransfers WHERE datearrived IS NULL AND datecancelled IS NULL' }, + ); + + if ( !C4::Context->preference('AllowHoldsOnDamagedItems') ) { + $where{'item.damaged'} = 0; + } + + if ( C4::Context->only_my_library() ) { + $where{'me.branchcode'} = C4::Context->userenv->{'branch'}; + } + + return \%where; +} + =head2 Internal methods =head3 _type diff --git a/Koha/Item.pm b/Koha/Item.pm index 07fc6ad5c8..8977ada6bc 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -2524,6 +2524,37 @@ sub analytics_count { return C4::Items::GetAnalyticsCount($self->itemnumber); } +=head3 is_available_for_closed_stack_request + +Returns 1 if item is available for a closed stack request, 0 otherwise + +An item is available for a closed stack request if: + +=over + +=item * it is flagged as "closed stack" + +=item * there is no holds on it + +=item * it is not checked out + +=item * it is not in transfer + +=back + +=cut + +sub is_available_for_closed_stack_request { + my ($self) = @_; + + return 0 unless $self->is_closed_stack; + return 0 if $self->holds->count > 0; + return 0 if $self->checkout; + return 0 if $self->get_transfers->count > 0; + + return 1; +} + =head3 strings_map Returns a map of column name to string representations including the string, diff --git a/Koha/Items.pm b/Koha/Items.pm index 1cb1dce1b5..be1325c92b 100644 --- a/Koha/Items.pm +++ b/Koha/Items.pm @@ -105,6 +105,13 @@ sub filter_by_for_hold { notforloan => { '<=' => 0 }, # items with negative or zero notforloan value are holdable ( C4::Context->preference('AllowHoldsOnDamagedItems')? (): ( damaged => 0 ) ), ( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ), + -or => [ + { is_closed_stack => 0 }, + { + is_closed_stack => 1, + 'reserves.reserve_id' => { '!=', undef }, + }, + ], }; if ( C4::Context->preference("item-level_itypes") ) { @@ -112,6 +119,9 @@ sub filter_by_for_hold { { %$params, itype => { -not_in => \@hold_not_allowed_itypes }, + }, + { + join => 'reserves', } ); } else { @@ -121,7 +131,7 @@ sub filter_by_for_hold { 'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes }, }, { - join => 'biblioitem', + join => ['biblioitem', 'reserves'], } ); } @@ -222,6 +232,22 @@ sub filter_by_bookable { ); } +=head3 filter_by_closed_stack + + my $filterd_items = $items->filter_by_closed_stack; + +Returns a new resultset, containing only those items that are flagged as +"closed stack". + +=cut + +sub filter_by_closed_stack { + my ($self) = @_; + + return $self->search({ is_closed_stack => 1 }); +} + + =head3 move_to_biblio $items->move_to_biblio($to_biblio); diff --git a/Koha/Template/Plugin/Biblio.pm b/Koha/Template/Plugin/Biblio.pm index b16bb3e138..c1d4a9ce12 100644 --- a/Koha/Template/Plugin/Biblio.pm +++ b/Koha/Template/Plugin/Biblio.pm @@ -32,7 +32,7 @@ sub HoldsCount { warn "HoldsCount is deprecated, you should use biblio.holds.count instead"; - my $holds = Koha::Holds->search( { biblionumber => $biblionumber } ); + my $holds = Koha::Holds->search( { 'me.biblionumber' => $biblionumber } )->filter_out_closed_stack_requests(); return $holds->count(); } diff --git a/Koha/Template/Plugin/GD/Barcode.pm b/Koha/Template/Plugin/GD/Barcode.pm new file mode 100644 index 0000000000..03d46e8c4b --- /dev/null +++ b/Koha/Template/Plugin/GD/Barcode.pm @@ -0,0 +1,44 @@ +package Koha::Template::Plugin::GD::Barcode; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use GD::Barcode; +use MIME::Base64; + +use base qw( Template::Plugin ); + +sub new { + my ($class) = @_; + + my $self = {}; + + return bless $self, $class; +} + +sub create_as_data_url { + my ($self, $type, $barcode, $args, $plot_args) = @_; + + $args //= {}; + $plot_args //= {}; + + my $data = GD::Barcode->new($type, $barcode, $args)->plot(%$plot_args)->png; + + return 'data:image/png;base64,' . encode_base64($data); +} + +1; diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml index b4f487d965..a32fe5d3fb 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -1891,6 +1891,26 @@ modules: - columnname: print_slip + closed-stack-requests: + default_display_length: 20 + default_sort_order: 0 + columns: + - columnname: patron + - columnname: title + - columnname: libraries + - columnname: barcodes + - columnname: call_numbers + - columnname: copy_numbers + - columnname: stocknumber + - columnname: enumeration + - columnname: itemtypes + - columnname: locations + - columnname: collection + - columnname: hold_date + - columnname: reserve_notes + - columnname: pickup_location + - columnname: action + holdsratios: holds-ratios: default_display_length: 20 diff --git a/api/v1/swagger/definitions/item.yaml b/api/v1/swagger/definitions/item.yaml index b254eb2dc3..8fd81dee9c 100644 --- a/api/v1/swagger/definitions/item.yaml +++ b/api/v1/swagger/definitions/item.yaml @@ -207,6 +207,10 @@ properties: - string - "null" description: Itemtype defining the type for this item + is_closed_stack: + type: + - boolean + - "null" effective_item_type_id: type: - string diff --git a/catalogue/detail.pl b/catalogue/detail.pl index 7af8010343..9b239834d7 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -488,7 +488,10 @@ if (C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pref #we only need to pass the number of holds to the template my $holds = $biblio->holds; -$template->param( holdcount => $holds->count ); +$template->param( + holdcount => $holds->filter_out_closed_stack_requests->count, + closed_stack_request_count => $holds->filter_by_closed_stack_requests->count, +); # Check if there are any ILL requests connected to the biblio my $illrequests = diff --git a/catalogue/updateitem.pl b/catalogue/updateitem.pl index cb92efbb16..60d62d27f4 100755 --- a/catalogue/updateitem.pl +++ b/catalogue/updateitem.pl @@ -37,6 +37,7 @@ my $itemlost=$cgi->param('itemlost'); my $itemnotes=$cgi->param('itemnotes'); my $itemnotes_nonpublic=$cgi->param('itemnotes_nonpublic'); my $withdrawn=$cgi->param('withdrawn'); +my $is_closed_stack = $cgi->param('is_closed_stack'); my $damaged=$cgi->param('damaged'); my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority'); my $bookable = $cgi->param('bookable') // q{}; @@ -73,6 +74,8 @@ elsif ( $op eq "cud-set_public_note" ) { # i.e., itemnotes parameter passed from $item->itemlost($itemlost); } elsif ( $op eq "cud-set_withdrawn" && $withdrawn ne $item_data_hashref->{'withdrawn'}) { $item->withdrawn($withdrawn); +} elsif ( $op eq "cud-set_is_closed_stack") { + $item->is_closed_stack($is_closed_stack); } elsif ( $op eq "cud-set_exclude_priority" && $exclude_from_local_holds_priority ne $item_data_hashref->{'exclude_from_local_holds_priority'}) { $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority); $messages = "updated_exclude_from_local_holds_priority=$exclude_from_local_holds_priority&"; diff --git a/circ/circulation.pl b/circ/circulation.pl index 968470e6f1..76d27970c1 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -594,7 +594,8 @@ if ($patron) { my $holds = Koha::Holds->search( { borrowernumber => $borrowernumber } ); # FIXME must be Koha::Patron->holds my $waiting_holds = $holds->waiting; $template->param( - holds_count => $holds->count(), + holds_count => $holds->filter_out_closed_stack_requests()->count(), + closed_stack_requests_count => $holds->filter_by_closed_stack_requests()->count(), WaitingHolds => $waiting_holds, ); diff --git a/circ/closed-stack-requests.pl b/circ/closed-stack-requests.pl new file mode 100755 index 0000000000..f75218e7b8 --- /dev/null +++ b/circ/closed-stack-requests.pl @@ -0,0 +1,120 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI '-utf8'; + +use C4::Output qw( output_html_with_http_headers ); +use C4::Auth qw( get_template_and_user ); +use Koha::Holds; +use Koha::I18N; + +my $cgi = CGI->new; + +my $op = $cgi->param('op') || ''; +if ($op eq 'cud-print_slip') { + my $reserve_id = $cgi->param('reserve_id'); + + my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => 'circ/printslip.tt', + query => $cgi, + type => 'intranet', + flagsrequired => { circulate => 'circulate_remaining_permissions' }, + } + ); + + my $hold = Koha::Holds->find($reserve_id); + + my $letter = C4::Letters::GetPreparedLetter( + module => 'reserves', + letter_code => 'CLOSED_STACK_SLIP', + branchcode => $hold->branchcode, + lang => $hold->patron->lang, + message_transport_type => 'print', + tables => { + reserves => $hold->unblessed, + branches => $hold->branchcode, + borrowers => $hold->borrowernumber, + biblio => $hold->biblionumber, + biblioitems => $hold->biblionumber, + items => $hold->itemnumber, + }, + objects => { + hold => $hold, + } + ); + if ($letter) { + $template->param( + title => __('Closed stack request slip'), + slip => $letter->{content}, + plain => !$letter->{is_html}, + ); + $hold->closed_stack_request_slip_printed(1); + $hold->store(); + } + + output_html_with_http_headers $cgi, $cookie, $template->output; + exit; +} + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => "circ/closed-stack-requests.tt", + query => $cgi, + type => "intranet", + flagsrequired => { circulate => "circulate_remaining_permissions" }, + } +); + +my @messages; +if ( $op eq 'cud-cancel_reserve' ) { + my $reserve_id = $cgi->param('reserve_id'); + my $hold = Koha::Holds->find($reserve_id); + if ($hold) { + my $cancellation_reason = $cgi->param('cancellation-reason'); + $hold->cancel( { cancellation_reason => $cancellation_reason } ); + push @messages, { type => 'message', code => 'hold_cancelled' }; + } +} + +my $holds = Koha::Holds->search()->filter_by_closed_stack_requests(); + +my $branchcode = $cgi->param('branchcode') // C4::Context->userenv->{branch}; +if ($branchcode ne '') { + $holds = $holds->search({ branchcode => $branchcode }); +} +$template->param(branchcode => $branchcode); + +my (@pending_holds, @printed_slip_holds); + +foreach my $hold ($holds->as_list) { + if ($hold->closed_stack_request_slip_printed) { + push @printed_slip_holds, $hold; + } else { + push @pending_holds, $hold; + } +} + +$template->param( + pending_holds => \@pending_holds, + printed_slip_holds => \@printed_slip_holds, + messages => \@messages, +); + +output_html_with_http_headers $cgi, $cookie, $template->output; diff --git a/installer/data/mysql/atomicupdate/closed-stack-requests.pl b/installer/data/mysql/atomicupdate/closed-stack-requests.pl new file mode 100644 index 0000000000..b680381578 --- /dev/null +++ b/installer/data/mysql/atomicupdate/closed-stack-requests.pl @@ -0,0 +1,39 @@ +use Modern::Perl; + +return { + bug_number => "38666", + description => "Closed stack requests", + up => sub { + my ($args) = @_; + my ($dbh, $out) = @$args{qw(dbh out)}; + + unless (column_exists('items', 'is_closed_stack')) { + $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`"); + say $out "Column items.is_closed_stack created"; + } else { + say $out "Column items.is_closed_stack not created (already exists)"; + } + + unless (column_exists('deleteditems', 'is_closed_stack')) { + $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`"); + say $out "Column deleteditems.is_closed_stack created"; + } else { + say $out "Column deleteditems.is_closed_stack not created (already exists)"; + } + + unless (column_exists('reserves', 'closed_stack_request_slip_printed')) { + $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`"); + say $out "Column reserves.closed_stack_request_slip_printed created"; + } else { + say $out "Column reserves.closed_stack_request_slip_printed not created (already exists)"; + } + + unless (column_exists('old_reserves', 'closed_stack_request_slip_printed')) { + $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`"); + say $out "Column old_reserves.closed_stack_request_slip_printed created"; + } else { + say $out "Column old_reserves.closed_stack_request_slip_printed not created (already exists)"; + } + + }, +}; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index d368659571..469e9a186b 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -4064,6 +4064,7 @@ CREATE TABLE `items` ( `materials` mediumtext DEFAULT NULL COMMENT 'materials specified (MARC21 952$3)', `uri` mediumtext DEFAULT NULL COMMENT 'URL for the item (MARC21 952$u)', `itype` varchar(10) DEFAULT NULL COMMENT 'foreign key from the itemtypes table defining the type for this item (MARC21 952$y)', + `is_closed_stack` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'if true, special rules apply for holds on this item', `more_subfields_xml` longtext DEFAULT NULL COMMENT 'additional 952 subfields in XML format', `enumchron` mediumtext DEFAULT NULL COMMENT 'serial enumeration/chronology for the item (MARC21 952$h)', `copynumber` varchar(32) DEFAULT NULL COMMENT 'copy number (MARC21 952$t)', @@ -5044,6 +5045,7 @@ CREATE TABLE `old_reserves` ( `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting', `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level', `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold', + `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)', PRIMARY KEY (`reserve_id`), KEY `old_reserves_borrowernumber` (`borrowernumber`), KEY `old_reserves_biblionumber` (`biblionumber`), @@ -5614,6 +5616,7 @@ CREATE TABLE `reserves` ( `itemtype` varchar(10) DEFAULT NULL COMMENT 'If record level hold, the optional itemtype of the item the patron is requesting', `item_level_hold` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is the hold placed at item level', `non_priority` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Is this a non priority hold', + `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)', PRIMARY KEY (`reserve_id`), KEY `priorityfoundidx` (`priority`,`found`), KEY `borrowernumber` (`borrowernumber`), diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc index 3e5625eff0..3c909fdbc7 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc @@ -61,14 +61,21 @@ [% END %] [%- IF ( CAN_user_reserveforothers ) -%] - [%- IF ( holdsview ) -%] + [%- IF ( holdsview && !closed_stack_request ) -%]
  • [%- ELSE -%]
  • [%- END -%] - Holds ([% biblio.holds.count | html %]) + Holds ([% biblio.holds.filter_out_closed_stack_requests.count | html %])
  • [%- END -%] + [%- IF ( holdsview && closed_stack_request ) -%] +
  • + [%- ELSE -%] +
  • + [%- END -%] + Closed stack requests ([% biblio.holds.filter_by_closed_stack_requests.count | html %]) +
  • [%- IF ( EasyAnalyticalRecords ) -%] [%- IF ( analyze ) -%] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc index 36d924b952..9030fa2b04 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -270,6 +270,10 @@ [% END %] [% END %] + + [% IF items.filter_by_closed_stack.count %] + + [% END %] [% END %] [% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc index 86ca5d18ce..7ba80e8381 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-nav.inc @@ -54,6 +54,9 @@
  • Holds to pull
  • +
  • + Closed stack requests +
  • Holds awaiting pickup
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-detail-tabs.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-detail-tabs.inc index 68f5e54630..4cfed7ff39 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/patron-detail-tabs.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/patron-detail-tabs.inc @@ -23,6 +23,11 @@ [% WRAPPER tab_item tabname= "holds" %] Holds ([% holds_count || 0 | html %]) [% END %] + [% IF closed_stack_requests_count > 0 %] + [% WRAPPER tab_item tabname="closed-stack-requests" %] + Closed stack requests ([% closed_stack_requests_count | html %]) + [% END %] + [% END %] [% WRAPPER tab_item tabname="bookings" %] [% SET bookings_count = patron.bookings.filter_by_active.count %] [% SET expired_bookings_count = patron.bookings.count - bookings_count %] @@ -215,6 +220,47 @@ [% END %] [% END # /tab_panel#holds %] + [% IF closed_stack_requests_count > 0 %] + [% WRAPPER tab_panel tabname="closed-stack-requests" %] +
    +
    + + + + + + + + + + + + + + + + +
    Hold dateTitleCall numberItem typeBarcodeExpirationPriorityDelete?Status
    + +
    + + + [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] + [% IF hold_cancellation.count %] + + + [% END %] +
    +
    +
    + [% END %] + [% END %] + [% WRAPPER tab_panel tabname="bookings" %] [% IF ( bookings_count ) %]
    diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt index b82e221362..8e19963c45 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -198,6 +198,19 @@ [% END %] + [% IF ( closed_stack_request_count ) %] + + Closed stack requests: + + [% IF CAN_user_reserveforothers_place_holds %] + [% closed_stack_request_count | html %] + [% ELSE %] + [% closed_stack_request_count | html %] + [% END %] + + + [% END %] + [% IF illrequests.count %] ILL requests: diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt index 2020f9fb9d..d2b85e9579 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt @@ -315,6 +315,33 @@ [% END %] [% END %] +
  • + Closed stack: + [% IF ( CAN_user_circulate ) %] +
    + [% INCLUDE 'csrf-token.inc' %] + + + + + + +
    + [% ELSE %] + [% IF ITEM_DAT.is_closed_stack %] + Yes + [% ELSE %] + No + [% END %] + [% END %] +
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt index 656dfd4d1f..0ca02770bf 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation-home.tt @@ -87,6 +87,9 @@
  • Holds to pull
  • +
  • + Closed stack requests +
  • Holds awaiting pickup
  • diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt new file mode 100644 index 0000000000..4a06177989 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt @@ -0,0 +1,327 @@ +[% USE raw %] +[% USE Asset %] +[% USE Branches %] +[% USE Koha %] +[% USE KohaDates %] +[% USE TablesSettings %] +[% USE AuthorisedValues %] +[%- USE Branches -%] +[%- USE ItemTypes -%] +[% SET footerjs = 1 %] +[% INCLUDE 'doc-head-open.inc' %] +Closed stack requests › Circulation › Koha +[% INCLUDE 'doc-head-close.inc' %] + + + +[% WRAPPER 'header.inc' %] + [% INCLUDE 'circ-search.inc' %] +[% END %] + +[% WRAPPER 'sub-header.inc' %] + [% WRAPPER breadcrumbs %] + [% WRAPPER breadcrumb_item %] + Circulation + [% END %] + [% WRAPPER breadcrumb_item bc_active= 1 %] + Closed stack requests + [% END %] + [% END #/ WRAPPER breadcrumbs %] +[% END #/ WRAPPER sub-header.inc %] + +
    +
    +
    +
    + + [% FOR m IN messages %] +
    + [% SWITCH m.code %] + [% CASE 'hold_cancelled' %] + The hold has been correctly cancelled. + [% CASE %] + [% m.code | html %] + [% END %] +
    + [% END %] + +

    Closed stack requests

    + +[% BLOCK holds_table %] + [% IF holds %] + + + + + + + + + + + + + + + + + + + + + + [% FOREACH hold IN holds %] + + [% SET patron = hold.patron %] + [% SET item = hold.item %] + [% SET biblio = hold.biblio %] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [% END %] + + + + + + + + + + + + + + + + + + + + + +
    PatronTitleLibraryBarcodeCall numberCopy numberStock numberEnumerationItem typeLocationCollectionHold dateHold notesPickup locationAction
    [% patron.firstname | html %] [% patron.surname | html %] +

    + [% INCLUDE 'biblio-title.inc' biblio=biblio link = 1 %] +

    + [% IF ( biblio.author ) %]

    by [% biblio.author | html %]

    [% END %] + [% IF ( biblio.biblioitem.editionstatement ) %]

    [% biblio.biblioitem.editionstatement | html %]

    [% END %] + [% IF ( Koha.Preference('marcflavour') == 'MARC21' ) %] + [% IF ( biblio.copyrightdate ) %]

    [% biblio.copyrightdate | html %]

    [% END %] + [% ELSE %] + [% IF ( biblio.biblioitem.publicationyear ) %]

    [% biblio.biblioitem.publicationyear | html %]

    [% END %] + [% END %] +
    [% Branches.GetName(item.holdingbranch) | html %][% item.barcode | html %][% item.itemcallnumber | html %][% item.copynumber | html %][% item.stocknumber | html %][% item.enumchron | html %][% ItemTypes.GetDescription(item.itype) | html %][% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.location', authorised_value => item.location ) | html %] + + [% AuthorisedValues.GetDescriptionByKohaField( kohafield => 'items.ccode', authorised_value => item.ccode ) | html %] + [% hold.reservedate | $KohaDates %] in [% Branches.GetName ( hold.branchcode ) | html %] + [% hold.reservenotes | html %][% Branches.GetName ( hold.branchcode ) | html %] + + +
    + [% INCLUDE 'csrf-token.inc' %] + + + + [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] + [% IF hold_cancellation.count %] +
    + + +
    + [% END %] + + [% IF item.holdingbranch != item.homebranch %] + + [% ELSE %] + + [% END %] +
    +
    + [% ELSE %] + No items found. + [% END %] +[% END %] + +[% PROCESS 'html_helpers.inc' %] +[% WRAPPER tabs %] + [% WRAPPER tabs_nav %] + [% WRAPPER tab_item tabname="pending" bt_active=1 %]Pending ([% pending_holds.size || 0 | html %])[% END %] + [% WRAPPER tab_item tabname="printed" %]Slip printed ([% printed_slip_holds.size || 0 | html %])[% END %] + [% END %] + [% WRAPPER tab_panels %] + [% WRAPPER tab_panel tabname="pending" bt_active=1 %] +

    The following holds have not been filled. Please retrieve them and check them in.

    + + [% INCLUDE holds_table id="holdst" holds=pending_holds %] + [% END %] + + [% WRAPPER tab_panel tabname="printed" %] + [% INCLUDE holds_table id="printed_slip_holds" holds=printed_slip_holds %] + [% END %] + [% END %] +[% END %] + + +
    +
    +
    + + [% IF Koha.Preference('CircSidebar') %] + + [% END %] +
    + +
    + +[% MACRO jsinclude BLOCK %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'columns_settings.inc' %] + + +[% END %] + +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt index c32de6ab9b..3bb75120c4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt @@ -180,7 +180,7 @@
    [%# Following statement must be in one line for translatability %] - [% 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 ) %] + [% 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 %]
    [% IF pending_article_requests %]
    @@ -275,6 +275,13 @@ [% holds_with_cancellation_requests | html %]
    [% END %] + + [% IF pending_closed_stack_requests.count %] +
    + Pending closed stack requests: + [% pending_closed_stack_requests.count | html %] +
    + [% END %]
    [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt index 2fcc09479f..9a65b0fff4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -122,7 +122,11 @@ [% INCLUDE 'biblio-title.inc' link =1 %] [% END %] [% WRAPPER breadcrumb_item bc_active= 1 %] - Place a hold + [% IF closed_stack_request %] + Closed stack request + [% ELSE %] + Place a hold + [% END %] [% END %] [% ELSE %] [% IF ( patron ) %] @@ -226,7 +230,11 @@ [% END %] [% UNLESS ( multi_hold ) %] -

    Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]

    + [% IF closed_stack_request %] +

    Closed stack request on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]

    + [% ELSE %] +

    Place a hold on [% INCLUDE 'biblio-title.inc' link = 1 %] [% IF biblio.author %] by [% biblio.author | html %][% END %]

    + [% END %] [% ELSE %]

    [% IF ( patron ) %] @@ -562,6 +570,10 @@ + [% IF closed_stack_request %] + + [% END %] +
    1. Patron: @@ -613,184 +625,189 @@

    -
    - - [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %] - - [% ELSIF force_hold_level == 'record' %] - - - (Required) - [% ELSE %] - - [% END %] - - - -
    - [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %] - - - Hold must be item level - - [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %] - - - Hold must be item group level - - [% ELSE %] -
      - -
    1. - - -
    2. - [% IF Koha.Preference('AllowHoldItemTypeSelection') %] -
    3. - - -
    4. - [% END %] - [% UNLESS remaining_holds_for_record == 1 %] -
    5. - - -
    6. + [% biblio_info = biblioloop.0 %] + + [% UNLESS closed_stack_request %] +
      + + [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %] + + [% ELSIF force_hold_level == 'record' %] + + + (Required) [% ELSE %] - + [% END %] -
    - [% END %] + + + +
    + [% IF force_hold_level == 'item' # Patron has placed a item level hold previously for this record %] + + + Hold must be item level + + [% ELSIF force_hold_level == 'item_group' # Patron has placed an item group level hold previously for this record %] + + + Hold must be item group level + + [% ELSE %] +
      +
    1. + + +
    2. -
      - [% IF ( patron.borrowernumber ) %] - [% IF ( override_required ) %] - - [% ELSIF ( none_available ) %] - - [% ELSE %] - + [% IF Koha.Preference('AllowHoldItemTypeSelection') %] +
    3. + + +
    4. + [% END %] + [% UNLESS remaining_holds_for_record == 1 %] +
    5. + + +
    6. + [% ELSE %] + + [% END %] +
    [% END %] - [% END %] -
    -
    -
    - -
    - [% biblio_info = biblioloop.0 %] - - [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %] -
    - - [% IF force_hold_level == 'item_group' %] - - (Required) - [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %] - - [% ELSE %] - - [% END %] - - -
    - [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %] - - - Hold must be record level - - [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %] - - - Hold must be item level - - [% ELSE %] -
      -
    • - - -
    • -
    • - - - - - - - - - - [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %] - [% IF g.items.count %] - - - - - - [% ELSE %] - - - - - - [% END %] +
      + [% IF ( patron.borrowernumber ) %] + [% IF ( override_required ) %] + + [% ELSIF ( none_available ) %] + + [% ELSE %] + [% END %] -
      -
      HoldItem groupHoldable items
      - - - - - [% FOREACH i IN g.items %] - - [% END %] -
      - - - - -
      No holdable items in this item group.
      -
      -
    • -
    - [% END %] -
    - [% IF ( patron.borrowernumber ) %] - [% IF ( override_required ) %] - - [% ELSIF ( none_available ) %] - + [% END %] +
    +
    +
    + +
    + + + [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %] +
    + + [% IF force_hold_level == 'item_group' %] + + (Required) + [% ELSIF force_hold_level == 'item' || force_hold_level == 'record' %] + [% ELSE %] - + [% END %] + + + +
    + [% IF force_hold_level == 'record' # Patron has placed a record level hold previously for this record %] + + + Hold must be record level + + [% ELSIF force_hold_level == 'item' # Patron has placed an item level hold previously for this record %] + + + Hold must be item level + + [% ELSE %] +
      +
    • + + +
    • +
    • + + + + + + + + + + [% FOREACH g IN biblio_info.object.item_groups.search({}, { order_by => ['display_order'] }) %] + [% IF g.items.count %] + + + + + + [% ELSE %] + + + + + + [% END %] + [% END %] + +
      HoldItem groupHoldable items
      + + + + + [% FOREACH i IN g.items %] + + [% END %] +
      + + + + +
      No holdable items in this item group.
      +
      +
    • +
    [% END %] + +
    + [% IF ( patron.borrowernumber ) %] + [% IF ( override_required ) %] + + [% ELSIF ( none_available ) %] + + [% ELSE %] + + [% END %] + [% END %] +
    +
    - - + [% END # UNLESS closed_stack_request %] + [% END %] -
    @@ -1188,6 +1205,10 @@ [% END %] [% END %] + + [% IF itemloo.is_closed_stack %] +
    Closed stack + [% END %] [% END # /FOREACH biblioloo %] @@ -1490,6 +1511,9 @@ [% IF multi_hold %] [% SET url_biblio_params = url_biblio_params _ "&multi_hold=1" %] [% END %] + [% IF closed_stack_request %] + [% SET url_biblio_params = url_biblio_params _ "&closed_stack_request=1" %] + [% END %]