Bugzilla – Attachment 173770 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
0001-Bug-23269-use-REST-API-for-patron_holds_table.patch (text/plain), 84.09 KB, created by
Johanna Räisä
on 2024-10-31 11:02:16 UTC
(
hide
)
Description:
Bug 23269: use REST API for patron_holds_table
Filename:
MIME Type:
Creator:
Johanna Räisä
Created:
2024-10-31 11:02:16 UTC
Size:
84.09 KB
patch
obsolete
>From 59f69b0e3a7e2f1b57d2c44620a631bac5720854 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 >--- > api/v1/swagger/definitions/hold.yaml | 5 + > api/v1/swagger/paths/holds.yaml | 2 + > .../prog/en/includes/holds_table.inc | 258 +------- > .../prog/en/modules/reserve/request.tt | 306 +++------ > koha-tmpl/intranet-tmpl/prog/js/holds.js | 615 ++++++++++++++++++ > reserve/request.pl | 82 +-- > 6 files changed, 734 insertions(+), 534 deletions(-) > >diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml >index 2ba3367e04..b8c6754396 100644 >--- a/api/v1/swagger/definitions/hold.yaml >+++ b/api/v1/swagger/definitions/hold.yaml >@@ -123,6 +123,11 @@ properties: > - object > - "null" > description: The item >+ patron: >+ type: >+ - object >+ - "null" >+ description: The patron > pickup_library: > type: > - object >diff --git a/api/v1/swagger/paths/holds.yaml b/api/v1/swagger/paths/holds.yaml >index 820f9dde9a..4967061add 100644 >--- a/api/v1/swagger/paths/holds.yaml >+++ b/api/v1/swagger/paths/holds.yaml >@@ -104,6 +104,8 @@ > - cancellation_requested > - biblio > - pickup_library >+ - patron >+ - item > collectionFormat: csv > produces: > - 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 dd99815cb2..22633b70f8 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >@@ -5,273 +5,23 @@ > [% #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 -%] >- [% IF hold.suspend %] >- <tr class="suspend"> >- [% ELSE %] >- <tr> >- [% END %] >- <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> >- [% ELSE %] >- <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> >- [% 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> >- [% END %] >- </a> >- </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="request.pl?op=cancel&borrowernumber=[% hold.borrowernumber | html %]&biblionumber=[% hold.biblionumber | html %]&reserve_id=[% hold.reserve_id | html %]&date=[% hold.date | html %]"> >- <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 8d1c1fddc3..d5aa5dff4d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >@@ -1229,7 +1229,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 ) %] >@@ -1249,7 +1249,7 @@ > <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)" /> >+ <input type="submit" class="btn btn-primary update_selected_holds" name="submit" value="Update hold(s)" /> > </div> > <div class="btn-group"> > <button class="btn cancel_selected_holds" data-bulk="true"></button> >@@ -1270,147 +1270,81 @@ > <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 text-bg-info"><span>[% biblioloo.reserveloop.size | html %] [% tn('Hold', 'Holds', biblioloo.reserveloop.size) | $raw %]</span></span> >+ </a> <span class="badge text-bg-info"><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="note"> >- There are no holds on this title. >+ >+ [% 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="note"> >- There are no holds on this title. >+ [% 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="note"> >- 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="note"> >- 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 %] >+ </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> <!-- /#existing_holds --> >@@ -1483,6 +1417,9 @@ > [% INCLUDE 'calendar.inc' %] > [% INCLUDE 'columns_settings.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%] > >@@ -1492,14 +1429,16 @@ > [% END %] > > <script> >- $(document).ready(function () { >- hold_table_settings = [% TablesSettings.GetTableSettings( 'circ', 'holds', 'patron_holds_table', 'json' ) | $raw %]; >- KohaTable("patron_holds_table", { >- "bPaginate":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 ) %] >@@ -1529,6 +1468,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 ) { >@@ -1879,118 +1843,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> > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/holds.js b/koha-tmpl/intranet-tmpl/prog/js/holds.js >index d18d394787..b0708e22b6 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/holds.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/holds.js >@@ -498,3 +498,618 @@ $(document).ready(function() { > }); > > }); >+ >+function fetch_libraries() { >+ return $.ajax({ >+ method: "GET", >+ url: "/api/v1/libraries", >+ data: {_per_page: -1}, >+ success: function(data, textStatus, request){ >+ }, >+ error: function( jqXHR, textStatus, errorThrown) { >+ console.log(errorThrown); >+ }, >+ }); >+} >+ >+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; >+ const multiChange = {}; >+ let pageStart; >+ const libraries = await fetch_libraries(); >+ 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'], >+ 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.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)+'" 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)+'" 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].library_id == data ){ >+ selectedbranch = " selected='selected' "; >+ setbranch = __(" (current) "); >+ libraryname = libraries[i].name; >+ } else if ( libraries[i].pickup_location == false ) { >+ continue; >+ } else{ >+ selectedbranch = ''; >+ setbranch = ''; >+ } >+ branchSelect += '<option value="'+ libraries[i].library_id.escapeHtml() +'"'+selectedbranch+'>'+libraries[i].name.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: "item_id", >+ orderable: false, >+ searchable: false, >+ render: function(data, type, row, meta) { >+ let currentCell = $(table_id).DataTable().cells({"row":meta.row, "column":meta.col}).nodes(0); >+ if (data) { >+ $.ajax({ >+ method: "GET", >+ url: "/api/v1/items/"+data, >+ success: function(res){ >+ $(currentCell).html('<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber='+biblio_id+'&itemnumber='+data+'">'+res.external_id+'</a>'); >+ } >+ }); >+ return '<img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" /><span class="waiting_msg"></span></div>'; >+ } else if (row.item_group_id) { >+ $.ajax({ >+ method: "GET", >+ url: "/api/v1/biblios/"+biblio_id+"/item_groups/"+row.item_group_id, >+ success: function(res){ >+ $(currentCell).html(__("Next available item group <strong>%s</strong> item").format( res.description )); >+ } >+ }); >+ return '<img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" /><span class="waiting_msg"></span></div>'; >+ } 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" 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" 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+'"><i class="fa fa-trash text-danger" 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)+'" class="suspenddate" data-flatpickr-futuredate="true" data-current-date="'+row.suspended_until+'" />'; >+ } >+ return td; >+ } else { >+ return null; >+ } >+ } >+ } >+ }, >+ { >+ data: "hold_id", >+ orderable: false, >+ searchable: false, >+ render: function(data, type, row, meta) { >+ return '<a class="btn btn-default btn-xs printholdslip" data-reserve_id="'+data+'">'+__("Print slip")+'</a>'; >+ } >+ }, >+ ] >+ },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(".dataTables_filter, .dt_button_clear_filter").remove(); >+ let multiselect = false; >+ 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')); >+ multiselect = true; >+ >+ }); >+ $('.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')); >+ multiselect = true; >+ }); >+ $(".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; >+ }); >+ $("#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({ >+ dateFormat: flatpickr_dateformat_string, >+ 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'); >+ const date = new Date(selectedDates); >+ const year = date.getFullYear(); >+ const month = String(date.getMonth() + 1).padStart(2, '0'); >+ const day = String(date.getDate()).padStart(2, '0'); >+ let newdate; >+ if (year && month && day) { >+ newdate = [year, month, day].join('-'); >+ } >+ multiChange[hold_id] = fieldname == "hold_date" ? {"hold_date": newdate} : { "expiration_date": dateStr }; >+ let req = fieldname == "hold_date" ? { "hold_date": newdate } : { "expiration_date": newdate }; >+ if (current_date != newdate && !multiselect) { >+ $.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({ >+ dateFormat: flatpickr_dateformat_string, >+ onChange: function (selectedDates, dateStr, instance){ >+ let hold_id = $(instance.input).data('id'); >+ let current_date = $(instance.input).attr('data-current-date'); >+ const date = new Date(selectedDates); >+ const year = date.getFullYear(); >+ const month = String(date.getMonth() + 1).padStart(2, '0'); >+ const day = String(date.getDate()).padStart(2, '0'); >+ const newdate = [year, month, day].join('-'); >+ const method = dateStr ? 'POST' : 'DELETE'; >+ multiChange[hold_id] = {suspended_until: dateStr}; >+ if (current_date != newdate && !multiselect) { >+ $.ajax({ >+ method: method, >+ url: '/api/v1/holds/' + encodeURIComponent(hold_id) +'/suspension', >+ contentType: 'application/json', >+ data: JSON.stringify({ "end_date": newdate }), >+ success: function( data ){ holdsQueueTable.api().ajax.reload(null, false); }, >+ error: function( jqXHR, textStatus, errorThrown) { >+ holdsQueueTable.api().ajax.reload(null, false); >+ }, >+ }); >+ } >+ } >+ }); >+ $(".toggle-suspend").one("click",function(e) { >+ e.preventDefault(); >+ const hold_id = $(this).data('id'); >+ const suspended = $(this).attr('data-suspended'); >+ const method = suspended == 'true' ? 'DELETE' : 'POST'; >+ if (!multiselect) { >+ $.ajax({ >+ method: method, >+ url: '/api/v1/holds/' + encodeURIComponent(hold_id) +'/suspension', >+ 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; >+ multiChange[hold_id] = {priority: priority}; >+ if (!multiselect) { >+ $.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; >+ } >+ if (!multiselect) { >+ $.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'); >+ multiChange[res_id] = {pickup_library_id: $(this).val()}; >+ if (!multiselect) { >+ $(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; >+ }); >+ }); >+ >+ $('.update_selected_holds').on('click', function (e) { >+ const data = [] >+ holdsQueueTable.api().rows('.selected').every(function (index, element) { >+ data.push(this.data()); >+ }); >+ fieldNames = { >+ hold_id: 'reserve_id', >+ hold_date: 'reservedate', >+ pickup_library_id: 'pickup', >+ item_id: 'itemnumber', >+ expiration_date: 'expirationdate', >+ suspended_until: 'suspend_until', >+ priority: 'rank-request', >+ }; >+ let biblionumberInput = $("<input>").attr({ >+ type: "hidden", >+ name: "biblionumber", >+ value: biblio_id >+ }); >+ $("#existing_holds").append(biblionumberInput); >+ data.forEach((row) => { >+ Object.keys(row).forEach((key) => { >+ let update = multiChange[row.hold_id]; >+ if (update && update[key]) { >+ row[key] = update[key]; >+ } >+ if (fieldNames[key] && row[key] != null) { >+ let input = $("<input>").attr({ >+ type: "hidden", >+ name: fieldNames[key], >+ value: row[key] >+ }); >+ $("#existing_holds").append(input); >+ } >+ }); >+ }); >+ }); >+} >diff --git a/reserve/request.pl b/reserve/request.pl >index b19fa3b868..7a9b8bd8af 100755 >--- a/reserve/request.pl >+++ b/reserve/request.pl >@@ -595,6 +595,9 @@ 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'); >@@ -610,61 +613,29 @@ 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... >@@ -692,11 +663,16 @@ if ( ( $findborrower && $borrowernumber_hold || $findclub && $club_hold ) > $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 > my @pickup_locations = $biblio->pickup_locations({ patron => $patron })->as_list; >-- >2.34.1 >
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