Bug 40517: Patron holds table: Add 'group selected holds' functionality
This adds the UI elements required to group selected holds from the patron pages as well as 2 new concepts: Visual group ID: The visual group ID is designed to be easy to understand from a UI/UX perspective and not show the user an indefinitely incremented value. When holds are cancelled, filled or moved to a different group, if the old group is left empty or with a single hold, the hold group is deleted. Signed-off-by: Anneli <anneli.osterman@koha-suomi.fi> Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
483578c74d
commit
9df5ffeffe
33
Koha/Hold.pm
33
Koha/Hold.pm
|
|
@ -250,6 +250,27 @@ sub move_hold {
|
|||
return { success => 1, hold => $self };
|
||||
}
|
||||
|
||||
=head3 cleanup_hold_group
|
||||
|
||||
$self->cleanup_hold_group;
|
||||
|
||||
Check if a hold group is left with a single or zero holds. Delete hold_group if so.
|
||||
Accepts an optional $hold_group_id that, if defined, uses that instead of self->hold_group_id
|
||||
|
||||
=cut
|
||||
|
||||
sub cleanup_hold_group {
|
||||
my ( $self, $hold_group_id ) = @_;
|
||||
|
||||
my $hold_group_id_to_check = $hold_group_id // $self->hold_group_id;
|
||||
my $hold_group = Koha::HoldGroups->find($hold_group_id_to_check);
|
||||
return unless $hold_group;
|
||||
|
||||
if ( $hold_group->holds->count <= 1 ) {
|
||||
$hold_group->delete();
|
||||
}
|
||||
}
|
||||
|
||||
=head3 set_transfer
|
||||
|
||||
=cut
|
||||
|
|
@ -744,9 +765,10 @@ sub cancellation_requested {
|
|||
|
||||
my $cancel_hold = $hold->cancel(
|
||||
{
|
||||
[ charge_cancel_fee => 1||0, ]
|
||||
[ cancellation_reason => $cancellation_reason, ]
|
||||
[ skip_holds_queue => 1||0 ]
|
||||
[ charge_cancel_fee => 1||0, ]
|
||||
[ cancellation_reason => $cancellation_reason, ]
|
||||
[ skip_holds_queue => 1||0 ]
|
||||
[ skip_hold_group_cleanup => 1||0 ]
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -870,6 +892,8 @@ sub cancel {
|
|||
}
|
||||
);
|
||||
|
||||
$self->cleanup_hold_group() unless $params->{skip_hold_group_cleanup};
|
||||
|
||||
if ($autofill_next) {
|
||||
my ( undef, $next_hold ) = C4::Reserves::CheckReserves( $self->item );
|
||||
if ($next_hold) {
|
||||
|
|
@ -957,9 +981,10 @@ sub fill {
|
|||
if ( $self->hold_group_id ) {
|
||||
my @holds = $self->hold_group->holds->as_list;
|
||||
foreach my $h (@holds) {
|
||||
$h->cancel unless $h->reserve_id == $self->id;
|
||||
$h->cancel( { skip_hold_group_cleanup => 1 } ) unless $h->reserve_id == $self->id;
|
||||
}
|
||||
}
|
||||
$self->cleanup_hold_group();
|
||||
|
||||
Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
|
||||
{ biblio_ids => [ $old_me->biblionumber ] } )
|
||||
|
|
|
|||
|
|
@ -1912,6 +1912,8 @@ Creates and returns a hold group given a list of hold ids
|
|||
|
||||
If force_grouped is supplied, the hold group will be created even if the holds are already grouped
|
||||
|
||||
Checks if the hold is moving from another group, and if so, removes the old group if empty
|
||||
|
||||
=cut
|
||||
|
||||
sub create_hold_group {
|
||||
|
|
@ -1968,9 +1970,13 @@ sub create_hold_group {
|
|||
{ visual_hold_group_id => $next_available_visual_hold_group_id }
|
||||
);
|
||||
foreach my $hold_id (@$hold_ids) {
|
||||
my $hold = Koha::Holds->find($hold_id);
|
||||
my $hold = Koha::Holds->find($hold_id);
|
||||
my $previous_hold_group_id = $hold->hold_group_id;
|
||||
|
||||
$hold->hold_group_id( $hold_group_rs->hold_group_id )->store;
|
||||
if ( $previous_hold_group_id && $previous_hold_group_id != $hold_group_rs->hold_group_id ) {
|
||||
$hold->cleanup_hold_group($previous_hold_group_id);
|
||||
}
|
||||
}
|
||||
|
||||
return Koha::HoldGroup->_new_from_dbic($hold_group_rs);
|
||||
|
|
|
|||
|
|
@ -74,3 +74,24 @@
|
|||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
[% BLOCK 'group-hold-modal' %]
|
||||
<div id="group-modal" class="modal" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="suspend-modal-label"> Group selected holds </h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to group the selected holds?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="group-modal-submit" class="btn btn-primary" type="submit" name="submit">Group</button>
|
||||
<button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel </button>
|
||||
</div>
|
||||
<div id="inputs"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@
|
|||
<tr>
|
||||
<th id="checkbox" data-colname="checkbox"><input type="checkbox" class="select_hold_all" /></th>
|
||||
<th>Hold date</th>
|
||||
[% IF Koha.Preference('DisplayAddHoldGroups') %]
|
||||
<th>Hold group</th>
|
||||
[% END %]
|
||||
<th class="anti-the">Title</th>
|
||||
<th>Call number</th>
|
||||
<th>Item type</th>
|
||||
|
|
@ -171,6 +174,12 @@
|
|||
|
||||
[% PROCESS 'cancel-hold-modal' form_action = '/cgi-bin/koha/reserve/modrequest.pl' from_param = patronpage %]
|
||||
[% PROCESS 'suspend-hold-modal' %]
|
||||
[% PROCESS 'group-hold-modal' %]
|
||||
[% IF Koha.Preference('DisplayAddHoldGroups') %]
|
||||
<fieldset class="action">
|
||||
<button class="btn btn-primary group_selected_holds" data-bulk="true"></button>
|
||||
</fieldset>
|
||||
[% END # IF DisplayAddHoldGroups %]
|
||||
|
||||
[% IF Koha.Preference('SuspendHoldsIntranet') %]
|
||||
<fieldset class="action">
|
||||
|
|
|
|||
|
|
@ -1103,6 +1103,7 @@
|
|||
relatives_borrowernumbers.push("[% b | html %]");
|
||||
[% END %]
|
||||
var SuspendHoldsIntranet = [% ( Koha.Preference('SuspendHoldsIntranet') ) ? 1 : 0 | html %];
|
||||
var DisplayAddHoldGroups = [% ( Koha.Preference('DisplayAddHoldGroups') ) ? 1 : 0 | html %];
|
||||
</script>
|
||||
[% Asset.js("js/pages/circulation.js") | $raw %]
|
||||
[% IF Koha.Preference('ClaimReturnedLostValue') || Koha.Preference('BundleLostValue') %]
|
||||
|
|
|
|||
|
|
@ -785,6 +785,7 @@
|
|||
relatives_borrowernumbers.push("[% b | html %]");
|
||||
[% END %]
|
||||
var SuspendHoldsIntranet = [% ( Koha.Preference('SuspendHoldsIntranet') ) ? 1 : 0 | html %];
|
||||
var DisplayAddHoldGroups = [% ( Koha.Preference('DisplayAddHoldGroups') ) ? 1 : 0 | html %];
|
||||
</script>
|
||||
[% Asset.js("js/pages/circulation.js") | $raw %]
|
||||
[% IF Koha.Preference('ClaimReturnedLostValue') || Koha.Preference('BundleLostValue') %]
|
||||
|
|
|
|||
|
|
@ -164,6 +164,11 @@ $(document).ready(function () {
|
|||
return (
|
||||
'<input type="checkbox" class="select_hold" data-id="' +
|
||||
oObj.reserve_id +
|
||||
(oObj.hold_group_id
|
||||
? '" data-hold-group-id="' +
|
||||
oObj.hold_group_id +
|
||||
'"'
|
||||
: '"') +
|
||||
'" data-borrowernumber="' +
|
||||
borrowernumber +
|
||||
'" data-biblionumber="' +
|
||||
|
|
@ -178,6 +183,28 @@ $(document).ready(function () {
|
|||
sort: "reservedate",
|
||||
},
|
||||
},
|
||||
...(DisplayAddHoldGroups
|
||||
? [
|
||||
{
|
||||
data: function (oObj) {
|
||||
title = "";
|
||||
if (oObj.visual_hold_group_id) {
|
||||
var link =
|
||||
'<a class="hold-group" href="/cgi-bin/koha/reserve/hold-group.pl?hold_group_id=' +
|
||||
oObj.hold_group_id +
|
||||
'">' +
|
||||
oObj.visual_hold_group_id +
|
||||
"</a>";
|
||||
|
||||
title =
|
||||
"<span>" + link + "</span>";
|
||||
}
|
||||
|
||||
return title;
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
data: function (oObj) {
|
||||
title =
|
||||
|
|
@ -232,17 +259,6 @@ $(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;
|
||||
},
|
||||
},
|
||||
|
|
@ -617,19 +633,7 @@ $(document).ready(function () {
|
|||
e.preventDefault();
|
||||
let selected_holds;
|
||||
if (!$(this).data("hold-id")) {
|
||||
selected_holds =
|
||||
"[" +
|
||||
$(".holds_table .select_hold:checked")
|
||||
.toArray()
|
||||
.map(el =>
|
||||
JSON.stringify({
|
||||
hold: $(el).data("id"),
|
||||
borrowernumber: $(el).data("borrowernumber"),
|
||||
biblionumber: $(el).data("biblionumber"),
|
||||
})
|
||||
)
|
||||
.join(",") +
|
||||
"]";
|
||||
selected_holds = get_selected_holds_data();
|
||||
} else {
|
||||
selected_holds =
|
||||
"[" + JSON.stringify({ hold: $(this).data("hold-id") }) + "]";
|
||||
|
|
@ -800,6 +804,8 @@ $(document).ready(function () {
|
|||
});
|
||||
}
|
||||
|
||||
var MSG_GROUP_SELECTED = _("Group selected (%s)");
|
||||
|
||||
function updateSelectedHoldsButtonCounters() {
|
||||
$(".cancel_selected_holds").html(
|
||||
MSG_CANCEL_SELECTED.format(
|
||||
|
|
@ -816,6 +822,11 @@ $(document).ready(function () {
|
|||
$(".holds_table .select_hold:checked").length
|
||||
)
|
||||
);
|
||||
$(".group_selected_holds").html(
|
||||
MSG_GROUP_SELECTED.format(
|
||||
$(".holds_table .select_hold:checked").length
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function updateMoveButtons(table) {
|
||||
|
|
@ -1148,4 +1159,100 @@ $(document).ready(function () {
|
|||
'<input type="hidden" name="reserve_id" value="' + hold.hold + '">'
|
||||
);
|
||||
}
|
||||
|
||||
$(".group_selected_holds").html(
|
||||
MSG_GROUP_SELECTED.format($(".holds_table .select_hold:checked").length)
|
||||
);
|
||||
|
||||
$(".group_selected_holds").click(function (e) {
|
||||
if ($(".holds_table .select_hold:checked").length > 1) {
|
||||
let selected_holds = get_selected_holds_data();
|
||||
const group_ids = JSON.parse(selected_holds)
|
||||
.filter(hold => hold.hold_group_id)
|
||||
.map(hold => hold.hold_group_id);
|
||||
|
||||
if (group_ids.length > 0) {
|
||||
$("#group-modal .modal-body").prepend(
|
||||
'<div class="alert alert-warning">' +
|
||||
__(
|
||||
"Already grouped holds will be moved to the new group"
|
||||
) +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
|
||||
$("#group-modal").modal("show");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#group-modal-submit").click(function (e) {
|
||||
e.preventDefault();
|
||||
let selected_holds = get_selected_holds_data();
|
||||
|
||||
const hold_ids = JSON.parse(selected_holds).map(hold => hold.hold);
|
||||
|
||||
try {
|
||||
group_holds(hold_ids)
|
||||
.success(function () {
|
||||
holdsTable.api().ajax.reload();
|
||||
})
|
||||
.fail(function (jqXHR) {
|
||||
$("#group-modal .modal-body").prepend(
|
||||
'<div class="alert alert-danger">' +
|
||||
jqXHR.responseJSON.error +
|
||||
"</div>"
|
||||
);
|
||||
$("#group-modal-submit").prop("disabled", true);
|
||||
})
|
||||
.done(function () {
|
||||
$("#group-modal").modal("hide");
|
||||
$(".select_hold_all").click();
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
alert(__("Unable to group, hold not found."));
|
||||
} else {
|
||||
alert(
|
||||
__(
|
||||
"Your request could not be processed. Check the logs for details."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
function group_holds(hold_ids) {
|
||||
return $.ajax({
|
||||
method: "POST",
|
||||
url: "/api/v1/patrons/" + borrowernumber + "/hold_groups",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ hold_ids: hold_ids, force_grouped: true }),
|
||||
});
|
||||
}
|
||||
|
||||
$("#group-modal").on("hidden.bs.modal", function () {
|
||||
$("#group-modal .modal-body .alert-warning").remove();
|
||||
$("#group-modal .modal-body .alert-danger").remove();
|
||||
$("#group-modal-submit").prop("disabled", false);
|
||||
});
|
||||
|
||||
function get_selected_holds_data() {
|
||||
return (
|
||||
"[" +
|
||||
$(".holds_table .select_hold:checked")
|
||||
.toArray()
|
||||
.map(el =>
|
||||
JSON.stringify({
|
||||
hold: $(el).data("id"),
|
||||
borrowernumber: $(el).data("borrowernumber"),
|
||||
biblionumber: $(el).data("biblionumber"),
|
||||
hold_group_id: $(el).data("hold-group-id"),
|
||||
})
|
||||
)
|
||||
.join(",") +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue