From 22960279ed6f5bfb3c56f0501abe3e0b8a4f202a 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 the patch: a. apply patch, b. run updatedatabase.pl and `update_dbix_class_files.pl --force`, c. run `yarn run build`, d. and finally 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 | 73 +++-- Koha/Biblio.pm | 33 ++ Koha/CirculationRules.pm | 2 + Koha/Hold.pm | 6 +- Koha/Holds.pm | 78 ++++- Koha/Item.pm | 35 +- Koha/Items.pm | 27 +- Koha/Template/Plugin/Biblio.pm | 2 +- Koha/Template/Plugin/GD/Barcode.pm | 86 +++++ admin/columns_settings.yml | 20 ++ api/v1/swagger/definitions/item.yaml | 4 + catalogue/detail.pl | 5 +- catalogue/updateitem.pl | 4 + circ/circulation.pl | 5 +- circ/closed-stack-requests.pl | 120 +++++++ .../atomicupdate/closed-stack-requests.pl | 47 +++ installer/data/mysql/kohastructure.sql | 4 + .../prog/en/includes/biblio-view-menu.inc | 7 +- .../prog/en/includes/cat-toolbar.inc | 6 + .../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 | 28 ++ .../prog/en/modules/circ/circulation-home.tt | 3 + .../en/modules/circ/closed-stack-requests.tt | 300 +++++++++++++++++ .../prog/en/modules/intranet-main.tt | 9 +- .../prog/en/modules/reserve/request.tt | 301 ++++++++++-------- koha-tmpl/intranet-tmpl/prog/js/holds.js | 265 +++++++++++++++ .../bootstrap/en/includes/holds-table.inc | 3 +- .../en/includes/opac-detail-sidebar.inc | 8 + .../bootstrap/en/modules/opac-reserve.tt | 33 +- .../bootstrap/en/modules/opac-user.tt | 16 +- mainpage.pl | 5 + members/moremember.pl | 32 +- opac/opac-reserve.pl | 31 +- opac/opac-user.pl | 10 +- reserve/placerequest.pl | 6 +- reserve/request.pl | 24 +- svc/holds | 10 + 39 files changed, 1482 insertions(+), 228 deletions(-) create mode 100644 Koha/Template/Plugin/GD/Barcode.pm create mode 100755 circ/closed-stack-requests.pl create mode 100755 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 432ae461dc..a2a7cbba88 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 @@ -862,14 +862,16 @@ 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 @@ -1367,12 +1369,16 @@ 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 + if $item->itemlost + || $item->withdrawn || ( $item->damaged && !C4::Context->preference('AllowHoldsOnDamagedItems') ); if ($pickup_branchcode) { @@ -1388,29 +1394,34 @@ sub IsAvailableForItemLevelRequest { || $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 e3971b1cb5..d67bf5005e 100644 --- a/Koha/Biblio.pm +++ b/Koha/Biblio.pm @@ -2349,6 +2349,39 @@ 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 7bae491af5..1e6fca5f6e 100644 --- a/Koha/CirculationRules.pm +++ b/Koha/CirculationRules.pm @@ -567,6 +567,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 b3bf4a02b7..da7f00e2fe 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -435,8 +435,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 8336be0cd4..915ec808cb 100644 --- a/Koha/Holds.pm +++ b/Koha/Holds.pm @@ -132,6 +132,7 @@ Items that are not: widthdrawn not for loan not on loan + in closed stack =cut @@ -164,10 +165,11 @@ sub get_items_that_can_fill { return Koha::Items->search( { - -or => \@bibs_or_items, - itemnumber => { -not_in => [ @branchtransfers, @waiting_holds ] }, - onloan => undef, - notforloan => 0, + -or => \@bibs_or_items, + itemnumber => { -not_in => [ @branchtransfers, @waiting_holds ] }, + onloan => undef, + notforloan => 0, + is_closed_stack => 0, } )->filter_by_for_hold(); } @@ -206,6 +208,74 @@ sub filter_out_has_cancellation_requests { ); } +=head3 filter_by_closed_stack_requests + +Apply filter to keep only closed stack requests + + $holds = Koha::Holds->search($filter)->filter_by_closed_stack_requests(); + +Returns a C object + +=cut + +sub filter_by_closed_stack_requests { + my ($self) = @_; + + return $self->search( $self->_closed_stack_request_filter, { join => 'item' } ); +} + +=head3 filter_out_closed_stack_requests + +Apply filter to exclude closed stack requests + + $holds = Koha::Holds->search($filter)->filter_out_closed_stack_requests(); + +Returns a C object + +=cut + +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 e950118336..f2ba3f6891 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -1397,8 +1397,8 @@ sub columns_to_str { $subfield ? $subfield->{authorised_value} ? C4::Biblio::GetAuthorisedValueDesc( - $itemtagfield, - $subfield->{tagsubfield}, $value, '', $tagslib + $itemtagfield, + $subfield->{tagsubfield}, $value, '', $tagslib ) : $value : $value; @@ -2536,6 +2536,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 e353cf5b17..6665480629 100644 --- a/Koha/Items.pm +++ b/Koha/Items.pm @@ -108,6 +108,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") ) { @@ -115,6 +122,9 @@ sub filter_by_for_hold { { %$params, itype => { -not_in => \@hold_not_allowed_itypes }, + }, + { + join => 'reserves', } ); } else { @@ -124,7 +134,7 @@ sub filter_by_for_hold { 'biblioitem.itemtype' => { -not_in => \@hold_not_allowed_itypes }, }, { - join => 'biblioitem', + join => [ 'biblioitem', 'reserves' ], } ); } @@ -225,6 +235,21 @@ 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 4455b25b41..e661384034 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..9090ef6321 --- /dev/null +++ b/Koha/Template/Plugin/GD/Barcode.pm @@ -0,0 +1,86 @@ +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 . + +=head1 NAME + +Koha::Template::Plugin::GD::Barcode + +=head1 SYNOPSIS + + [% USE GD.Barcode %] + + + +=head1 DESCRIPTION + +This Template plugin allows to create barcode image as data URL + +Mostly useful in notices and slips + +=cut + +use Modern::Perl; + +use GD::Barcode; +use MIME::Base64; + +use base qw( Template::Plugin ); + +=head1 METHODS + +=head2 new + +Creates a new instance of the plugin + +=cut + +sub new { + my ($class) = @_; + + my $self = {}; + + return bless $self, $class; +} + +=head2 create_as_data_url + +Create a barcode image as a data URL + + [% GD.Barcode.create_as_data_url(type, barcode, args, plot_args) %] + +C, C and C are passed to C + +C is passed to C + +See L and L + +It returns a data URL suited for use in an img src attribute + +=cut + +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 e2293a9930..89d12d15e1 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -1965,6 +1965,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 d7d13b2f1d..ac86691d90 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -499,7 +499,10 @@ if ( C4::Context->preference('TagsEnabled') and $tag_quantity = C4::Context->pre #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 350f4b14e4..593e536675 100755 --- a/catalogue/updateitem.pl +++ b/catalogue/updateitem.pl @@ -41,6 +41,8 @@ my $damaged = $cgi->param('damaged'); my $exclude_from_local_holds_priority = $cgi->param('exclude_from_local_holds_priority'); my $bookable = $cgi->param('bookable') // q{}; +my $is_closed_stack = $cgi->param('is_closed_stack'); + my $confirm = $cgi->param('confirm'); my $dbh = C4::Context->dbh; @@ -74,6 +76,8 @@ if ( $op eq "cud-set_non_public_note" ) { $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'} ) { diff --git a/circ/circulation.pl b/circ/circulation.pl index e7732bfdc7..866d1707c4 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -595,8 +595,9 @@ 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(), - WaitingHolds => $waiting_holds, + holds_count => $holds->filter_out_closed_stack_requests()->count(), + closed_stack_requests_count => $holds->filter_by_closed_stack_requests()->count(), + WaitingHolds => $waiting_holds, ); if ( C4::Context->preference('UseRecalls') ) { diff --git a/circ/closed-stack-requests.pl b/circ/closed-stack-requests.pl new file mode 100755 index 0000000000..1b52bc195e --- /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 100755 index 0000000000..cb8a3aee10 --- /dev/null +++ b/installer/data/mysql/atomicupdate/closed-stack-requests.pl @@ -0,0 +1,47 @@ +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 9b7dc7fc6e..672197ddcc 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -2825,6 +2825,7 @@ CREATE TABLE `deleteditems` ( `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)', @@ -4146,6 +4147,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)', @@ -5090,6 +5092,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`), @@ -5660,6 +5663,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 7d304bceaf..926a7a23d1 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 @@ -50,10 +50,13 @@ [% END %] [%- IF ( CAN_user_reserveforothers ) -%] -
  • - Holds ([% biblio.holds.count | html %]) +
  • + Holds ([% biblio.holds.filter_out_closed_stack_requests.count | html %])
  • [%- END -%] +
  • + Closed stack requests ([% biblio.holds.filter_by_closed_stack_requests.count | html %]) +
  • [%- IF ( EasyAnalyticalRecords ) -%]
  • 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 2b033e40f6..d752dfdbf4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -284,6 +284,12 @@ [% END %] [% END %] + [% IF items.filter_by_closed_stack.count %] + + [% END %] + [% IF ( CAN_user_circulate_manage_bookings && biblio.items.filter_by_bookable.count ) %]
    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 4418f88548..e478a6fa64 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 %] @@ -214,6 +219,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 801f7dad1e..fd7dace5ac 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 e6ca9c6ae9..aff24e874c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/moredetail.tt @@ -355,6 +355,34 @@
  • Withdrawn on:[% ITEM_DAT.withdrawn_on | $KohaDates %]
  • [% 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 487cc5efbf..934f99c0a2 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 @@ -84,6 +84,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..83724c8f1c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/closed-stack-requests.tt @@ -0,0 +1,300 @@ +[% USE raw %] +[% USE Asset %] +[% USE Branches %] +[% USE To %] +[% 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' %] + + + [% 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 91172dac2a..895f98003c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt @@ -182,7 +182,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 ) || self_registered_count %] + [% IF ( CAN_user_tools_moderate_comments && pendingcomments ) || ( CAN_user_tools_moderate_tags && pendingtags ) || ( CAN_user_borrowers_edit_borrowers && pending_borrower_modifications ) || ( CAN_user_suggestions_suggestions_manage && ( pendingsuggestions || all_pendingsuggestions )) || ( CAN_user_borrowers_edit_borrowers && pending_discharge_requests ) || pending_article_requests || ( Koha.Preference('AllowCheckoutNotes') && CAN_user_circulate_manage_checkout_notes && pending_checkout_notes.count ) || ( ( Koha.Preference('OpacCatalogConcerns') || Koha.Preference('CatalogConcerns') ) && pending_biblio_tickets && CAN_user_editcatalogue_edit_catalogue ) || ( Koha.Preference('OPACReportProblem') && CAN_user_problem_reports && pending_problem_reports.count ) || already_ran_jobs || new_curbside_pickups.count || ( holds_with_cancellation_requests && CAN_user_circulate_circulate_remaining_permissions ) || self_registered_count || pending_closed_stack_requests.count %]
    [% IF pending_article_requests %]
    @@ -294,6 +294,13 @@ [% END %]
    [% 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 558b5e0d9f..9b7d9f97cd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -125,7 +125,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 ) %] @@ -222,7 +226,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,172 +625,176 @@

    -
    - - [% 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. - [% ELSE %] - - [% END %] -
    - [% END %] - -
    - [% IF ( patron.borrowernumber ) %] - [% IF ( override_required ) %] - - [% ELSIF ( none_available ) %] - - [% ELSE %] - - [% END %] - [% END %] -
    -
    -
    - -
    - [% biblio_info = biblioloop.0 %] - - [% IF Koha.Preference('EnableItemGroupHolds') && biblio_info.object.item_groups.count %] + + [% UNLESS closed_stack_request %]
    - [% IF force_hold_level == 'item_group' %] - + [% IF force_hold_level == 'item' || force_hold_level == 'item_group' %] + + [% ELSIF force_hold_level == 'record' %] + + (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 %] + +
    + [% 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. - [% PROCESS options_for_libraries libraries => Branches.pickup_locations({ search_params => { biblio => biblionumber, patron => patron }, selected => pickup }) %]
      2. -
      3. - - - - - - - - - - [% 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.
        -
        -
      4. -
    + + [% IF Koha.Preference('AllowHoldItemTypeSelection') %] +
  • + + +
  • + [% END %] + [% UNLESS remaining_holds_for_record == 1 %] +
  • + + +
  • + [% ELSE %] + + [% END %] + [% END %] +
    [% IF ( patron.borrowernumber ) %] [% IF ( override_required ) %] - + [% ELSIF ( none_available ) %] - + [% ELSE %] - + [% 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 %] + [% 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 %] + [% END %] -
    @@ -1158,6 +1174,10 @@ >
  • No items are available to be placed on hold
  • [% END %] + + [% IF itemloo.is_closed_stack %] +
    Closed stack + [% END %] [% END # /FOREACH biblioloo %] @@ -1431,6 +1451,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 %]