diff --git a/Koha/Hold.pm b/Koha/Hold.pm index d886f7d8b5..91269aa535 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -783,6 +783,24 @@ sub is_suspended { return $self->suspend(); } +=head3 item_level_holds + +Returns the number (count) of item-level holds for this hold's biblionumber and patron + +=cut + +sub item_level_holds { + my ($self) = @_; + + return Koha::Holds->search( + { + biblionumber => $self->biblionumber, + borrowernumber => $self->borrowernumber, + itemnumber => { '!=', undef }, + } + )->count; +} + =head3 add_cancellation_request my $cancellation_request = $hold->add_cancellation_request({ [ creation_date => $creation_date ] }); diff --git a/Koha/REST/V1/Holds.pm b/Koha/REST/V1/Holds.pm index ad5b4c4d64..90fdc38f7c 100644 --- a/Koha/REST/V1/Holds.pm +++ b/Koha/REST/V1/Holds.pm @@ -281,21 +281,27 @@ sub edit { } $pickup_library_id //= $hold->branchcode; - my $priority = $body->{priority} // $hold->priority; - my $hold_date = $body->{hold_date} // $hold->reservedate; - my $expiration_date = $body->{expiration_date} // $hold->expirationdate; + my $priority = $body->{priority} // $hold->priority; + my $hold_date = exists $body->{hold_date} ? $body->{hold_date} : $hold->reservedate; + my $expiration_date = exists $body->{expiration_date} ? $body->{expiration_date} : $hold->expirationdate; + my $item_id = exists $body->{item_id} ? $body->{item_id} : $hold->itemnumber; + my $item_level = + exists $body->{item_level} + ? ( $body->{item_level} ? 1 : 0 ) + : $hold->item_level_hold; # suspended_until can also be set to undef my $suspended_until = $body->{suspended_until} || $hold->suspend_until; my $params = { - reserve_id => $hold->id, - branchcode => $pickup_library_id, - rank => $priority, - suspend_until => $suspended_until, - itemnumber => $hold->itemnumber, - reservedate => $hold_date, - expirationdate => $expiration_date, + reserve_id => $hold->id, + branchcode => $pickup_library_id, + rank => $priority, + suspend_until => $suspended_until, + itemnumber => $item_id, + reservedate => $hold_date, + expirationdate => $expiration_date, + item_level_hold => $item_level }; C4::Reserves::ModReserve($params); diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml index 5247852c53..c75070f886 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -2141,6 +2141,7 @@ modules: columnname: action patron_holds_table: + default_sort_order: 1 columns: - columnname: checkbox diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml index 9a62c768c4..7ce4fb6fd9 100644 --- a/api/v1/swagger/definitions/hold.yaml +++ b/api/v1/swagger/definitions/hold.yaml @@ -114,6 +114,11 @@ properties: item_level: type: boolean description: If the hold is placed at item level + item_level_holds: + type: + - integer + - "null" + description: Count of item level holds for this bibliographic record cancellation_requested: type: - boolean @@ -134,6 +139,11 @@ properties: - object - "null" description: The item + item_group: + type: + - object + - "null" + description: The item group pickup_library: type: - object diff --git a/api/v1/swagger/paths/holds.yaml b/api/v1/swagger/paths/holds.yaml index dcd6413c03..71474e1218 100644 --- a/api/v1/swagger/paths/holds.yaml +++ b/api/v1/swagger/paths/holds.yaml @@ -108,6 +108,8 @@ - pickup_library - item - patron + - item_group + - item_level_holds collectionFormat: csv produces: - application/json @@ -430,12 +432,26 @@ format: date-time hold_date: description: Hold date - type: string + type: + - string + - "null" format: date expiration_date: description: Hold's expiration date - type: string + type: + - string + - "null" format: date + item_id: + description: Internal item identifier + type: + - integer + - "null" + item_level: + description: Whether the hold is item-level or not + type: + - boolean + - "null" additionalProperties: false consumes: - application/json diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc index 8e44790a45..a12f41c60f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc @@ -2,379 +2,25 @@ [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] [% USE AuthorisedValues %] - +[%#This include depends on the asset js/form-submit.js. Any template making use of this include must import form-submit.js as well. %] +[%#FIXME can form-submit.js be imported into this file directly? %] + +
- [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] - - [% END %] + - [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] - - [% END %] + - [%- SET first_priority = 0 -%] - [%- SET found_holds = 0 -%] - [%- SET last_priority = holds.last.priority -%] - - [% SET all_priorities = [] %] - [% FOREACH hold IN holds %] - [% all_priorities.push( hold.priority ) %] - [% END %] - - - [% FOREACH hold IN holds %] - [%- IF !hold.found && first_priority == 0 -%] - [%- first_priority = hold.priority -%] - [%- found_holds = loop.index() -%] - [%- END -%] - [%- IF Koha.Preference('HoldsSplitQueueNumbering') == 'actual' -%] - [%- this_priority = hold.priority -%] - [%- ELSE -%] - [%- this_priority = loop.count() - found_holds -%] - [%- END -%] - [% SET tr_class = hold.suspend ? 'suspend' : '' %] - - - - [%- IF ( CAN_user_reserveforothers_modify_holds_priority ) -%] - [%- UNLESS hold.found -%] - [%- SET prev_priority = loop.prev.priority -%] - [%- SET next_priority = loop.next.priority -%] - - [%- ELSE -%] - - [%- END -%] - [%- END -%] - - - - - - - [%- IF ( CAN_user_reserveforothers_modify_holds_priority ) -%] - [%- UNLESS hold.found -%] - - [%- ELSE -%] - - [%- END -%] - [%- END -%] - - - [% IF ( hold.intransit || hold.atdestination ) %] - - [% ELSE %] - - [% END %] - - [% END %] -
PriorityChange priorityChange priority Patron Notes Date Expiration Pickup library DetailsSet lowest prioritySet lowest priority Delete Suspend Print hold/transfer slip
-
- - - - [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] - [% IF Koha.Preference('HoldsSplitQueue') == "nothing" && !hold.found %] - - - [% ELSE %] - - - [% END %] - [% ELSE %] - - [% END %] -
- - [% hold.priority | html %] -
-
-
- - - - - - - - - - - - - - - - [% INCLUDE 'patron-title.inc' patron=hold.patron hide_patron_infos_if_needed=1 %] [% hold.notes | html | html_line_break %] - [% IF Koha.Preference('AllowHoldDateInFuture') %] - - [% ELSE %] - [% hold.date | $KohaDates %] - [% END %] - - - - [%- IF ( hold.found ) -%] - - [%- IF ( hold.atdestination ) -%] - Item waiting at [% hold.wbrname | html %][% IF hold.desk_name %], [% hold.desk_name | html %],[% END %] since [% hold.waiting_date | $KohaDates %] - [%- ELSIF (hold.intransit) -%] - Item being transferred to [% hold.wbrname | html %] - [%- ELSIF (hold.inprocessing) -%] - Item being processed at [% hold.wbrname | html %] - [%- ELSE -%] - Hold expected at [% hold.wbrname | html %], please checkin to verify status - [%- END -%] - [%- ELSE -%] - [%- IF Koha.Preference('IndependentBranches') && Branches.all().size == 1 -%] - [% Branches.GetName(hold.branchcode) | html %] - [%- ELSE -%] - - - [%- END -%] - [%- END -%] - - [%- IF ( hold.found ) -%] - - [%- IF ( hold.barcodenumber ) -%] - [%- hold.barcodenumber | html -%] - [%- ELSE -%] - No barcode - [%- END -%] - - - [%- ELSE -%] - [%- IF ( hold.item_level_hold ) -%] - - [%- IF ! hold.change_hold_type_allowed -%] - Only item - - [%- IF ( hold.barcodenumber ) -%] - [%- hold.barcodenumber | html -%] - [%- ELSE -%] - No barcode - [%- END -%] - - - [%- ELSE -%] - - [%- IF ( hold.itemnumber ) -%] - - [%- END -%] - [%- IF hold.itemtype -%] - Next available [% ItemTypes.GetDescription( hold.itemtype ) | html %] item - [%- ELSE -%] - Next available - [%- END -%] - [%- END -%] - - [%- ELSE -%] - [%- IF hold.itemtype -%] - Next available [% ItemTypes.GetDescription( hold.itemtype ) | html %] item - [%- ELSIF hold.object.item_group -%] - Next available item from group [% hold.object.item_group.description | html %] - [%- ELSE -%] - Next available - [%- END -%] - - - [%- END -%] - [%- END -%] - [%- IF hold.non_priority -%] -
Non priority hold - [%- END -%] - [%- IF hold.hold_group_id -%] -
(part of a hold group)
- [%- END -%] -
- [% IF ( hold.lowestPriority ) %] - - - - [% ELSE %] - - - - [% END %] - - - - - - [% IF Koha.Preference('SuspendHoldsIntranet') %] - [% UNLESS ( hold.found ) %] - [% IF ( hold.suspend ) %] - - [% ELSE %] - - [% END %] - - [% IF Koha.Preference('AutoResumeSuspendedHolds') %] - - - [%- ELSE -%] - - [%- END -%] - [%- END -%] - [%- END # IF SuspendHoldsIntranet -%] - [%- IF ( hold.found ) -%] - [% IF hold.intransit %]Revert transit status[% ELSE %]Revert waiting status[% 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 3ba9f7e711..90a69ccde3 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -1311,7 +1311,7 @@ [% END %] [% END %] [% END %] - [% IF ( reserveloop ) %] + [% IF ( total_holds ) %]
[% INCLUDE 'csrf-token.inc' %] [% IF ( multi_hold ) %] @@ -1330,11 +1330,7 @@

Existing holds

- - -
-
- +
[% FOREACH biblioloo IN biblioloop %] - [% IF ( biblioloo.reserveloop ) %] + [% IF ( biblioloo.total_holds ) %]
[% IF ( multi_hold ) %]

[% biblioloo.title | html %] - [% biblioloo.reserveloop.size | html %] [% tn('Hold', 'Holds', biblioloo.reserveloop.size) | $raw %] + [% biblioloo.total_holds | html %] [% tn('Hold', 'Holds', biblioloo.total_holds) | $raw %]

[% END %] [% IF Koha.Preference('HoldsSplitQueue') == 'branch' %] - - [% SET branchcodes = [] %] - - [% FOREACH h IN biblioloo.reserveloop %] - [% branchcodes.push( h.branchcode ) %] - [% END %] - [% branchcodes = branchcodes.unique %] - [% IF ( branchcodes.empty ) %] -
There are no holds on this title.
+ [% IF biblioloo.hold_branches.empty %] +
+

Any library

+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] +
[% ELSE %] - - [% FOREACH b IN branchcodes.sort %] - [% SET holds_by_branch = [] %] - [% FOREACH h IN biblioloo.reserveloop %] - [% IF h.branchcode == b %] - [% holds_by_branch.push( h ) %] - [% END %] - [% END %] + [% FOREACH b IN biblioloo.hold_branches %]

[% Branches.GetName( b ) | html %]

- [% INCLUDE holds_table.inc holds=holds_by_branch %] + [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data=b %]
- [% END # /FOREACh b %] - [% END # /IF ( branchcodes.empty ) %] + [% END %] + [% END # /IF hold_branches.empty %] [% ELSIF Koha.Preference('HoldsSplitQueue') == 'itemtype' %] - [% SET itemtypes = [] %] - - [% FOREACH h IN biblioloo.reserveloop %] - [% SET hold_itemtype = h.object.item.effective_itemtype || h.itemtype %] - [% itemtypes.push( hold_itemtype ) %] - [% END %] - [% itemtypes = itemtypes.unique %] - [% IF ( itemtypes.empty ) %] -
There are no holds on this title.
+ [% IF biblioloo.hold_itemtypes.empty %] +
+

Any item type

+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] +
[% ELSE %] - - [% FOREACH i IN itemtypes.sort %] - [% SET holds_by_itemtype = [] %] - [% FOREACH h IN biblioloo.reserveloop %] - [% SET hold_itemtype = h.object.item.effective_itemtype || h.itemtype %] - [% IF hold_itemtype == i %] - [% holds_by_itemtype.push( h ) %] - [% END %] - [% END %] - + [% FOREACH i IN biblioloo.hold_itemtypes %]
- [% IF i %] + [% IF i && i != 'any' %]

[% ItemTypes.GetDescription( i ) | html %]

[% ELSE %]

Any item type

[% END %] - [% INCLUDE holds_table.inc holds=holds_by_itemtype %] + [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data=i %]
- [% END # /FOREACH i %] - [% END # /IF ( itemtypes.empty ) %] + [% END %] + [% END # /IF hold_split.empty %] [% ELSIF Koha.Preference('HoldsSplitQueue') == 'branch_itemtype' %] - [% SET branchcodes = [] %] - [% FOREACH h IN biblioloo.reserveloop %] - [% branchcodes.push( h.branchcode ) %] - [% END %] - [% branchcodes = branchcodes.unique %] - [% IF ( branchcodes.empty ) %] -
There are no holds on this title.
- [% ELSE %] - - [% FOREACH b IN branchcodes.sort %] -
-

[% Branches.GetName( b ) | html %]

- [% SET holds_by_branch = [] %] - [% FOREACH h IN biblioloo.reserveloop %] - [% IF h.branchcode == b %] - [% holds_by_branch.push( h ) %] + [% FOREACH b IN biblioloo.hold_branches %] +
+

[% Branches.GetName( b ) | html %]

+ [% FOREACH i IN biblioloo.hold_itemtypes %] +
+ [% IF i && i != 'any' %] +

[% ItemTypes.GetDescription( i ) | html %]

+ [% ELSE %] +

Any item type

[% END %] - [% END %] - - [% SET itemtypes = [] %] - [% FOREACH h IN holds_by_branch %] - [% SET hold_itemtype = h.object.item.effective_itemtype || h.itemtype %] - [% itemtypes.push( hold_itemtype ) %] - [% END %] - [% itemtypes = itemtypes.unique %] - - [% FOREACH i IN itemtypes.sort %] -
-
- [% IF i %] - [% ItemTypes.GetDescription( i ) | html %] - [% ELSE %] - Any item type - [% END %] -
- - [% SET holds_by_itemtype = [] %] - [% FOREACH h IN holds_by_branch %] - [% SET hold_itemtype = h.object.item.effective_itemtype || h.itemtype %] - [% IF hold_itemtype == i %] - [% holds_by_itemtype.push( h ) %] - [% END %] - [% END %] - [% INCLUDE holds_table.inc holds=holds_by_itemtype %] -
- - [% END %] -
- - [% END # /FOREACH b %] - [% END # /IF ( branchcodes.empty ) %] - [% ELSE %] - - [% IF ( biblioloo.reserveloop.size ) %] - [% INCLUDE holds_table.inc holds=biblioloo.reserveloop %] - [% ELSE %] -
There are no holds on this title.
+ [% SET split_data = b _ "_" _ i %] + [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data=split_data %] +
+ [% END %] +
[% END %] + [% ELSE %] + [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] [% END # /IF HoldsSplitQueue %]
- [% END # /IF biblioloo.reserveloop %] + [% ELSE %] +
There are no holds on this title.
+ [% END # /IF ( biblioloo.total_holds ) %] [% END # FOREACH biblioloo %]
@@ -1605,6 +1539,9 @@ [% INCLUDE 'datatables.inc' %] [% INCLUDE 'calendar.inc' %] [% INCLUDE 'select2.inc' %] + [% Asset.js("js/holds.js") | $raw %] [% Asset.js("js/hold-group.js") | $raw %] @@ -1613,8 +1550,16 @@ [% SET url_biblio_params = url_biblio_params _ "&multi_hold=1" %] [% END %] [% END %] - [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 7b6dde8a7c..b3cd2441d0 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -1456,6 +1456,9 @@ function update_search_description( emptyTable: options.emptyTable ? options.emptyTable : __("No data available in table"), + infoFiltered: options.infoFiltered + ? options.infoFiltered + : __("(filtered from _MAX_ total entries)"), }, }, options diff --git a/koha-tmpl/intranet-tmpl/prog/js/holds.js b/koha-tmpl/intranet-tmpl/prog/js/holds.js index 5e6537fa72..e89bdc8d4d 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/holds.js +++ b/koha-tmpl/intranet-tmpl/prog/js/holds.js @@ -791,13 +791,13 @@ $(document).ready(function () { } }); - if (!patron_page) { - $(".holds_table .select_hold_all").each(function () { - var table = $(this).parents(".holds_table"); - var count = $(".select_hold:not(:checked)", table).length; - $(".select_hold_all", table).prop("checked", !count); - }); - } + // if (!patron_page) { + // $(".holds_table .select_hold_all").each(function () { + // var table = $(this).parents(".holds_table"); + // var count = $(".select_hold:not(:checked)", table).length; + // $(".select_hold_all", table).prop("checked", !count); + // }); + // } function updateSelectedHoldsButtonCounters() { $(".move_selected_holds").html( @@ -808,20 +808,15 @@ $(document).ready(function () { $(".selected_holds_count").html( $(".holds_table .select_hold:checked").length ); - if (patron_page) { - var selectedHolds = $(".holds_table .select_hold:checked"); - var hasSelectedHolds = selectedHolds.length > 0; - var hasMultipleSelectedHolds = selectedHolds.length >= 2; + var selectedHolds = $(".holds_table .select_hold:checked"); + var hasSelectedHolds = selectedHolds.length > 0; + var hasMultipleSelectedHolds = selectedHolds.length >= 2; - $(".cancel_selected_holds, .suspend_selected_holds").prop( - "disabled", - !hasSelectedHolds - ); - $(".group_selected_holds").prop( - "disabled", - !hasMultipleSelectedHolds - ); - } + $(".cancel_selected_holds, .suspend_selected_holds").prop( + "disabled", + !hasSelectedHolds + ); + $(".group_selected_holds").prop("disabled", !hasMultipleSelectedHolds); } function updateMoveButtons(table) { @@ -841,96 +836,73 @@ $(document).ready(function () { $(".move_selected_holds").prop("disabled", !checked_count); } - updateSelectedHoldsButtonCounters(); - - $(".holds_table .select_hold_all").click(function () { - var table; - if (!patron_page) { - table = $(this).parents(".holds_table"); - } else { - table = $(".holds_table:not(.fixedHeader-floating)"); - } - - var checked_count = $(".select_hold:checked", table).length; - $(".select_hold", table).prop("checked", !checked_count); - $(this).prop("checked", !checked_count); - - updateMoveButtons(table); - + if (holds_table_patron_page()) { updateSelectedHoldsButtonCounters(); - $("#cancel_hold_alert").html( - MSG_CANCEL_ALERT.format( - $(".holds_table .select_hold:checked").length - ) - ); - $("#cancel_hold_alert").show(); - localStorage.selectedHolds = - "[" + - $(".holds_table .select_hold:checked") - .toArray() - .map(el => - JSON.stringify({ - hold: $(el).data("id"), - borrowernumber: $(el).data("borrowernumber"), - biblionumber: $(el).data("biblionumber"), - }) + + $(".holds_table .select_hold_all").click(function () { + var table = $(".holds_table:not(.fixedHeader-floating)"); + + var checked_count = $(".select_hold:checked", table).length; + $(".select_hold", table).prop("checked", !checked_count); + $(this).prop("checked", !checked_count); + + updateMoveButtons(table); + + updateSelectedHoldsButtonCounters(); + $("#cancel_hold_alert").html( + MSG_CANCEL_ALERT.format( + $(".holds_table .select_hold:checked").length ) - .join(",") + - "]"; - }); - - $(".holds_table").on("click", ".select_hold", function () { - var table = $(this).parents(".holds_table"); - var count = $(".select_hold:not(:checked)", table).length; - $(".select_hold_all", table).prop("checked", !count); - - updateMoveButtons(table); - - updateSelectedHoldsButtonCounters(); - $("#cancel_hold_alert").html( - MSG_CANCEL_ALERT.format( - $(".holds_table .select_hold:checked").length - ) - ); - $("#cancel_hold_alert").show(); - localStorage.selectedHolds = - "[" + - $(".holds_table .select_hold:checked") - .toArray() - .map(el => - JSON.stringify({ - hold: $(el).data("id"), - borrowernumber: $(el).data("borrowernumber"), - biblionumber: $(el).data("biblionumber"), - }) - ) - .join(",") + - "]"; - }); - - $(".cancel_selected_holds").click(function (e) { - e.preventDefault(); - if ($(".holds_table .select_hold:checked").length) { - $("#cancel_modal_form #inputs").empty(); - if (!patron_page) { - biblionumbers.forEach(function (biblionumber) { - $("#cancel_modal_form #inputs").append( - '' - ); - }); - $("#cancel_modal_form #inputs").append( - '' - ); - let hold_ids = $(".holds_table .select_hold:checked") + ); + $("#cancel_hold_alert").show(); + localStorage.selectedHolds = + "[" + + $(".holds_table .select_hold:checked") .toArray() - .map(el => $(el).data("id")) - .join(","); - $("#cancel_modal_form #inputs").append( - '' - ); - } else { + .map(el => + JSON.stringify({ + hold: $(el).data("id"), + borrowernumber: $(el).data("borrowernumber"), + biblionumber: $(el).data("biblionumber"), + }) + ) + .join(",") + + "]"; + }); + + $(".holds_table").on("click", ".select_hold", function () { + var table = $(this).parents(".holds_table"); + var count = $(".select_hold:not(:checked)", table).length; + $(".select_hold_all", table).prop("checked", !count); + + updateMoveButtons(table); + + updateSelectedHoldsButtonCounters(); + $("#cancel_hold_alert").html( + MSG_CANCEL_ALERT.format( + $(".holds_table .select_hold:checked").length + ) + ); + $("#cancel_hold_alert").show(); + localStorage.selectedHolds = + "[" + + $(".holds_table .select_hold:checked") + .toArray() + .map(el => + JSON.stringify({ + hold: $(el).data("id"), + borrowernumber: $(el).data("borrowernumber"), + biblionumber: $(el).data("biblionumber"), + }) + ) + .join(",") + + "]"; + }); + + $(".cancel_selected_holds").click(function (e) { + e.preventDefault(); + if ($(".holds_table .select_hold:checked").length) { + $("#cancel_modal_form #inputs").empty(); $("#cancel_modal_form #inputs").append( '' ); @@ -950,14 +922,12 @@ $(document).ready(function () { JSON.parse(hold_data).forEach(function (hold) { _append_patron_page_cancel_hold_modal_data(hold); }); + delete localStorage.selectedHolds; + $("#cancelModal").modal("show"); } - - delete localStorage.selectedHolds; - $("#cancelModal").modal("show"); - } - return false; - }); - + return false; + }); + } $("#itemSearchForm").on("submit", function (event) { event.preventDefault(); $("#move_hold_item_confirm").prop("disabled", true); @@ -1076,18 +1046,26 @@ $(document).ready(function () { $("#itemResultMessage").empty(); $("#move_hold_item_selection table tbody").empty(); $("#moveHoldItemModal").modal("show"); - $(".select_hold:checked").each(function () { - let reserve_id = $(this).data("id"); - let reserve_biblionumber = $(this).data("biblionumber"); - let reserve_itemnumber = $(this).data("itemnumber"); - let item_level_hold = $(this).data("item_level_hold"); - let item_waiting = $(this).data("waiting"); - let item_intransit = $(this).data("intransit"); - let error_message = $(this).data("item_level_hold") - ? "" - : __( - "Cannot move a waiting, in transit, or record level hold" - ); + const selectedHolds = + JSON.parse(localStorage.selectedHolds) || + $(".select_hold:checked"); + $(selectedHolds).each(function () { + let reserve_id = this.hold || $(this).data("id"); + let reserve_biblionumber = + this.biblionumber || $(this).data("biblionumber"); + let reserve_itemnumber = + this.itemnumber || $(this).data("itemnumber"); + let item_level_hold = + this.item_level_hold || $(this).data("item_level_hold"); + let item_waiting = this.waiting || $(this).data("waiting"); + let item_intransit = + this.intransit || $(this).data("intransit"); + let error_message = + this.item_level_hold || $(this).data("item_level_hold") + ? "" + : __( + "Cannot move a waiting, in transit, or record level hold" + ); let found_status = $(this).data("found"); if (item_level_hold && (!item_waiting || !item_intransit)) { $("#move_hold_item_selection table").append( @@ -1105,23 +1083,31 @@ $(document).ready(function () { $(".move_hold_biblio").click(function (e) { e.preventDefault(); $("#move_hold_biblio_confirm").prop("disabled", true); - if ($(".holds_table .select_hold:checked").length) { + const selectedHolds = + JSON.parse(localStorage.selectedHolds || "[]") || + $(".select_hold:checked"); + if (selectedHolds.length) { $("#biblioResultMessage").empty(); $("#move_hold_biblio_selection table tbody").empty(); $("#moveHoldBiblioModal").modal("show"); - $(".select_hold:checked").each(function () { - let reserve_id = $(this).data("id"); - let reserve_biblionumber = $(this).data("biblionumber"); - let reserve_itemnumber = $(this).data("itemnumber"); - let item_level_hold = $(this).data("item_level_hold"); - let item_status = $(this).data("status"); - let item_waiting = $(this).data("waiting"); - let item_intransit = $(this).data("intransit"); - let error_message = $(this).data("item_level_hold") - ? __( - "Cannot move a waiting, in transit, or item level hold" - ) - : ""; + $(selectedHolds).each(function () { + let reserve_id = this.hold || $(this).data("id"); + let reserve_biblionumber = + this.biblionumber || $(this).data("biblionumber"); + let reserve_itemnumber = + this.itemnumber || $(this).data("itemnumber"); + let item_level_hold = + this.item_level_hold || $(this).data("item_level_hold"); + let item_status = this.status || $(this).data("status"); + let item_waiting = this.waiting || $(this).data("waiting"); + let item_intransit = + this.intransit || $(this).data("intransit"); + let error_message = + this.item_level_hold || $(this).data("item_level_hold") + ? __( + "Cannot move a waiting, in transit, or item level hold" + ) + : ""; let found_status = $(this).data("found"); if (!item_level_hold && (!item_waiting || !item_intransit)) { $("#move_hold_biblio_selection table").append( @@ -1226,12 +1212,15 @@ $(document).ready(function () { }), }); } - - $("#cancelModal").on("hidden.bs.modal", function () { - $("#cancelModal .modal-body .alert-danger").remove(); - $("#cancelModalConfirmBtn").prop("disabled", false); - holdsTable.api().ajax.reload(); - }); + if (holds_table_patron_page()) { + $("#cancelModal").on("hidden.bs.modal", function () { + $("#cancelModal .modal-body .alert-danger").remove(); + $("#cancelModalConfirmBtn").prop("disabled", false); + if (holdsTable) { + holdsTable.api().ajax.reload(); + } + }); + } $("#group-modal-submit").click(function (e) { e.preventDefault(); @@ -1303,3 +1292,1099 @@ $(document).ready(function () { ); } }); + +async function load_patron_holds_table(biblio_id, split_data) { + const { name: split_name, value: split_value } = split_data; + let table_class = `patron_holds_table_${biblio_id}_${split_value}`; + const table_id = `#` + table_class; + let url = `/api/v1/holds/?q={"me.biblio_id":${biblio_id}`; + + if (split_name === "branch" && split_value !== "any") { + url += `, "me.pickup_library_id":"${split_value}"`; + } else if (split_name === "itemtype" && split_value !== "any") { + url += `, "me.itemtype":"${split_value}"`; + } else if (split_name === "branch_itemtype") { + const [branch, itemtype] = split_value.split("_"); + url += + itemtype === "any" + ? `, "me.pickup_library_id":"${branch}"` + : `, "me.pickup_library_id":"${branch}", "me.itemtype":"${itemtype}"`; + } + + url += "}"; + const totalHolds = $(table_id).data("total-holds"); + const totalHoldsSelect = parseInt(totalHolds) + 1; + var holdsQueueTable = $(table_id).kohaTable( + { + language: { + infoFiltered: "", + }, + ajax: { + url: url, + }, + embed: ["patron", "item", "item_group", "item_level_holds"], + columnDefs: [ + { + targets: [2, 3], + className: "dt-body-nowrap", + }, + { + targets: [3, 9], + visible: CAN_user_reserveforothers_modify_holds_priority + ? true + : false, + }, + ], + columns: [ + { + data: "hold_id", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + return ( + '' + ); + }, + }, + { + data: "priority", + orderable: true, + searchable: false, + render: function (data, type, row, meta) { + let select = + ''; + } else if (row.status == "P") { + select += + '" disabled="disabled">'; + } else if (row.status == "W") { + select += + '" disabled="disabled">'; + } else { + if ( + HoldsSplitQueue !== "nothing" && + HoldsSplitQueueNumbering === "virtual" + ) { + let virtualPriority = + meta.settings._iDisplayStart + + meta.row + + 1; + select += + '" disabled="disabled">"; + } else { + select += + '" disabled="disabled">"; + } + } + } + select += ""; + return select; + }, + }, + { + data: "", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + if (row.status) { + return null; + } + let buttons = + ''; + buttons += + ''; + buttons += + ''; + buttons += + ''; + return buttons; + }, + }, + { + data: "patron.cardnumber", + orderable: true, + searchable: true, + render: function (data, type, row, meta) { + if (data == null) { + let library = libraries.find( + library => library._id == row.pickup_library_id + ); + return __("A patron from library %s").format( + library.name + ); + } else { + return ( + '' + + data + + "" + ); + } + }, + }, + { + data: "notes", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + return data; + }, + }, + { + data: "hold_date", + orderable: true, + searchable: false, + render: function (data, type, row, meta) { + if (AllowHoldDateInFuture) { + return ( + '' + ); + } else { + return $date(data); + } + }, + }, + { + data: "expiration_date", + orderable: true, + searchable: false, + render: function (data, type, row, meta) { + return ( + '' + ); + }, + }, + { + data: "pickup_library_id", + orderable: true, + searchable: false, + render: function (data, type, row, meta) { + var branchSelect = + ""; + if (row.status == "T") { + return __( + "Item being transferred to %s" + ).format(libraryname); + } else if (row.status == "P") { + return __( + "Item being processed at %s" + ).format(libraryname); + } else if (row.status == "W") { + return __( + "Item waiting at %s since %s" + ).format(libraryname, $date(row.waiting_date)); + } else { + return branchSelect; + } + }, + }, + { + data: "", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + if (row.status) { + return ( + '' + + row.item.external_id + + "" + ); + } else if (row.item_id && row.item_level) { + let barcode = row.item.external_id + ? row.item.external_id + : __("No barcode"); + if (row.item_level_holds >= 2) { + let link = + '' + + (row.item.external_id + ? row.item.external_id.escapeHtml() + : __("No barcode")) + + ""; + return __("Only item") + " " + link; + } else { + let select = + '"; + return select; + } + } else if (row.item_group_id) { + return __( + "Next available item from group %s" + ).format(row.item_group.description); + } else if (row.hold_group_id) { + return ( + "
(" + + __("part of") + + ' ' + + __("hold group") + + ")
" + ); + } else { + if (row.non_priority) { + return ( + "" + + __("Next available") + + "
" + + __("Non priority hold") + + "" + ); + } else { + return "" + __("Next available") + ""; + } + } + }, + }, + { + data: "", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + if (row.item_id) { + return null; + } else { + if (row.lowest_priority) { + return ( + '' + ); + } else { + return ( + '' + ); + } + } + }, + }, + { + data: "hold_id", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + return ( + '' + ); + }, + }, + { + data: "hold_id", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + if (row.status) { + var link_value = + row.status == "T" + ? __("Revert transit status") + : __("Revert waiting status"); + return ( + '' + + link_value + + "" + ); + } else { + let td = ""; + if (SuspendHoldsIntranet) { + td += + '"; + } else { + td += + ' ' + + __("Suspend") + + ""; + } + if (AutoResumeSuspendedHolds) { + if (row.suspended) { + td += + '"; + } else { + td += + '"; + } + td += + ''; + } + return td; + } else { + return null; + } + } + }, + }, + { + data: "hold_id", + orderable: false, + searchable: false, + render: function (data, type, row, meta) { + if (row.status == "W" || row.status == "T") { + return ( + '' + + __("Print slip") + + "" + ); + } else { + return ""; + } + }, + }, + ], + }, + hold_table_settings + ); + holdsQueueTable.api().page(0).draw(false); + // Clear selectedHolds on page load + localStorage.removeItem("selectedHolds"); + $(table_id).on("draw.dt", function () { + function updateMSGCounters() { + var MSG_CANCEL_SELECTED = __("Cancel selected (%s)"); + var MSG_MOVE_SELECTED = __("Move selected (%s)"); + var MSG_CANCEL_ALERT = __( + "This action will cancel %s hold(s)." + ); + var selectedCount = JSON.parse( + localStorage.selectedHolds || "[]" + ).length; + $(".cancel_selected_holds").html( + MSG_CANCEL_SELECTED.format(selectedCount) + ); + $(".move_selected_holds").html( + MSG_MOVE_SELECTED.format(selectedCount) + ); + $("#cancel_hold_alert").html( + MSG_CANCEL_ALERT.format(selectedCount) + ); + if (selectedCount > 0) { + $("#cancel_hold_alert").show(); + } else { + $("#cancel_hold_alert").hide(); + } + } + + function updateMoveButtons() { + var checked_holds = JSON.parse(localStorage.selectedHolds || "[]"); + var checked_count = checked_holds.length; + + var item_level_count = checked_holds.filter(function (hold) { + return hold.item_level_hold > 0; + }).length; + + var record_level_count = checked_holds.filter(function (hold) { + return hold.item_level_hold === 0; + }).length; + + $(".move_hold_item").toggleClass("disabled", item_level_count <= 0); + $(".move_hold_biblio").toggleClass( + "disabled", + record_level_count <= 0 + ); + $(".move_selected_holds").prop("disabled", !checked_count); + } + + // Always deselect the "select all" checkbox when the page changes + $(".holds_table .select_hold_all").prop("checked", false); + updateMSGCounters(); + $(".holds_table .select_hold." + table_class).each(function () { + const selected = JSON.parse(localStorage.selectedHolds || "[]"); + const holdId = $(this).data("id"); + if (selected.some(s => s.hold === holdId)) { + $(this).prop("checked", true); + $(this).parent().parent().addClass("selected"); + var table = $(this).closest(".holds_table"); + var count = $( + ".select_hold." + table_class + ":not(:checked)", + table + ).length; + $(".select_hold_all." + table_class, table).prop( + "checked", + !count + ); + } + }); + $(".holds_table .select_hold_all").on("click", function () { + var table = $(this).closest(".holds_table"); + var isChecked = $(this).prop("checked"); + var allCheckboxes = table + .DataTable() + .rows({ search: "applied" }) + .nodes(); + let selected = JSON.parse(localStorage.selectedHolds || "[]"); + let pageHolds = []; + + $("input.select_hold", allCheckboxes).each(function () { + let hold_data = { + hold: $(this).data("id"), + borrowernumber: $(this).data("borrowernumber"), + biblionumber: $(this).data("biblionumber"), + itemnumber: $(this).data("itemnumber"), + waiting: $(this).data("waiting"), + intransit: $(this).data("intransit"), + status: $(this).data("status"), + item_level_hold: $(this).data("item_level_hold"), + hold_group_id: $(this).data("hold-group-id"), + }; + pageHolds.push(hold_data); + }); + + if (isChecked) { + // Add all page holds to the selection, avoiding duplicates + pageHolds.forEach(hold => { + if (!selected.some(s => s.hold === hold.hold)) { + selected.push(hold); + } + }); + $("input.select_hold", allCheckboxes).prop("checked", true); + $("input.select_hold", allCheckboxes).each(function () { + $(this).parent().parent().addClass("selected"); + }); + } else { + // Remove all page holds from the selection + const pageIds = pageHolds.map(h => h.hold); + selected = selected.filter(s => !pageIds.includes(s.hold)); + $("input.select_hold", allCheckboxes).prop("checked", false); + $("input.select_hold", allCheckboxes).each(function () { + $(this).parent().parent().removeClass("selected"); + }); + } + + localStorage.selectedHolds = JSON.stringify(selected); + updateMoveButtons(); + updateMSGCounters(); + }); + $(".holds_table .select_hold").on("click", function () { + let selected = JSON.parse(localStorage.selectedHolds || "[]"); + const hold_data = { + hold: $(this).data("id"), + borrowernumber: $(this).data("borrowernumber"), + biblionumber: $(this).data("biblionumber"), + itemnumber: $(this).data("itemnumber"), + waiting: $(this).data("waiting"), + intransit: $(this).data("intransit"), + status: $(this).data("status"), + item_level_hold: $(this).data("item_level_hold"), + hold_group_id: $(this).data("hold-group-id"), + }; + if ($(this).is(":checked")) { + if (!selected.some(s => s.hold === hold_data.hold)) { + selected.push(hold_data); + } + } else { + selected = selected.filter(s => s.hold !== hold_data.hold); + } + localStorage.selectedHolds = JSON.stringify(selected); + updateMoveButtons(); + updateMSGCounters(); + var table = $(this).parents(".holds_table"); + var count = $(".select_hold:not(:checked)", table).length; + $(".select_hold_all", table).prop("checked", !count); + $(this).parent().parent().toggleClass("selected"); + }); + $(".cancel-hold." + table_class).on("click", function (e) { + e.preventDefault; + cancel_link = $(this); + $("#cancel_modal_form #inputs").empty(); + let reserve_id = cancel_link.data("id"); + let biblionumber = cancel_link.data("biblionumber"); + _append_patron_page_cancel_hold_modal_data({ + hold: reserve_id, + biblionumber: biblionumber, + borrowernumber: cancel_link.data("borrowernumber"), + }); + $("#cancelModal").modal("show"); + }); + $(".cancel_selected_holds").click(function (e) { + e.preventDefault(); + const selectedHolds = JSON.parse( + localStorage.selectedHolds || "[]" + ); + if (selectedHolds.length) { + $("#cancel_modal_form #inputs").empty(); + + selectedHolds.forEach(function (hold) { + _append_patron_page_cancel_hold_modal_data(hold); + }); + + //delete localStorage.selectedHolds; + $("#cancelModal").modal("show"); + } + return false; + }); + function _append_patron_page_cancel_hold_modal_data(hold) { + $("#cancel_modal_form #inputs").append( + '' + ); + $("#cancel_modal_form #inputs").append( + '' + ); + $("#cancel_modal_form #inputs").append( + '' + ); + $("#cancel_modal_form #inputs").append( + '' + ); + } + // Remove any previously attached handlers + $("#cancelModalConfirmBtn").off("click"); + // Attach the handler to the button + $("#cancelModalConfirmBtn").one("click", function (e) { + e.preventDefault(); + let formInputs = {}; + formInputs["reserve_id"] = $( + "#cancel_modal_form :input[name='reserve_id']" + ) + .map(function () { + return $(this).val(); + }) + .get(); + formInputs["cancellation-reason"] = $( + "#cancel_modal_form :input[name='cancellation-reason']" + ).val(); + cancel_holds( + formInputs["reserve_id"], + formInputs["cancellation-reason"] + ) + .success(function () { + holdsQueueTable.api().ajax.reload(null, false); + }) + .fail(function (jqXHR) { + $("#cancelModal .modal-body").prepend( + '
' + + jqXHR.responseJSON.error + + "
" + ); + $("#cancelModalConfirmBtn").prop("disabled", true); + }) + .done(function () { + $("#cancelModal").modal("hide"); + if ($(".select_hold_all").prop("checked")) { + $(".select_hold_all").click(); + } + }); + }); + function cancel_holds(hold_ids, cancellation_reason) { + return $.ajax({ + method: "DELETE", + url: "/api/v1/holds/cancellation_bulk", + contentType: "application/json", + data: JSON.stringify({ + hold_ids: hold_ids, + cancellation_reason: cancellation_reason, + }), + }); + } + $( + ".holddate." + table_class + ", .expirationdate." + table_class + ).flatpickr({ + onReady: function (selectedDates, dateStr, instance) { + $(instance.altInput) + .wrap("") + .after( + $("") + .attr("href", "#") + .addClass("clear_date") + .addClass("fa fa-times") + .addClass("ps-2") + .on("click", function (e) { + e.preventDefault(); + instance.clear(); + }) + .attr("aria-hidden", true) + ); + }, + onChange: function (selectedDates, dateStr, instance) { + let hold_id = $(instance.input).attr("data-id"); + let fieldname = $(instance.input).attr("name"); + let current_date = $(instance.input).attr("data-current-date"); + dateStr = dateStr ? dateStr : null; + let req = + fieldname == "hold_date" + ? { hold_date: dateStr } + : { expiration_date: dateStr }; + if (current_date != dateStr) { + $.ajax({ + method: "PATCH", + url: "/api/v1/holds/" + encodeURIComponent(hold_id), + contentType: "application/json", + data: JSON.stringify(req), + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + $(instance.input).attr( + "data-current-date", + dateStr + ); + }, + error: function (jqXHR, textStatus, errorThrown) { + holdsQueueTable.api().ajax.reload(null, false); + }, + }); + } + }, + }); + $(".suspenddate." + table_class).flatpickr({ + onReady: function (selectedDates, dateStr, instance) { + $(instance.altInput) + .wrap("") + .after( + $("") + .attr("href", "#") + .addClass("clear_date") + .addClass("fa fa-times") + .addClass("ps-2") + .on("click", function (e) { + e.preventDefault(); + instance.clear(); + }) + .attr("aria-hidden", true) + ); + }, + }); + $(".toggle-suspend." + table_class).one("click", function (e) { + e.preventDefault(); + const hold_id = $(this).data("id"); + const suspended = $(this).attr("data-suspended"); + const input = $( + `.suspenddate.` + table_class + `[data-id="${hold_id}"]` + ); + const method = suspended == "true" ? "DELETE" : "POST"; + let end_date = input.val() && method == "POST" ? input.val() : null; + let params = + end_date !== null && end_date !== "" + ? JSON.stringify({ end_date: end_date }) + : null; + $.ajax({ + method: method, + url: + "/api/v1/holds/" + + encodeURIComponent(hold_id) + + "/suspension", + contentType: "application/json", + data: params, + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + }, + error: function (jqXHR, textStatus, errorThrown) { + holdsQueueTable.api().ajax.reload(null, false); + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + }, + }); + }); + $(".rank-request." + table_class).on("change", function (e) { + e.preventDefault(); + const hold_id = $(this).data("id"); + let priority = e.target.value; + // Replace select with spinner + const $select = $(this); + const $spinner = $( + 'Loading...' + ); + $select.hide().after($spinner); + $.ajax({ + method: "PUT", + url: + "/api/v1/holds/" + + encodeURIComponent(hold_id) + + "/priority", + data: JSON.stringify(priority), + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + $spinner.remove(); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + $select.show(); + $spinner.remove(); + }, + }); + }); + $(".move-hold." + table_class).one("click", function (e) { + e.preventDefault(); + let toPosition = $(this).attr("data-move-hold"); + let priority = $(this).attr("data-priority"); + var res_id = $(this).attr("reserve_id"); + var moveTo; + if (toPosition == "up") { + moveTo = parseInt(priority) - 1; + } + if (toPosition == "down") { + moveTo = parseInt(priority) + 1; + } + if (toPosition == "top") { + moveTo = 1; + } + if (toPosition == "bottom") { + moveTo = totalHolds; + } + $.ajax({ + method: "PUT", + url: + "/api/v1/holds/" + encodeURIComponent(res_id) + "/priority", + data: JSON.stringify(moveTo), + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + }, + }); + }); + $(".toggle-lowest-priority." + table_class).one("click", function (e) { + e.preventDefault(); + var res_id = $(this).attr("data-reserve_id"); + $.ajax({ + method: "PUT", + url: + "/api/v1/holds/" + + encodeURIComponent(res_id) + + "/lowest_priority", + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + }, + }); + }); + $(".hold_location_select." + table_class).on("change", function () { + $(this).prop("disabled", true); + var cur_select = $(this); + var res_id = $(this).attr("reserve_id"); + $(this).after( + '
' + ); + let api_url = + "/api/v1/holds/" + + encodeURIComponent(res_id) + + "/pickup_location"; + $.ajax({ + method: "PUT", + url: api_url, + data: JSON.stringify({ pickup_library_id: $(this).val() }), + headers: { "x-koha-override": "any" }, + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + cur_select.prop("disabled", false); + $("#updating_reserveno" + res_id).remove(); + cur_select.val( + cur_select.children('option[selected="selected"]').val() + ); + }, + }); + }); + $(".change_hold_type." + table_class).on("change", function () { + $(this).prop("disabled", true); + var cur_select = $(this); + var hold_id = $(this).attr("data-id"); + $(this).after( + '
' + ); + let api_url = "/api/v1/holds/" + encodeURIComponent(hold_id); + $.ajax({ + method: "PATCH", + url: api_url, + data: JSON.stringify({ item_id: null, item_level: false }), + headers: { "x-koha-override": "any" }, + contentType: "application/json", + success: function (data) { + holdsQueueTable.api().ajax.reload(null, false); + }, + error: function (jqXHR, textStatus, errorThrown) { + alert( + "There was an error:" + textStatus + " " + errorThrown + ); + cur_select.prop("disabled", false); + $("#updating_holdno" + hold_id).remove(); + cur_select.val( + cur_select.children('option[selected="selected"]').val() + ); + }, + }); + }); + $(".printholdslip." + table_class).one("click", function () { + var reserve_id = $(this).attr("data-reserve_id"); + window.open( + "/cgi-bin/koha/circ/hold-transfer-slip.pl?reserve_id=" + + reserve_id + ); + return false; + }); + updateMSGCounters(); + }); +} diff --git a/reserve/request.pl b/reserve/request.pl index fbc08a0826..9f0299ff77 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -669,9 +669,13 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) # existingreserves building my @reserveloop; + my $total_holds = 0; + my $hold_branches = []; + my $hold_itemtypes = []; my $always_show_holds = $input->cookie('always_show_holds'); $template->param( always_show_holds => $always_show_holds ); my $show_holds_now = $input->param('show_holds_now'); + unless ( ( defined $always_show_holds && $always_show_holds eq 'DONT' ) && !$show_holds_now ) { my $holds_count_per_patron = { map { $_->{borrowernumber} => $_->{hold_count} } @{ Koha::Holds->search( @@ -684,64 +688,27 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) )->unblessed } }; - my @reserves = Koha::Holds->search( - { 'me.biblionumber' => $biblionumber }, - { prefetch => [ 'borrowernumber', 'itemnumber' ], order_by => 'priority' } - )->as_list; - foreach my $res ( - sort { - my $a_found = $a->found() || ''; - my $b_found = $a->found() || ''; - $a_found cmp $b_found; - } @reserves - ) - { - my %reserve; - if ( $res->is_found() ) { - $reserve{'holdingbranch'} = $res->item()->holdingbranch(); - $reserve{'biblionumber'} = $res->item()->biblionumber(); - $reserve{'barcodenumber'} = $res->item()->barcode(); - $reserve{'wbrcode'} = $res->branchcode(); - $reserve{'itemnumber'} = $res->itemnumber(); - $reserve{'wbrname'} = $res->branch()->branchname(); - $reserve{'atdestination'} = $res->is_at_destination(); - $reserve{'desk_name'} = ( $res->desk() ) ? $res->desk()->desk_name() : ''; - $reserve{'found'} = $res->is_found(); - $reserve{'inprocessing'} = $res->is_in_processing(); - $reserve{'intransit'} = $res->is_in_transit(); - } elsif ( $res->priority() > 0 ) { - if ( my $item = $res->item() ) { - $reserve{'itemnumber'} = $item->id(); - $reserve{'barcodenumber'} = $item->barcode(); - $reserve{'item_level_hold'} = 1; - } + $total_holds = Koha::Holds->search( { biblionumber => $biblionumber } )->count; + my $branches = Koha::Holds->search( + { + biblionumber => $biblionumber, + }, + { + select => [ { distinct => 'branchcode' } ], + as => [qw( branchcode )], } - $reserve{'expirationdate'} = $res->expirationdate; - $reserve{'date'} = $res->reservedate; - $reserve{'borrowernumber'} = $res->borrowernumber(); - $reserve{'biblionumber'} = $res->biblionumber(); - $reserve{'patron'} = $res->borrower; - $reserve{'notes'} = $res->reservenotes(); - $reserve{'waiting_date'} = $res->waitingdate(); - $reserve{'ccode'} = $res->item() ? $res->item()->ccode() : undef; - $reserve{'barcode'} = $res->item() ? $res->item()->barcode() : undef; - $reserve{'priority'} = $res->priority(); - $reserve{'lowestPriority'} = $res->lowestPriority(); - $reserve{'suspend'} = $res->suspend(); - $reserve{'suspend_until'} = $res->suspend_until(); - $reserve{'reserve_id'} = $res->reserve_id(); - $reserve{itemtype} = $res->itemtype(); - $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; + )->unblessed; + $hold_branches = [ map { $_->{branchcode} } @$branches ]; + my $itemtypes = Koha::Holds->search( + { + biblionumber => $biblionumber, + }, + { + select => [ { distinct => 'itemtype' } ], + as => [qw( itemtype )], } - - push( @reserveloop, \%reserve ); - } + )->unblessed; + $hold_itemtypes = [ map { $_->{itemtype} ? $_->{itemtype} : 'any' } @$itemtypes ]; } # get the time for the form name... @@ -764,11 +731,14 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) C4::Search::enabled_staff_search_views, ); - $biblioloopiter{biblionumber} = $biblionumber; - $biblioloopiter{title} = $biblio->title; - $biblioloopiter{author} = $biblio->author; - $biblioloopiter{rank} = $fixedRank; - $biblioloopiter{reserveloop} = \@reserveloop; + $biblioloopiter{biblionumber} = $biblionumber; + $biblioloopiter{title} = $biblio->title; + $biblioloopiter{author} = $biblio->author; + $biblioloopiter{rank} = $fixedRank; + $biblioloopiter{reserveloop} = \@reserveloop; + $biblioloopiter{total_holds} = $total_holds; + $biblioloopiter{hold_branches} = $hold_branches; + $biblioloopiter{hold_itemtypes} = $hold_itemtypes; # Pass through any reserve charge if ($patron) { @@ -791,6 +761,8 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) $template->param( reserveloop => \@reserveloop ); } + $template->param( total_holds => $total_holds ); + if ( $patron && $multi_hold ) { # Add the valid pickup locations