Bugzilla – Attachment 181244 Details for
Bug 23269
Long hold queues are slowing the service
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 23269: use REST API for patron_holds_table
Bug-23269-use-REST-API-for-patronholdstable.patch (text/plain), 98.21 KB, created by
Pedro Amorim
on 2025-04-22 10:39:58 UTC
(
hide
)
Description:
Bug 23269: use REST API for patron_holds_table
Filename:
MIME Type:
Creator:
Pedro Amorim
Created:
2025-04-22 10:39:58 UTC
Size:
98.21 KB
patch
obsolete
>From fe5769085398eb0203aa67f208609e10a7a4f94e Mon Sep 17 00:00:00 2001 >From: =?UTF-8?q?Johanna=20R=C3=A4is=C3=A4?= <johanna.raisa@gmail.com> >Date: Thu, 24 Oct 2024 09:31:39 +0300 >Subject: [PATCH] Bug 23269: use REST API for patron_holds_table > >This patch uses the REST API to populate the holds table > >Test plan: >1) Apply the patch >2) Go to a record with holds >3) Check that the holds table is populated correctly >4) Check that hold tables functions work correctly >5) Change HoldsSplitQueue and HoldsSplitQueueNumbering system preference, try all variations >6) Repeat steps 3 and 4 >7) Search for records and place holds to multiple records >8) Repeat steps 3 and 4 >9) Try with lesser permissions > >Sponsored-by: Koha-Suomi Oy > >Pedro rebased and tidied after bug 38664 >--- > Koha/REST/V1/Holds.pm | 6 +- > api/v1/swagger/definitions/hold.yaml | 12 +- > api/v1/swagger/paths/holds.yaml | 11 +- > .../prog/en/includes/holds_table.inc | 345 +------ > .../prog/en/modules/reserve/request.tt | 340 ++----- > koha-tmpl/intranet-tmpl/prog/js/holds.js | 851 ++++++++++++++++++ > reserve/request.pl | 91 +- > 7 files changed, 1001 insertions(+), 655 deletions(-) > >diff --git a/Koha/REST/V1/Holds.pm b/Koha/REST/V1/Holds.pm >index 7f8764e6cb1..59e0df659e4 100644 >--- a/Koha/REST/V1/Holds.pm >+++ b/Koha/REST/V1/Holds.pm >@@ -265,9 +265,9 @@ 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; > > # suspended_until can also be set to undef > my $suspended_until = $body->{suspended_until} || $hold->suspend_until; >diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml >index 2ba3367e04f..f33987fccd2 100644 >--- a/api/v1/swagger/definitions/hold.yaml >+++ b/api/v1/swagger/definitions/hold.yaml >@@ -123,10 +123,20 @@ properties: > - object > - "null" > description: The item >+ item_group: >+ type: >+ - object >+ - "null" >+ description: The item group >+ patron: >+ type: >+ - object >+ - "null" >+ description: The patron > pickup_library: > type: > - object > - "null" > description: Pickup library > >-additionalProperties: false >+additionalProperties: false >\ No newline at end of file >diff --git a/api/v1/swagger/paths/holds.yaml b/api/v1/swagger/paths/holds.yaml >index a6aff1b75c2..67899b99e79 100644 >--- a/api/v1/swagger/paths/holds.yaml >+++ b/api/v1/swagger/paths/holds.yaml >@@ -104,6 +104,9 @@ > - cancellation_requested > - biblio > - pickup_library >+ - patron >+ - item >+ - item_group > collectionFormat: csv > produces: > - application/json >@@ -289,11 +292,15 @@ > 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 > additionalProperties: false > consumes: >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 2d65e83dc1f..a12f41c60fb 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >@@ -5,361 +5,22 @@ > [%#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? %] > >-<table id="patron_holds_table" class="holds_table"> >+<table id="patron_holds_table_[% biblio_id | html %]_[% split_data | html %]" class="holds_table" data-total-holds="[% total_holds | html %]"> > <thead> > <tr> > <th id="checkbox" data-colname="checkbox"><input type="checkbox" class="select_hold_all" /></th> > <th id="priority" data-colname="priority">Priority</th> >- [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] >- <th id="change_priority" data-colname="change_priority">Change priority</th> >- [% END %] >+ <th id="change_priority" data-colname="change_priority">Change priority</th> > <th id="patron" data-colname="patron">Patron</th> > <th id="notes" data-colname="notes">Notes</th> > <th id="date" data-colname="date">Date</th> > <th id="expiration" data-colname="expiration">Expiration</th> > <th id="pickup_library" data-colname="pickup_library">Pickup library</th> > <th id="details" data-colname="details">Details</th> >- [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] >- <th id="set_lowest_priority" data-colname="set_lowest_priority"><span style="display:none;">Set lowest priority</span><i class="fa fa-lg fa-rotate-90 fa-fast-forward" title="Toggle set to lowest priority"></i></th> >- [% END %] >+ <th id="set_lowest_priority" data-colname="set_lowest_priority"><span style="display:none;">Set lowest priority</span><i class="fa fa-lg fa-rotate-90 fa-fast-forward" title="Toggle set to lowest priority"></i></th> > <th id="delete" data-colname="delete">Delete</th> > <th id="suspend" data-colname="suspend">Suspend</th> > <th id="print_slip" data-colname="print_slip">Print hold/transfer slip</th> > </tr> > </thead> >- [%- 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 %] >- >- <tbody> >- [% 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' : '' %] >- <tr class="[% tr_class | html %]"> >- <td><input type="checkbox" class="select_hold" data-id="[% hold.reserve_id | html %]" /></td> >- <td> >- <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" /> >- <input type="hidden" name="borrowernumber" value="[% hold.borrowernumber | html %]" /> >- <input type="hidden" name="biblionumber" value="[% hold.biblionumber | html %]" /> >- [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] >- [% IF Koha.Preference('HoldsSplitQueue') == "nothing" && !hold.found %] >- <select name="rank-request" class="rank-request" data-hold-id="[% hold.reserve_id | html %]"> >- [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] >- [% FOREACH priority IN all_priorities %] >- [% IF priority == this_priority %] >- <option value="[% priority | html %]" selected="selected">[% priority | html %]</option> >- [% ELSIF priority > 0 %] >- <option value="[% priority | html %]">[% priority | html %]</option> >- [% END %] >- [% END %] >- [% ELSE %] >- <option value="[% hold.priority | html %]" selected="selected">[% this_priority | html %]</option> >- [% END %] >- <option value="del">del</option> >- </select> >- [% ELSE %] >- <input type="hidden" name="rank-request" class="rank-request" value="[% hold.priority | html %]" data-hold-id="[% hold.reserve_id | html %]" /> >- <select name="rank-request" class="rank-request" disabled="disabled" data-hold-id="[% hold.reserve_id | html %]"> >- [% IF ( hold.found ) %] >- [% IF ( hold.intransit ) %] >- <option value="T" selected="selected">In transit</option> >- [% ELSIF (hold.inprocessing) %] >- <option value="P" selected="selected">In processing</option> >- [% ELSE %] >- <option value="W" selected="selected">Waiting</option> >- [% END %] >- [% ELSE %] >- <option value="[% hold.priority | html %]" selected="selected">[% this_priority | html %]</option> >- [% END %] >- </select> >- [% END %] >- [% ELSE %] >- <input type="hidden" name="rank-request" class="rank-request" value="[% hold.priority | html %]" data-hold-id="[% hold.reserve_id | html %]" /> >- [% hold.priority | html %] >- [% END %] >- </td> >- [%- IF ( CAN_user_reserveforothers_modify_holds_priority ) -%] >- [%- UNLESS hold.found -%] >- [%- SET prev_priority = loop.prev.priority -%] >- [%- SET next_priority = loop.next.priority -%] >- <td style="white-space:nowrap;"> >- <a >- class="hold-arrow" >- title="Move hold up" >- data-op="cud-move" >- data-where="up" >- data-first_priority="[% first_priority | html %]" >- data-last_priority="[% last_priority | html %]" >- data-prev_priority="[% prev_priority | html %]" >- data-next_priority="[% next_priority | html %]" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg icon-move-hold-up" aria-hidden="true"></i> >- </a> >- >- <a >- class="hold-arrow" >- title="Move hold to top" >- data-op="cud-move" >- data-where="top" >- data-first_priority="[% first_priority | html %]" >- data-last_priority="[% last_priority | html %]" >- data-prev_priority="[% prev_priority | html %]" >- data-next_priority="[% next_priority | html %]" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg icon-move-hold-top" aria-hidden="true"></i> >- </a> >- >- <a >- class="hold-arrow" >- title="Move hold to bottom" >- data-op="cud-move" >- data-where="bottom" >- data-first_priority="[% first_priority | html %]" >- data-last_priority="[% last_priority | html %]" >- data-prev_priority="[% prev_priority | html %]" >- data-next_priority="[% next_priority | html %]" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg icon-move-hold-bottom" aria-hidden="true"></i> >- </a> >- >- <a >- class="hold-arrow" >- title="Move hold down" >- data-op="cud-move" >- data-where="down" >- data-first_priority="[% first_priority | html %]" >- data-last_priority="[% last_priority | html %]" >- data-prev_priority="[% prev_priority | html %]" >- data-next_priority="[% next_priority | html %]" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg icon-move-hold-down" aria-hidden="true"></i> >- </a> >- </td> >- [%- ELSE -%] >- <td></td> >- [%- END -%] >- [%- END -%] >- <td> [% INCLUDE 'patron-title.inc' patron=hold.patron hide_patron_infos_if_needed=1 %] </td> >- <td>[% hold.notes | html | html_line_break %]</td> >- <td data-order="[% hold.date| html %]"> >- [% IF Koha.Preference('AllowHoldDateInFuture') %] >- <input type="text" class="flatpickr" value="[% hold.date | html %]" required="required" size="10" name="reservedate" /> >- [% ELSE %] >- [% hold.date | $KohaDates %] >- [% END %] >- </td> >- <td> >- <input type="text" class="flatpickr" data-flatpickr-futuredate="true" value="[% hold.expirationdate | html %]" size="10" name="expirationdate" /> >- </td> >- <td> >- [%- IF ( hold.found ) -%] >- <input type="hidden" name="pickup" value="[% hold.wbrcode | html %]" /> >- [%- IF ( hold.atdestination ) -%] >- Item waiting at <strong> [% hold.wbrname | html %]</strong>[% IF hold.desk_name %], [% hold.desk_name | html %],[% END %] since [% hold.waiting_date | $KohaDates %] >- [%- ELSIF (hold.intransit) -%] >- Item being transferred to <strong> [% hold.wbrname | html %]</strong> >- [%- ELSIF (hold.inprocessing) -%] >- Item being processed at <strong> [% hold.wbrname | html %]</strong> >- [%- ELSE -%] >- Hold expected at <strong>[% hold.wbrname | html %]</strong>, please checkin to verify status >- [%- END -%] >- [%- ELSE -%] >- [%- IF Koha.Preference('IndependentBranches') && Branches.all().size == 1 -%] >- [% Branches.GetName(hold.branchcode) | html %] <input type="hidden" name="pickup" value="[% hold.branchcode | html %]" /> >- [%- ELSE -%] >- <select class="pickup_location_dropdown" data-selected="[% hold.branchcode | html %]" data-hold-id="[% hold.reserve_id | html %]" data-pickup-location-source="hold" name="pickup"> >- <option selected="selected" value="[% hold.branchcode | html %]">[% Branches.GetName(hold.branchcode) | html %]</option> >- <option value="" disabled="disabled" class="loading">Loading...</option> >- </select> >- <img class="loading_[% hold.reserve_id | html %]" src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" style="display:none;" /> >- [%- END -%] >- [%- END -%] >- </td> >- <td> >- [%- IF ( hold.found ) -%] >- <a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% hold.biblionumber | uri %]&itemnumber=[% hold.itemnumber | uri %]#item[% hold.itemnumber | uri %]"> >- [%- IF ( hold.barcodenumber ) -%] >- [%- hold.barcodenumber | html -%] >- [%- ELSE -%] >- <span>No barcode</span> >- [%- END -%] >- <input type="hidden" name="itemnumber" value="[% hold.itemnumber | html %]" /> >- </a> >- [%- ELSE -%] >- [%- IF ( hold.item_level_hold ) -%] >- <em> >- [%- IF ! hold.change_hold_type_allowed -%] >- <span>Only item</span> >- <a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% hold.biblionumber | uri %]&itemnumber=[% hold.itemnumber | uri %]#item[% hold.itemnumber | uri %]"> >- [%- IF ( hold.barcodenumber ) -%] >- [%- hold.barcodenumber | html -%] >- [%- ELSE -%] >- <span>No barcode</span> >- [%- END -%] >- <input type="hidden" name="itemnumber" value="[% hold.itemnumber | html %]" /> >- </a> >- [%- ELSE -%] >- <select name="change_hold_type_[% hold.reserve_id | html %]"> >- <option selected="selected" value="" >- >Only item >- [%- IF ( hold.barcodenumber ) -%] >- [%- hold.barcodenumber | html -%] >- [%- ELSE -%] >- No barcode >- [%- END -%] >- </option> >- <option value="1">Next available</option> >- </select> >- [%- IF ( hold.itemnumber ) -%] >- <input type="hidden" name="itemnumber" value="[% hold.itemnumber | html %]" /> >- [%- END -%] >- [%- IF hold.itemtype -%] >- <span style="display:none">Next available [% ItemTypes.GetDescription( hold.itemtype ) | html %] item</span> >- [%- ELSE -%] >- <span style="display:none">Next available</span> >- [%- END -%] >- [%- END -%] >- </em> >- [%- ELSE -%] >- [%- IF hold.itemtype -%] >- <em>Next available [% ItemTypes.GetDescription( hold.itemtype ) | html %] item</em> >- [%- ELSIF hold.object.item_group -%] >- <em>Next available item from group <strong>[% hold.object.item_group.description | html %]</strong></em> >- [%- ELSE -%] >- <em>Next available</em> >- [%- END -%] >- >- <input type="hidden" name="itemnumber" value="" /> >- [%- END -%] >- [%- END -%] >- [%- IF hold.non_priority -%] >- <br /><i>Non priority hold</i> >- [%- END -%] >- </td> >- [%- IF ( CAN_user_reserveforothers_modify_holds_priority ) -%] >- [%- UNLESS hold.found -%] >- <td> >- [% IF ( hold.lowestPriority ) %] >- <a >- class="hold-arrow" >- title="Remove lowest priority" >- data-op="cud-setLowestPriority" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg fa-rotate-90 icon-unset-lowest" aria-hidden="true"></i> >- </a> >- [% ELSE %] >- <a >- class="hold-arrow" >- title="Set lowest priority" >- data-op="cud-setLowestPriority" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- > >- <i class="fa fa-lg fa-rotate-90 icon-set-lowest" aria-hidden="true"></i> >- </a> >- [% END %] >- </td> >- [%- ELSE -%] >- <td></td> >- [%- END -%] >- [%- END -%] >- <td> >- <a class="cancel-hold" title="Cancel hold" data-borrowernumber="[% hold.borrowernumber | html %]" data-biblionumber="[% hold.biblionumber | html %]" data-id="[% hold.reserve_id | html %]" href="#"> >- <i class="fa fa-trash" aria-label="Cancel hold"></i> >- </a> >- </td> >- <td> >- [% IF Koha.Preference('SuspendHoldsIntranet') %] >- [% UNLESS ( hold.found ) %] >- [% IF ( hold.suspend ) %] >- <button class="btn btn-default btn-xs unsuspend-hold" data-reserve-id="[% hold.reserve_id | html %]" data-biblionumber="[% hold.biblionumber | html %]"> >- <i class="fa fa-play" aria-hidden="true"></i> Unsuspend >- </button> >- [% ELSE %] >- <button class="btn btn-default btn-xs suspend-hold" data-reserve-id="[% hold.reserve_id | html %]" data-biblionumber="[% hold.biblionumber | html %]"> >- <i class="fa fa-pause" aria-hidden="true"></i> Suspend >- </button> >- [% END %] >- >- [% IF Koha.Preference('AutoResumeSuspendedHolds') %] >- <label for="suspend_until_[% hold.reserve_id | html %]">[% IF ( hold.suspend ) %]on[% ELSE %]until[% END %]</label> >- <input >- type="text" >- name="suspend_until_[% hold.reserve_id | html %]" >- id="suspend_until_[% hold.reserve_id | html %]" >- size="10" >- value="[% hold.suspend_until | html %]" >- class="flatpickr" >- data-flatpickr-futuredate="true" >- /> >- [%- ELSE -%] >- <input type="hidden" name="suspend_until_[% hold.reserve_id | html %]" id="suspend_until_[% hold.reserve_id | html %]" value="" /> >- [%- END -%] >- [%- END -%] >- [%- END # IF SuspendHoldsIntranet -%] >- [%- IF ( hold.found ) -%] >- <a >- class="btn btn-default submit-form-link" >- href="#" >- id="revert_hold_[% hold.reserve_id | html %]" >- data-op="cud-move" >- data-where="down" >- data-first_priority="[% first_priority | html %]" >- data-last_priority="[% last_priority | html %]" >- data-prev_priority="0" >- data-next_priority="1" >- data-borrowernumber="[% hold.borrowernumber | html %]" >- data-biblionumber="[% hold.biblionumber | html %]" >- data-itemnumber="[% hold.itemnumber | html %]" >- data-reserve_id="[% hold.reserve_id | html %]" >- data-date="[% hold.date | html %]" >- data-action="request.pl" >- data-method="post" >- >[% IF hold.intransit %]Revert transit status[% ELSE %]Revert waiting status[% END %]</a >- > >- [%- END -%] >- </td> >- [% IF ( hold.intransit || hold.atdestination ) %] >- <td><input class="printholdslip" type="button" name="printholdslip" value="Print slip" data-reserve_id="[% hold.reserve_id | html %]" /></td> >- [% ELSE %] >- <td></td> >- [% END %] >- </tr> >- [% END %] >- </tbody> > </table> >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 c7783e21971..bd5039b1559 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >@@ -1198,7 +1198,7 @@ > [% END %] > [% END %] > [% END %] >- [% IF ( reserveloop ) %] >+ [% IF ( total_holds ) %] > <form id="existing_holds" name="T[% time | html %]" action="modrequest.pl" method="post" style="display:block"> > [% INCLUDE 'csrf-token.inc' %] > [% IF ( multi_hold ) %] >@@ -1216,10 +1216,6 @@ > > <h2>Existing holds</h2> > <div id="toolbar" class="btn-toolbar sticky"> >- <div class="btn-group"> >- <input type="hidden" name="op" value="cud-modifyall" /> >- <input type="submit" class="btn btn-primary" name="submit" value="Update hold(s)" /> >- </div> > <div class="btn-group"> > <button class="btn cancel_selected_holds" data-bulk="true"></button> > </div> >@@ -1238,137 +1234,75 @@ > </div> > <div class="page-section"> > [% FOREACH biblioloo IN biblioloop %] >- [% IF ( biblioloo.reserveloop ) %] >+ [% IF ( biblioloo.total_holds ) %] > <div class="hold_title" id="hold_title_[% biblioloo.biblionumber | html %]"> > [% IF ( multi_hold ) %] > <h3> > <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblioloo.biblionumber | uri %]"> [% biblioloo.title | html %] </a> >- <span class="badge bg-info-subtle"><span>[% biblioloo.reserveloop.size | html %] [% tn('Hold', 'Holds', biblioloo.reserveloop.size) | $raw %]</span></span> >+ <span class="badge bg-info-subtle"><span>[% biblioloo.total_holds | html %] [% tn('Hold', 'Holds', biblioloo.total_holds) | $raw %]</span></span> > </h3> > [% END %] > > [% IF Koha.Preference('HoldsSplitQueue') == 'branch' %] >- >- [% SET branchcodes = [] %] >- >- [% FOREACH h IN biblioloo.reserveloop %] >- [% branchcodes.push( h.branchcode ) %] >- [% END %] >- [% branchcodes = branchcodes.unique %] >- [% IF ( branchcodes.empty ) %] >- <div class="alert alert-info"> There are no holds on this title. </div> >+ [% IF biblioloo.hold_branches.empty %] >+ <div class="holds_by_library"> >+ <h4>Any library</h4> >+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] >+ </div> > [% 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 %] > <div class="holds_by_library"> > <h4>[% Branches.GetName( b ) | html %]</h4> > >- [% INCLUDE holds_table.inc holds=holds_by_branch %] >+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data=b %] > </div> >- [% 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 ) %] >- <div class="alert alert-info"> There are no holds on this title. </div> >+ [% IF biblioloo.hold_itemtypes.empty %] >+ <div class="holds_by_itemtype"> >+ <h4>Any item type</h4> >+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] >+ </div> > [% 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 %] > <div class="holds_by_itemtype"> >- [% IF i %] >+ [% IF i && i != 'any' %] > <h4>[% ItemTypes.GetDescription( i ) | html %]</h4> > [% ELSE %] > <h4>Any item type</h4> > [% 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 %] > </div> >- [% 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 ) %] >- <div class="alert alert-info"> There are no holds on this title. </div> >- [% ELSE %] >- >- [% FOREACH b IN branchcodes.sort %] >- <div class="holds_by_library"> >- <h4 class="library_holds">[% Branches.GetName( b ) | html %]</h4> >- [% SET holds_by_branch = [] %] >- [% FOREACH h IN biblioloo.reserveloop %] >- [% IF h.branchcode == b %] >- [% holds_by_branch.push( h ) %] >+ [% FOREACH b IN biblioloo.hold_branches %] >+ <div class="holds_by_library"> >+ <h4>[% Branches.GetName( b ) | html %]</h4> >+ [% FOREACH i IN biblioloo.hold_itemtypes %] >+ <div class="holds_by_itemtype"> >+ [% IF i && i != 'any' %] >+ <h4>[% ItemTypes.GetDescription( i ) | html %]</h4> >+ [% ELSE %] >+ <h4>Any item type</h4> > [% 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 %] >- <div class="holds_by_itemtype"> >- <h5 class="itemtype_holds"> >- [% IF i %] >- [% ItemTypes.GetDescription( i ) | html %] >- [% ELSE %] >- <span>Any item type</span> >- [% END %] >- </h5> >- >- [% 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 %] >- </div> >- <!-- /.holds_by_itemtype --> >- [% END %] >- </div> >- <!-- /.holds_by_library --> >- [% END # /FOREACH b %] >- [% END # /IF ( branchcodes.empty ) %] >- [% ELSE %] >- >- [% IF ( biblioloo.reserveloop.size ) %] >- [% INCLUDE holds_table.inc holds=biblioloo.reserveloop %] >- [% ELSE %] >- <div class="alert alert-info"> There are no holds on this title. </div> >+ [% SET split_data = b _ "_" _ i %] >+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data=split_data %] >+ </div> >+ [% END %] >+ </div> > [% END %] >+ [% ELSE %] >+ [% INCLUDE holds_table.inc biblio_id=biblioloo.biblionumber total_holds=biblioloo.total_holds split_data="any" %] > [% END # /IF HoldsSplitQueue %] > </div> > <!-- /hold_title --> >- [% END # /IF biblioloo.reserveloop %] >+ [% ELSE %] >+ <div class="note"> There are no holds on this title. </div> >+ [% END # /IF ( biblioloo.total_holds ) %] > [% END # FOREACH biblioloo %] > </div> > </form> >@@ -1424,6 +1358,9 @@ > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'calendar.inc' %] > [% INCLUDE 'select2.inc' %] >+ <script> >+ hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %]; >+ </script> > [% Asset.js("js/holds.js") | $raw %] > [% Asset.js("js/form-submit.js") | $raw %] > >@@ -1432,17 +1369,16 @@ > [% SET url_biblio_params = url_biblio_params _ "&multi_hold=1" %] > [% END %] > <script> >- $(document).ready(function () { >- hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %]; >- $("#patron_holds_table").kohaTable( >- { >- paging: false, >- bKohaColumnsUseNames: true, >- }, >- hold_table_settings >- ); >- }); > var biblionumbers = [[% biblionumbers.join(', ') | $raw %]]; >+ var hold_branches = {[% FOREACH biblio_info IN biblioloop %] [% biblio_info.biblionumber | html %]: ["[% biblio_info.hold_branches.join('", "') | $raw %]"], [% END %]}; >+ var hold_itemtypes = {[% FOREACH biblio_info IN biblioloop %] [% biblio_info.biblionumber | html %]: ["[% biblio_info.hold_itemtypes.join('", "') | $raw %]"], [% END %]}; >+ var HoldsSplitQueue = "[% Koha.Preference('HoldsSplitQueue') | html %]"; >+ var HoldsSplitQueueNumbering = "[% Koha.Preference('HoldsSplitQueueNumbering') | html %]"; >+ var SuspendHoldsIntranet = [% ( Koha.Preference('SuspendHoldsIntranet') ) ? 1 : 0 | html %]; >+ var AutoResumeSuspendedHolds = [% ( Koha.Preference('AutoResumeSuspendedHolds') ) ? 1 : 0 | html %]; >+ var AllowHoldDateInFuture = [% ( Koha.Preference('AllowHoldDateInFuture') ) ? 1 : 0 | html %]; >+ var HidePatronName = [% ( Koha.Preference('HidePatronName') ) ? 1 : 0 | html %]; >+ var CAN_user_reserveforothers_modify_holds_priority = [%CAN_user_reserveforothers_modify_holds_priority ? 1 : 0 | html %]; > var borrowernumber = "[% patron.borrowernumber | $raw %]"; > var patron_homebranch = "[% To.json( Branches.GetName( patron.branchcode ) ) | $raw %]"; > var override_items = {[% FOREACH biblio_info IN biblioloop %][% FOREACH itemloo IN biblio_info.itemloop %][% IF ( itemloo.override ) %] >@@ -1472,6 +1408,31 @@ > $.fn.select2.defaults.set("dropdownAutoWidth", true ); > > $(document).ready(function() { >+ biblionumbers.forEach(function(biblionumber) { >+ let branches = hold_branches[biblionumber]; >+ let itemtypes = hold_itemtypes[biblionumber]; >+ switch (HoldsSplitQueue) { >+ case 'branch': >+ branches.forEach(function(branch) { >+ load_patron_holds_table(biblionumber, {name: HoldsSplitQueue, value: branch}); >+ }); >+ break; >+ case 'itemtype': >+ itemtypes.forEach(function(itemtype) { >+ load_patron_holds_table(biblionumber, {name: HoldsSplitQueue, value: itemtype}); >+ }); >+ break; >+ case 'branch_itemtype': >+ branches.forEach(function(branch) { >+ itemtypes.forEach(function(itemtype) { >+ load_patron_holds_table(biblionumber, {name: HoldsSplitQueue, value: branch + '_' + itemtype}); >+ }); >+ }); >+ break; >+ default: >+ load_patron_holds_table(biblionumber, {name: 'any', value: 'any'}); >+ } >+ }); > $('#cancellation-reason-fieldset').hide(); > $('.rank-request').on('change', function() { > if ( $(".rank-request option:selected[value='del']").length ) { >@@ -1787,19 +1748,19 @@ > }); > > // Confirm cancellation of hold >- let cancel_link; >- $(".cancel-hold").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'); >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="reserve_id" value="' + reserve_id + '">'); >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="biblionumber" value="' + biblionumber + '">'); >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="op" value="cud-cancel">'); >- $('#cancelModal').modal('show'); >- return false; >- }); >+ // let cancel_link; >+ // $(".cancel-hold").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'); >+ // $("#cancel_modal_form #inputs").append('<input type="hidden" name="reserve_id" value="' + reserve_id + '">'); >+ // $("#cancel_modal_form #inputs").append('<input type="hidden" name="biblionumber" value="' + biblionumber + '">'); >+ // $("#cancel_modal_form #inputs").append('<input type="hidden" name="op" value="cud-cancel">'); >+ // $('#cancelModal').modal('show'); >+ // return false; >+ // }); > > [% UNLESS ( patron || patron.borrowernumber || borrowers || noitems || nobiblio ) %] > [% IF ( PatronAutoComplete ) %] >@@ -1821,118 +1782,6 @@ > localStorage.selectedHolds = []; > } > >- $('.holds_table .select_hold').each(function() { >- if(localStorage.selectedHolds.includes($(this).data('id'))) { >- $(this).prop('checked', true); >- } >- }); >- >- $('.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); >- }); >- >- $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >- >- $('.holds_table .select_hold_all').click(function() { >- var table = $(this).parents('.holds_table'); >- var count = $('.select_hold:checked', table).length; >- $('.select_hold', table).prop('checked', !count); >- $(this).prop('checked', !count); >- $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >- $('#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 => $(el).data('id')); >- }); >- >- $('.holds_table .select_hold').click(function() { >- var table = $(this).parents('.holds_table'); >- var count = $('.select_hold:not(:checked)', table).length; >- $('.select_hold_all', table).prop('checked', !count); >- $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >- $('#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 => $(el).data('id')); >- }); >- >- $('.cancel_selected_holds').click(function(e) { >- e.preventDefault(); >- if($('.holds_table .select_hold:checked').length) { >- cancel_link = $(this); >- $("#cancel_modal_form #inputs").empty(); >- biblionumbers.forEach( function(biblionumber){ >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="biblionumber" value="'+biblionumber+'">'); >- }); >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="op" value="cud-cancel_bulk">'); >- let hold_ids= $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(','); >- $("#cancel_modal_form #inputs").append('<input type="hidden" name="ids" value="' + hold_ids + '">'); >- delete localStorage.selectedHolds; >- $('#cancelModal').modal('show'); >- } >- return false; >- }); >- >- $(".hold-arrow").click(function(e) { >- e.preventDefault(); >- let arrowForm = $("#hold-actions-form").attr({ >- action: 'request.pl', >- method: 'post' >- }); >- let arrow_op = $("<input />").attr({ >- name: 'op', >- type: 'hidden', >- value: $(this).data('op') >- }); >- let arrow_where = $("<input />").attr({ >- name: 'where', >- type: 'hidden', >- value: $(this).data('where') >- }); >- let arrow_fp = $("<input />").attr({ >- name: 'first_priority', >- type: 'hidden', >- value: $(this).data('first_priority') >- }); >- let arrow_lp = $("<input />").attr({ >- name: 'last_priority', >- type: 'hidden', >- value: $(this).data('last_priority') >- }); >- let arrow_pp = $("<input />").attr({ >- name: 'prev_priority', >- type: 'hidden', >- value: $(this).data('prev_priority') >- }); >- let arrow_np = $("<input />").attr({ >- name: 'next_priority', >- type: 'hidden', >- value: $(this).data('next_priority') >- }); >- let arrow_bn = $("<input />").attr({ >- name: 'borrowernumber', >- type: 'hidden', >- value: $(this).data('borrowernumber') >- }); >- let arrow_bb = $("<input />").attr({ >- name: 'biblionumber', >- type: 'hidden', >- value: $(this).data('biblionumber') >- }); >- let arrow_ri = $("<input />").attr({ >- name: 'reserve_id', >- type: 'hidden', >- value: $(this).data('reserve_id') >- }); >- let arrow_date = $("<input />").attr({ >- name: 'date', >- type: 'hidden', >- value: $(this).data('date') >- }); >- arrowForm.append(arrow_op,arrow_where,arrow_fp,arrow_lp,arrow_pp,arrow_np,arrow_bn,arrow_bb,arrow_ri,arrow_date); >- $("#hold-actions-form").submit(); >- return true; >- }) > }); > </script> > <script> >@@ -1948,13 +1797,6 @@ > }); > </script> > [% END %] >- <script> >- $(".printholdslip").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; >- }); >- </script> > [% END %] > > [% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/js/holds.js b/koha-tmpl/intranet-tmpl/prog/js/holds.js >index 752e0f5b6ab..57bb92123f0 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/holds.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/holds.js >@@ -698,3 +698,854 @@ $(document).ready(function () { > return toggle_suspend(this, inputs); > }); > }); >+ >+async function load_patron_holds_table(biblio_id, split_data) { >+ const { name: split_name, value: split_value } = split_data; >+ const table_id = `#patron_holds_table_${biblio_id}_${split_value}`; >+ hold_table_settings.table = `patron_holds_table_${biblio_id}_${split_data.value}`; >+ 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; >+ let pageStart; >+ var holdsQueueTable = $(table_id).kohaTable( >+ { >+ ajax: { >+ url: url, >+ data: function (params) { >+ pageStart = params.start; >+ var query = { >+ _per_page: params.length, >+ _page: params.start / params.length + 1, >+ _order_by: "priority", >+ _match: "exact", >+ }; >+ return query; >+ }, >+ }, >+ embed: ["patron", "item", "item_group"], >+ 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 ( >+ '<input type="checkbox" class="select_hold" data-id="' + >+ data + >+ '"/>' >+ ); >+ }, >+ }, >+ { >+ data: "priority", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ let select = >+ '<select name="rank-request" class="rank-request" data-id="' + >+ row.hold_id; >+ if ( >+ CAN_user_reserveforothers_modify_holds_priority && >+ split_name == "any" >+ ) { >+ for (var i = 0; i < totalHoldsSelect; i++) { >+ let selected; >+ let value; >+ let desc; >+ if (data == i && row.status == "T") { >+ select += '" disabled="disabled">'; >+ selected = " selected='selected' "; >+ value = "T"; >+ desc = "In transit"; >+ } else if (data == i && row.status == "P") { >+ select += '" disabled="disabled">'; >+ selected = " selected='selected' "; >+ value = "P"; >+ desc = "In processing"; >+ } else if (data == i && row.status == "W") { >+ select += '" disabled="disabled">'; >+ selected = " selected='selected' "; >+ value = "W"; >+ desc = "Waiting"; >+ } else if (data == i && !row.status) { >+ select += '">'; >+ selected = " selected='selected' "; >+ value = data; >+ desc = data; >+ } else { >+ if (i != 0) { >+ select += '">'; >+ value = i; >+ desc = i; >+ } else { >+ select += '">'; >+ } >+ } >+ if (value) { >+ select += >+ '<option value="' + >+ value + >+ '"' + >+ selected + >+ ">" + >+ desc + >+ "</option>"; >+ } >+ } >+ } else { >+ if (row.status == "T") { >+ select += >+ '" disabled="disabled"><option value="T" selected="selected">In transit</option></select>'; >+ } else if (row.status == "P") { >+ select += >+ '" disabled="disabled"><option value="P" selected="selected">In processing</option></select>'; >+ } else if (row.status == "W") { >+ select += >+ '" disabled="disabled"><option value="W" selected="selected">Waiting</option></select>'; >+ } else { >+ if ( >+ HoldsSplitQueue !== "nothing" && >+ HoldsSplitQueueNumbering === "virtual" >+ ) { >+ let virtualPriority = >+ pageStart + meta.row + 1; >+ select += >+ '" disabled="disabled"><option value="' + >+ data + >+ '" selected="selected">' + >+ virtualPriority + >+ "</option></select>"; >+ } else { >+ select += >+ '" disabled="disabled"><option value="' + >+ data + >+ '" selected="selected">' + >+ data + >+ "</option></select>"; >+ } >+ } >+ } >+ select += "</select>"; >+ return select; >+ }, >+ }, >+ { >+ data: "", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ if (row.status) { >+ return null; >+ } >+ let buttons = >+ '<a class="hold-arrow move-hold" title="Move hold up" href="#" data-move-hold="up" data-priority="' + >+ row.priority + >+ '" reserve_id="' + >+ row.hold_id + >+ '"><i class="fa fa-lg icon-move-hold-up" aria-hidden="true"></i></a>'; >+ buttons += >+ '<a class="hold-arrow move-hold" title="Move hold to top" href="#" data-move-hold="top" data-priority="' + >+ row.priority + >+ '" reserve_id="' + >+ row.hold_id + >+ '"><i class="fa fa-lg icon-move-hold-top" aria-hidden="true"></i></a>'; >+ buttons += >+ '<a class="hold-arrow move-hold" title="Move hold to bottom" href="#" data-move-hold="bottom" data-priority="' + >+ row.priority + >+ '" reserve_id="' + >+ row.hold_id + >+ '"><i class="fa fa-lg icon-move-hold-bottom" aria-hidden="true"></i></a>'; >+ buttons += >+ '<a class="hold-arrow move-hold" title="Move hold down" href="#" data-move-hold="down" data-priority="' + >+ row.priority + >+ '" reserve_id="' + >+ row.hold_id + >+ '"><i class="fa fa-lg icon-move-hold-down" aria-hidden="true"></i></a>'; >+ return buttons; >+ }, >+ }, >+ { >+ data: "patron.cardnumber", >+ orderable: false, >+ 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 ( >+ '<a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=' + >+ row.patron.patron_id + >+ '">' + >+ data + >+ "</a>" >+ ); >+ } >+ }, >+ }, >+ { >+ data: "notes", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ return data; >+ }, >+ }, >+ { >+ data: "hold_date", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ if (AllowHoldDateInFuture) { >+ return ( >+ '<input type="text" class="holddate" value="' + >+ $date(data, { dateformat: "rfc3339" }) + >+ '" size="10" name="hold_date" data-id="' + >+ row.hold_id + >+ '" data-current-date="' + >+ data + >+ '"/>' >+ ); >+ } else { >+ return $date(data); >+ } >+ }, >+ }, >+ { >+ data: "expiration_date", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ return ( >+ '<input type="text" class="expirationdate" value="' + >+ $date(data, { dateformat: "rfc3339" }) + >+ '" size="10" name="expiration_date" data-id="' + >+ row.hold_id + >+ '" data-current-date="' + >+ data + >+ '"/>' >+ ); >+ }, >+ }, >+ { >+ data: "pickup_library_id", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ var branchSelect = >+ "<select priority=" + >+ row.priority + >+ ' class="hold_location_select" data-id="' + >+ row.hold_id + >+ '" reserve_id="' + >+ row.hold_id + >+ '" name="pick-location" data-pickup-location-source="hold">'; >+ var libraryname; >+ for (var i = 0; i < libraries.length; i++) { >+ var selectedbranch; >+ var setbranch; >+ if (libraries[i]._id == data) { >+ selectedbranch = " selected='selected' "; >+ setbranch = __(" (current) "); >+ libraryname = libraries[i]._str; >+ } else if (libraries[i].pickup_location == false) { >+ continue; >+ } else { >+ selectedbranch = ""; >+ setbranch = ""; >+ } >+ branchSelect += >+ '<option value="' + >+ libraries[i]._id.escapeHtml() + >+ '"' + >+ selectedbranch + >+ ">" + >+ libraries[i]._str.escapeHtml() + >+ setbranch + >+ "</option>"; >+ } >+ branchSelect += "</select>"; >+ if (row.status == "T") { >+ return __( >+ "Item being transferred to <strong>%s</strong>" >+ ).format(libraryname); >+ } else if (row.status == "P") { >+ return __( >+ "Item being processed at <strong>%s</strong>" >+ ).format(libraryname); >+ } else if (row.status == "W") { >+ return __( >+ "Item waiting at <strong>%s</strong> since %s" >+ ).format(libraryname, $date(row.waiting_date)); >+ } else { >+ return branchSelect; >+ } >+ }, >+ }, >+ { >+ data: "", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ if (row.item_id) { >+ return ( >+ '<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=' + >+ row.biblio_id + >+ "&itemnumber=" + >+ row.item_id + >+ '">' + >+ row.item.external_id + >+ "</a>" >+ ); >+ } else if (row.item_group_id) { >+ return __( >+ "Next available item from group <strong>%s</strong>" >+ ).format(row.item_group.description); >+ } else { >+ if (row.non_priority) { >+ return ( >+ "<em>" + >+ __("Next available") + >+ "</em><br/><i>" + >+ __("Non priority hold") + >+ "</i>" >+ ); >+ } else { >+ return "<em>" + __("Next available") + "</em>"; >+ } >+ } >+ }, >+ }, >+ { >+ data: "", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ if (row.item_id) { >+ return null; >+ } else { >+ if (row.lowest_priority) { >+ return ( >+ '<a class="hold-arrow toggle-lowest-priority" title="Remove lowest priority" href="#" data-op="cud-setLowestPriority" data-borrowernumber="' + >+ row.patron_id + >+ '" data-biblionumber="' + >+ biblio_id + >+ '" data-reserve_id="' + >+ row.hold_id + >+ '" data-date="' + >+ row.hold_date + >+ '"><i class="fa fa-lg fa-rotate-90 icon-unset-lowest" aria-hidden="true"></i></a>' >+ ); >+ } else { >+ return ( >+ '<a class="hold-arrow toggle-lowest-priority" title="Set lowest priority" href="#" data-op="cud-setLowestPriority" data-borrowernumber="' + >+ row.patron_id + >+ '" data-biblionumber="' + >+ biblio_id + >+ '" data-reserve_id="' + >+ row.hold_id + >+ '" data-date="' + >+ row.hold_date + >+ '"><i class="fa fa-lg fa-rotate-90 icon-set-lowest" aria-hidden="true"></i></a>' >+ ); >+ } >+ } >+ }, >+ }, >+ { >+ data: "hold_id", >+ orderable: false, >+ searchable: false, >+ render: function (data, type, row, meta) { >+ return ( >+ '<a class="cancel-hold" title="Cancel hold" reserve_id="' + >+ data + >+ '" href="#"><i class="fa fa-trash" aria-label="Cancel hold"></i></a>' >+ ); >+ }, >+ }, >+ { >+ 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 ( >+ '<a class="btn btn-default submit-form-link" href="#" id="revert_hold_' + >+ data + >+ '" data-op="cud-move" data-where="down" data-first_priority="1" data-last_priority="' + >+ totalHolds + >+ '" data-prev_priority="0" data-next_priority="1" data-borrowernumber="' + >+ row.patron_id + >+ '" data-biblionumber="' + >+ biblio_id + >+ '" data-itemnumber="' + >+ row.item_id + >+ '" data-reserve_id="' + >+ row.hold_id + >+ '" data-date="' + >+ row.hold_date + >+ '" data-action="request.pl" data-method="post">' + >+ link_value + >+ "</a>" >+ ); >+ } else { >+ let td = ""; >+ if (SuspendHoldsIntranet) { >+ td += >+ '<button class="btn btn-default btn-xs toggle-suspend" data-id="' + >+ data + >+ '" data-biblionumber="' + >+ biblio_id + >+ '" data-suspended="' + >+ row.suspended + >+ '">'; >+ if (row.suspended) { >+ td += >+ '<i class="fa fa-play" aria-hidden="true"></i> ' + >+ __("Resume") + >+ "</button>"; >+ } else { >+ td += >+ '<i class="fa fa-pause" aria-hidden="true"></i> ' + >+ __("Suspend") + >+ "</button>"; >+ } >+ if (AutoResumeSuspendedHolds) { >+ if (row.suspended) { >+ td += >+ '<label for="suspend_until_' + >+ data + >+ '">' + >+ __("Suspend on") + >+ " </label>"; >+ } else { >+ td += >+ '<label for="suspend_until_' + >+ data + >+ '">' + >+ __("Suspend until") + >+ " </label>"; >+ } >+ td += >+ '<input type="text" name="suspend_until_' + >+ data + >+ '" data-id="' + >+ data + >+ '" size="10" value="' + >+ $date(row.suspended_until, { >+ dateformat: "rfc3339", >+ }) + >+ '" class="suspenddate" data-flatpickr-futuredate="true" data-suspend-date="' + >+ row.suspended_until + >+ '" />'; >+ } >+ 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 ( >+ '<a class="btn btn-default btn-xs printholdslip" data-reserve_id="' + >+ data + >+ '">' + >+ __("Print slip") + >+ "</a>" >+ ); >+ } else { >+ return ""; >+ } >+ }, >+ }, >+ ], >+ }, >+ hold_table_settings >+ ); >+ $(table_id).on("draw.dt", function () { >+ // Remove the search box. Don't know why it isn't working in the table settings >+ $(this).parent().find(".pager .table_controls .dt-search").remove(); >+ $(this) >+ .parent() >+ .find(".pager .table_controls .dt-buttons .dt_button_clear_filter") >+ .remove(); >+ var MSG_CANCEL_SELECTED = _("Cancel selected (%s)"); >+ $(".cancel_selected_holds").html( >+ MSG_CANCEL_SELECTED.format( >+ $(".holds_table .select_hold:checked").length >+ ) >+ ); >+ $(".holds_table .select_hold").each(function () { >+ if ( >+ localStorage.selectedHolds && >+ localStorage.selectedHolds.includes($(this).data("id")) >+ ) { >+ $(this).prop("checked", true); >+ } >+ }); >+ $(".holds_table .select_hold_all").on("click", function () { >+ var table = $(this).parents(".holds_table"); >+ var count = $(".select_hold:checked", table).length; >+ $(".select_hold", table).prop("checked", !count); >+ $(this).prop("checked", !count); >+ $(this).parent().parent().toggleClass("selected"); >+ $(".cancel_selected_holds").html( >+ MSG_CANCEL_SELECTED.format( >+ $(".holds_table .select_hold:checked").length >+ ) >+ ); >+ localStorage.selectedHolds = $(".holds_table .select_hold:checked") >+ .toArray() >+ .map(el => $(el).data("id")); >+ }); >+ $(".holds_table .select_hold").on("click", function () { >+ 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_selected_holds").html( >+ MSG_CANCEL_SELECTED.format( >+ $(".holds_table .select_hold:checked").length >+ ) >+ ); >+ localStorage.selectedHolds = $(".holds_table .select_hold:checked") >+ .toArray() >+ .map(el => $(el).data("id")); >+ }); >+ $(".cancel-hold").on("click", function (e) { >+ e.preventDefault; >+ var res_id = $(this).attr("reserve_id"); >+ $(".select_hold").prop("checked", false); >+ $(".select_hold_all").prop("checked", false); >+ $(".cancel_selected_holds").html(MSG_CANCEL_SELECTED.format(0)); >+ $("#cancelModal") >+ .modal("show") >+ .find("#cancelModalConfirmBtn") >+ .attr("data-id", res_id); >+ delete localStorage.selectedHolds; >+ }); >+ $(".cancel_selected_holds").on("click", function (e) { >+ e.preventDefault(); >+ if ($(".holds_table .select_hold:checked").length) { >+ delete localStorage.selectedHolds; >+ $("#cancelModal").modal("show"); >+ } >+ return false; >+ }); >+ // Remove any previously attached handlers >+ $("#cancelModalConfirmBtn").off("click"); >+ // Attach the handler to the button >+ $("#cancelModalConfirmBtn").one("click", function (e) { >+ e.preventDefault(); >+ let hold_ids; >+ const hold_id = $("#cancelModal") >+ .modal("show") >+ .find("#cancelModalConfirmBtn") >+ .attr("data-id"); >+ let reason = $("#modal-cancellation-reason").val(); >+ if (hold_id) { >+ hold_ids = [hold_id]; >+ } else { >+ hold_ids = $(".holds_table .select_hold:checked") >+ .toArray() >+ .map(el => $(el).data("id")); >+ } >+ $("#cancelModal") >+ .find(".modal-footer #cancelModalConfirmBtn") >+ .before( >+ '<img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" style="padding-right: 10px;"/>' >+ ); >+ deleteHolds(hold_ids, reason); >+ $("#cancelModal") >+ .modal("show") >+ .find("#cancelModalConfirmBtn") >+ .attr("data-id", ""); >+ }); >+ async function deleteHolds(hold_ids, reason) { >+ for (const hold_id of hold_ids) { >+ await deleteHold(hold_id, reason); >+ $("#cancelModal") >+ .find(".modal-body") >+ .append( >+ '<p class="hold-cancelled">' + >+ __("Hold") + >+ " " + >+ hold_id + >+ " " + >+ __("cancelled") + >+ "</p>" >+ ); >+ await new Promise(resolve => setTimeout(resolve, 1000)); >+ } >+ >+ holdsQueueTable.api().ajax.reload(null, false); >+ setTimeout(() => { >+ $("#cancelModal").modal("hide"); >+ $("#cancelModal") >+ .find(".modal-footer #cancelModalConfirmBtn") >+ .prev("img") >+ .remove(); >+ $("#cancelModal") >+ .find(".modal-body") >+ .find(".hold-cancelled") >+ .remove(); >+ }); >+ } >+ async function deleteHold(hold_id, reason) { >+ try { >+ await $.ajax({ >+ method: "DELETE", >+ url: "/api/v1/holds/" + encodeURIComponent(hold_id), >+ data: JSON.stringify(reason), >+ }); >+ } catch (error) { >+ console.error("Error when deleting hold: " + hold_id); >+ } >+ } >+ $(".holddate, .expirationdate").flatpickr({ >+ onReady: function (selectedDates, dateStr, instance) { >+ $(instance.altInput) >+ .wrap("<span class='flatpickr_wrapper'></span>") >+ .after( >+ $("<a/>") >+ .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); >+ }, >+ error: function (jqXHR, textStatus, errorThrown) { >+ holdsQueueTable.api().ajax.reload(null, false); >+ }, >+ }); >+ } >+ }, >+ }); >+ $(".suspenddate").flatpickr({ >+ onReady: function (selectedDates, dateStr, instance) { >+ $(instance.altInput) >+ .wrap("<span class='flatpickr_wrapper'></span>") >+ .after( >+ $("<a/>") >+ .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").one("click", function (e) { >+ e.preventDefault(); >+ const hold_id = $(this).data("id"); >+ const suspended = $(this).attr("data-suspended"); >+ const input = $(`.suspenddate[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").on("change", function (e) { >+ e.preventDefault(); >+ const hold_id = $(this).data("id"); >+ let priority = e.target.value; >+ $.ajax({ >+ method: "PUT", >+ url: >+ "/api/v1/holds/" + >+ encodeURIComponent(hold_id) + >+ "/priority", >+ data: JSON.stringify(priority), >+ success: function (data) { >+ holdsQueueTable.api().ajax.reload(null, false); >+ }, >+ error: function (jqXHR, textStatus, errorThrown) { >+ alert( >+ "There was an error:" + textStatus + " " + errorThrown >+ ); >+ }, >+ }); >+ }); >+ $(".move-hold").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").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").on("change", function () { >+ $(this).prop("disabled", true); >+ var cur_select = $(this); >+ var res_id = $(this).attr("reserve_id"); >+ $(this).after( >+ '<div id="updating_reserveno' + >+ res_id + >+ '" class="waiting"><img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" /><span class="waiting_msg"></span></div>' >+ ); >+ 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() >+ ); >+ }, >+ }); >+ }); >+ $(".printholdslip").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; >+ }); >+ }); >+} >diff --git a/reserve/request.pl b/reserve/request.pl >index c5e354aa7d2..1165b4754b3 100755 >--- a/reserve/request.pl >+++ b/reserve/request.pl >@@ -603,9 +603,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( >@@ -618,61 +622,27 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) > )->unblessed > } > }; >- my @reserves = >- Koha::Holds->search( { biblionumber => $biblionumber }, { 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; >- >- 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... >@@ -695,16 +665,21 @@ 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; > > if (@reserveloop) { > $template->param( reserveloop => \@reserveloop ); > } > >+ $template->param( total_holds => $total_holds ); >+ > if ( $patron && $multi_hold ) { > > # Add the valid pickup locations >-- >2.39.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 23269
:
173770
|
176705
|
177151
| 181244 |
181247