Bug 15516: Allow to reserve first available item from a group of titles
It can be useful, for instance, if a library has the same title from different publishers (so 1 title but several biblio records) but the user only wants a copy of the book, regardless of the publisher. Test plan: 0. Run updatedatabase.pl and misc/devel/update_dbix_class_files.pl 1. Go to intranet search, display some results, click on some checkboxes and click on 'Place hold' button at the top of the results. 2. Search for a patron 3. Check the 'Treat as hold group' checkbox 4. Click on 'Place hold' 5. In the next screen you should see all the holds you placed with the additional text '(part of a hold group)' in Details column. 6. Click on the "hold group" link. A modal window should appear with the content of the hold group (a list of holds) 7. Check in an item of one of the reserved biblios and confirm the hold 8. The hold status is changed to Waiting, and all other holds are deleted. The other holds will also be removed from the holds queue. 9. Confirm tests pass - t/db_dependent/Koha/Holds.t - t/db_dependent/Reserves/HoldGroup.t Note: the "hold group" link is displayed in the following pages: - reserve/request.pl - circ/circulation.pl - members/moremember.pl - circ/pendingreserves.pl - circ/view_holdsqueue.pl Note: A hold group is counted as only one hold Sponsored-by: Catalyst IT Signed-off-by: Michal Denar <black23@gmail.com> Signed-off-by: Victor Grousset/tuxayo <victor@tuxayo.net> Signed-off-by: Michal Denar <black23@gmail.com> Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk> Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
8bfe38b3ba
commit
559801d408
|
|
@ -598,7 +598,7 @@ 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->count_holds,
|
||||
WaitingHolds => $waiting_holds,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,16 @@ if ($run_report) {
|
|||
locationslimit => $locationslimit
|
||||
}
|
||||
);
|
||||
for my $item (@$items) {
|
||||
my $related_hold = Koha::Holds->search(
|
||||
{
|
||||
borrowernumber => $item->{borrowernumber}, reservedate => $item->{reservedate},
|
||||
biblionumber => $item->{biblionumber}
|
||||
},
|
||||
{ order_by => 'reservedate' }
|
||||
)->next;
|
||||
$item->{hold_group_id} = $related_hold ? $related_hold->hold_group_id : undef;
|
||||
}
|
||||
|
||||
$template->param(
|
||||
branchlimit => $branchlimit,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<div id="hold-group-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="hold-group-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="hold-group-modal-label">Hold group</h4>
|
||||
</div>
|
||||
<div class="modal-body"> </div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default deny" data-dismiss="modal">Close</button>
|
||||
</div> </div
|
||||
><!-- /.modal-content --> </div
|
||||
><!-- /.modal-dialog --> </div
|
||||
><!-- /.modal -->
|
||||
|
|
@ -266,6 +266,9 @@
|
|||
[%- IF hold.non_priority -%]
|
||||
<br /><i>Non priority hold</i>
|
||||
[%- END -%]
|
||||
[%- IF hold.hold_group_id -%]
|
||||
<div>(part of a <a href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=[% hold.hold_group_id | uri %]" class="hold-group">hold group</a>)</div>
|
||||
[%- END -%]
|
||||
</td>
|
||||
[%- IF ( CAN_user_reserveforothers_modify_holds_priority ) -%]
|
||||
[%- UNLESS hold.found -%]
|
||||
|
|
|
|||
|
|
@ -1078,6 +1078,8 @@
|
|||
[% END %]
|
||||
[% INCLUDE modals/cancel_booking.inc %]
|
||||
|
||||
[% INCLUDE 'hold-group-modal.inc' %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% INCLUDE 'select2.inc' %]
|
||||
|
|
@ -1117,6 +1119,7 @@
|
|||
[% Asset.js("js/cancel_booking_modal.js") | $raw %]
|
||||
[% Asset.js("js/combobox.js") | $raw %]
|
||||
[% INCLUDE 'js-biblio-format.inc' %]
|
||||
[% Asset.js("js/hold-group.js") | $raw %]
|
||||
<script>
|
||||
table_settings_issues_table = [% TablesSettings.GetTableSettings( 'circ', 'circulation', 'issues-table', 'json' ) | $raw %]
|
||||
table_settings_relatives_issues_table = [% TablesSettings.GetTableSettings( 'circ', 'circulation', 'relatives-issues-table', 'json' ) | $raw %]
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@
|
|||
[% ELSE %]
|
||||
[% IF ( biblio.biblioitem.publicationyear ) %]<p>[% biblio.biblioitem.publicationyear | html %]</p>[% END %]
|
||||
[% END %]
|
||||
[% IF ( hold.hold_group_id ) %]
|
||||
<p>(part of a <a class="hold-group" href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=[% hold.hold_group_id | html %]">hold group</a>)</p>
|
||||
[% END %]
|
||||
</td>
|
||||
[% ELSE %]
|
||||
<td>"</td>
|
||||
|
|
@ -289,9 +292,12 @@
|
|||
</div>
|
||||
<!-- /.main.container-fluid -->
|
||||
|
||||
[% INCLUDE 'hold-group-modal.inc' %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'calendar.inc' %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% Asset.js("js/hold-group.js") | $raw %]
|
||||
<script>
|
||||
function get_options(column){
|
||||
let regex = /(<([^>]+)>)/ig; // Remove html tags
|
||||
|
|
|
|||
|
|
@ -243,6 +243,9 @@
|
|||
>
|
||||
<em>or any available</em>
|
||||
[% END %]
|
||||
[% IF itemsloo.hold_group_id %]
|
||||
<div>(part of a <a href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=[% itemsloo.hold_group_id | uri %]" class="hold-group">hold group</a>)</div>
|
||||
[% END %]
|
||||
</td>
|
||||
<td class="hq-patron">
|
||||
<p> [% INCLUDE 'patron-title.inc' invert_name=1 patron=itemsloo.patron hide_patron_infos_if_needed=1 link_to="circulation_reserves" %] </p>
|
||||
|
|
@ -345,8 +348,11 @@
|
|||
</form>
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'hold-group-modal.inc' %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% Asset.js("js/hold-group.js") | $raw %]
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var holdst;
|
||||
|
|
|
|||
|
|
@ -757,6 +757,7 @@
|
|||
[% END %]
|
||||
|
||||
[% INCLUDE modals/cancel_booking.inc %]
|
||||
[% INCLUDE 'hold-group-modal.inc' %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
|
|
@ -796,6 +797,7 @@
|
|||
[% INCLUDE 'str/members-menu.inc' %]
|
||||
[% Asset.js("js/members-menu.js") | $raw %]
|
||||
[% Asset.js("js/recalls.js") | $raw %]
|
||||
[% Asset.js("js/hold-group.js") | $raw %]
|
||||
<script>
|
||||
const LoadCheckoutsTableDelay = 0;
|
||||
const AlwaysLoadCheckoutsTable = [% Koha.Preference('AlwaysLoadCheckoutsTable') | html %];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>Holds group ([% hold_group.id | html %]) › Holds › Circulation › Koha</title>
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
</head>
|
||||
<body id="hold-group">
|
||||
<h1>Holds group ([% hold_group.id | html %])</h1>
|
||||
<div id="main">
|
||||
[% holds = hold_group.holds %]
|
||||
[% IF holds.count %]
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Priority</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH hold IN holds %]
|
||||
[% biblio = hold.biblio %]
|
||||
<tr>
|
||||
<td><a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio.biblionumber | uri %]">[% biblio.title | html %]</a></td>
|
||||
<td>[% hold.priority | html %]</td>
|
||||
<td>[% hold.reservenotes | html %]</td>
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
</table>
|
||||
[% END %]
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
||||
</div>
|
||||
|
|
@ -619,6 +619,14 @@
|
|||
<input name="non_priority" id="non_priority" type="checkbox" />
|
||||
<span class="hint">A non priority hold doesn't prevent a current checkout from renewing</span>
|
||||
</li>
|
||||
|
||||
[% IF ( Koha.Preference('DisplayAddHoldGroups') && ( multi_hold ) ) %]
|
||||
<li id="hold_group_list_item">
|
||||
<label for="hold_group">Treat as hold group: </label>
|
||||
<input name="hold_group" id="hold_group" type="checkbox" />
|
||||
<span class="hint">Place a hold on the next available title from this group</span>
|
||||
</li>
|
||||
[% END %]
|
||||
</ol>
|
||||
</fieldset>
|
||||
<fieldset class="rows any_specific">
|
||||
|
|
@ -632,7 +640,7 @@
|
|||
[% ELSE %]
|
||||
<input type="radio" id="requestany" name="request" checked="checked" value="Any" />
|
||||
[% END %]
|
||||
<label for="requestany" class="inline"> Hold next available item </label>
|
||||
<label for="requestany" class="inline"> Hold next available item: </label>
|
||||
</legend>
|
||||
<input type="hidden" name="alreadyreserved" value="[% alreadyreserved | html %]" />
|
||||
<fieldset class="enable_request_any disable_request_group disable_request_specific">
|
||||
|
|
@ -668,7 +676,7 @@
|
|||
[% END %]
|
||||
[% UNLESS remaining_holds_for_record == 1 %]
|
||||
<li>
|
||||
<label for="holds_to_place_count">Holds to place (count)</label>
|
||||
<label for="holds_to_place_count">Holds to place (count): </label>
|
||||
<input type="text" inputmode="numeric" pattern="[0-9]*" id="holds_to_place_count" name="holds_to_place_count" value="1" />
|
||||
</li>
|
||||
[% ELSE %]
|
||||
|
|
@ -1391,11 +1399,14 @@
|
|||
<form id="hold-actions-form"> [% INCLUDE 'csrf-token.inc' %] </form>
|
||||
</div>
|
||||
|
||||
[% INCLUDE 'hold-group-modal.inc' %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% INCLUDE 'calendar.inc' %]
|
||||
[% INCLUDE 'select2.inc' %]
|
||||
[% Asset.js("js/holds.js") | $raw %]
|
||||
[% Asset.js("js/hold-group.js") | $raw %]
|
||||
|
||||
[% SET url_biblio_params = "biblionumber=" _ biblionumbers.join("&biblionumber=") %]
|
||||
[% IF multi_hold %]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
$(document).ready(function () {
|
||||
$("body").on("click", "a.hold-group", function () {
|
||||
var href = $(this).attr("href");
|
||||
$("#hold-group-modal .modal-body").load(href + " #main");
|
||||
$("#hold-group-modal").modal("show");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
|
@ -232,6 +232,17 @@ $(document).ready(function () {
|
|||
"</span>";
|
||||
}
|
||||
|
||||
if (oObj.hold_group_id) {
|
||||
title += "<br>";
|
||||
var link =
|
||||
'<a class="hold-group" href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=' +
|
||||
oObj.hold_group_id +
|
||||
'">' +
|
||||
__("part of a hold group") +
|
||||
"</a>";
|
||||
title += "<span>(" + link + ")</span>";
|
||||
}
|
||||
|
||||
return title;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ if ( $patron->is_expired || $patron->is_going_to_expire ) {
|
|||
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->count_holds,
|
||||
WaitingHolds => $waiting_holds,
|
||||
);
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ my $patron_lists_count = $patron->get_lists_with_patron->count();
|
|||
$template->param(
|
||||
patron => $patron,
|
||||
issuecount => $patron->checkouts->count,
|
||||
holds_count => $patron->holds->count,
|
||||
holds_count => $patron->holds->count_holds,
|
||||
fines => $patron->account->balance,
|
||||
translated_language => $translated_language,
|
||||
detailview => 1,
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ $template->param( branch => $branch );
|
|||
if ( $op eq 'cud-place_reserve' ) {
|
||||
my $reserve_cnt = 0;
|
||||
if ($maxreserves) {
|
||||
$reserve_cnt = $patron->holds->count;
|
||||
$reserve_cnt = $patron->holds->count_holds;
|
||||
}
|
||||
|
||||
# List is composed of alternating biblio/item/branch
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
#!/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 <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
use CGI qw( -utf8 );
|
||||
|
||||
use C4::Auth qw( get_template_and_user );
|
||||
use C4::Output qw( output_html_with_http_headers );
|
||||
use Koha::HoldGroups;
|
||||
|
||||
my $cgi = CGI->new;
|
||||
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
|
||||
{
|
||||
template_name => "reserve/hold-group.tt",
|
||||
query => $cgi,
|
||||
type => "intranet",
|
||||
flagsrequired => { circulate => 'circulate_remaining_permissions' },
|
||||
}
|
||||
);
|
||||
|
||||
my $reserve_group_id = $cgi->param('hold_group_id');
|
||||
my $hold_group = Koha::HoldGroups->find($reserve_group_id);
|
||||
|
||||
$template->param(
|
||||
hold_group => $hold_group,
|
||||
);
|
||||
|
||||
output_html_with_http_headers $cgi, $cookie, $template->output;
|
||||
|
|
@ -29,27 +29,30 @@ use C4::Auth qw( checkauth );
|
|||
|
||||
use Koha::Items;
|
||||
use Koha::Patrons;
|
||||
use Koha::HoldGroup;
|
||||
|
||||
my $input = CGI->new();
|
||||
|
||||
checkauth( $input, 0, { reserveforothers => 'place_holds' }, 'intranet' );
|
||||
|
||||
my @reqbib = $input->multi_param('reqbib');
|
||||
my @biblionumbers = $input->multi_param('biblionumber');
|
||||
my @holdable_bibs = $input->multi_param('holdable_bibs');
|
||||
my $borrowernumber = $input->param('borrowernumber');
|
||||
my $notes = $input->param('notes');
|
||||
my $branch = $input->param('pickup');
|
||||
my $startdate = $input->param('reserve_date') || '';
|
||||
my @rank = $input->multi_param('rank-request');
|
||||
my $title = $input->param('title');
|
||||
my @checkitems = $input->multi_param('checkitem');
|
||||
my $item_group_id = $input->param('item_group_id');
|
||||
my $expirationdate = $input->param('expiration_date');
|
||||
my $itemtype = $input->param('itemtype') || undef;
|
||||
my $non_priority = $input->param('non_priority');
|
||||
my $op = $input->param('op') || q{};
|
||||
my $multi_holds = $input->param('multi_holds');
|
||||
my @reqbib = $input->multi_param('reqbib');
|
||||
my @biblionumbers = $input->multi_param('biblionumber');
|
||||
my @holdable_bibs = $input->multi_param('holdable_bibs');
|
||||
my $borrowernumber = $input->param('borrowernumber');
|
||||
my $notes = $input->param('notes');
|
||||
my $branch = $input->param('pickup');
|
||||
my $startdate = $input->param('reserve_date') || '';
|
||||
my @rank = $input->multi_param('rank-request');
|
||||
my $title = $input->param('title');
|
||||
my @checkitems = $input->multi_param('checkitem');
|
||||
my $item_group_id = $input->param('item_group_id');
|
||||
my $expirationdate = $input->param('expiration_date');
|
||||
my $itemtype = $input->param('itemtype') || undef;
|
||||
my $non_priority = $input->param('non_priority');
|
||||
my $op = $input->param('op') || q{};
|
||||
my $multi_holds = $input->param('multi_holds');
|
||||
my $hold_group_param = $input->param('hold_group') || undef;
|
||||
my $hold_group;
|
||||
|
||||
my $patron = Koha::Patrons->find($borrowernumber);
|
||||
|
||||
|
|
@ -66,6 +69,10 @@ foreach my $bibnum (@holdable_bibs) {
|
|||
|
||||
if ( $op eq 'cud-placerequest' && $patron ) {
|
||||
my %failed_holds;
|
||||
if ($hold_group_param) {
|
||||
$hold_group = Koha::HoldGroup->new->store;
|
||||
}
|
||||
|
||||
foreach my $biblionumber ( keys %bibinfos ) {
|
||||
|
||||
my $can_override = C4::Context->preference('AllowHoldPolicyOverride');
|
||||
|
|
@ -102,6 +109,7 @@ if ( $op eq 'cud-placerequest' && $patron ) {
|
|||
found => undef,
|
||||
itemtype => $itemtype,
|
||||
non_priority => $non_priority,
|
||||
hold_group_id => $hold_group ? $hold_group->id : undef,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -130,6 +138,7 @@ if ( $op eq 'cud-placerequest' && $patron ) {
|
|||
found => undef,
|
||||
itemtype => $itemtype,
|
||||
non_priority => $non_priority,
|
||||
hold_group_id => $hold_group ? $hold_group->id : undef,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -153,6 +162,7 @@ if ( $op eq 'cud-placerequest' && $patron ) {
|
|||
itemtype => $itemtype,
|
||||
non_priority => $non_priority,
|
||||
item_group_id => $item_group_id,
|
||||
hold_group_id => $hold_group ? $hold_group->id : undef,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ if ( $borrowernumber_hold && !$op ) {
|
|||
# we check the reserves of the user, and if they can reserve a document
|
||||
# FIXME At this time we have a simple count of reservs, but, later, we could improve the infos "title" ...
|
||||
|
||||
my $reserves_count = $patron->holds->count;
|
||||
my $reserves_count = $patron->holds->count_holds;
|
||||
|
||||
my $new_reserves_count = scalar(@biblionumbers);
|
||||
|
||||
|
|
@ -677,6 +677,7 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold )
|
|||
$reserve{branchcode} = $res->branchcode();
|
||||
$reserve{non_priority} = $res->non_priority();
|
||||
$reserve{object} = $res;
|
||||
$reserve{hold_group_id} = $res->hold_group_id;
|
||||
|
||||
if ( $holds_count_per_patron->{ $reserve{'borrowernumber'} } == 1 ) {
|
||||
$reserve{'change_hold_type_allowed'} = 1;
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ while ( my $h = $holds_rs->next() ) {
|
|||
priority => $h->priority(),
|
||||
reservenotes => $h->reservenotes(),
|
||||
itemtype_limit => $itemtype_limit,
|
||||
hold_group_id => $h->hold_group_id,
|
||||
reservedate_formatted => $h->reservedate()
|
||||
? output_pref( { dt => dt_from_string( $h->reservedate() ), dateonly => 1 } )
|
||||
: q{},
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
use Modern::Perl;
|
||||
|
||||
use Test::NoWarnings;
|
||||
use Test::More tests => 13;
|
||||
use Test::More tests => 14;
|
||||
use Test::Warn;
|
||||
|
||||
use C4::Circulation qw( AddIssue );
|
||||
|
|
|
|||
Loading…
Reference in New Issue