Bugzilla – Attachment 101726 Details for
Bug 20936
Holds history for patrons in OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 20936: Add patron's hold history menu in OPAC
Bug-20936-Add-patrons-hold-history-menu-in-OPAC.patch (text/plain), 24.96 KB, created by
Agustín Moyano
on 2020-03-25 15:50:01 UTC
(
hide
)
Description:
Bug 20936: Add patron's hold history menu in OPAC
Filename:
MIME Type:
Creator:
Agustín Moyano
Created:
2020-03-25 15:50:01 UTC
Size:
24.96 KB
patch
obsolete
>From 52f3289530ee597ce786b9126be1c4ef77840311 Mon Sep 17 00:00:00 2001 >From: Agustin Moyano <agustinmoyano@theke.io> >Date: Tue, 10 Mar 2020 15:24:47 -0300 >Subject: [PATCH] Bug 20936: Add patron's hold history menu in OPAC > >This patch adds patron's hold history in OPAC. Right now, it only shows records from old_reserves table, but I'll wait till bug 20271 is pushed to show full history (old and new holds) > >To test: >1. apply this patch >2. Find a patron, place several holds and cancel or fulfill them >3. Go to patron's opac >CHECK => There is no 'your holds history' option in menu >4. In admin preferences enable OPACHoldsHistory >5. Go back to patron's opac >SUCCESS => There is a 'your holds history' menu option > => Holds history displays all holds canceled or fulfilled >6. Filter, order and change page >SUCCESS => All controls work as expected >7. Sign off. > >Table content is fetched from the api. If you see data, and you can order and filter then please sign off bug 24561. > >Date columns use $date function to transform dates strings from api (for example '2020-02-20') to 'dateformat' prefernce format ('02/20/20202'). If you change dateformat prefernce and see the changes reflected in date columns, please sign off bug 24455. > >Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> >--- > Koha/REST/V1/Patrons/Holds.pm | 80 ++++++ > api/v1/swagger/paths.json | 3 + > api/v1/swagger/paths/public_patrons.json | 188 ++++++++++++++ > .../bootstrap/en/includes/usermenu.inc | 24 +- > .../bootstrap/en/modules/opac-holdsrecord.tt | 234 ++++++++++++++++++ > opac/opac-holdsrecord.pl | 63 +++++ > 6 files changed, 585 insertions(+), 7 deletions(-) > create mode 100644 Koha/REST/V1/Patrons/Holds.pm > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-holdsrecord.tt > create mode 100755 opac/opac-holdsrecord.pl > >diff --git a/Koha/REST/V1/Patrons/Holds.pm b/Koha/REST/V1/Patrons/Holds.pm >new file mode 100644 >index 0000000000..5eb20cfa98 >--- /dev/null >+++ b/Koha/REST/V1/Patrons/Holds.pm >@@ -0,0 +1,80 @@ >+package Koha::REST::V1::Patrons::Holds; >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 3 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Mojo::Base 'Mojolicious::Controller'; >+ >+use Koha::Patrons; >+ >+use Scalar::Util qw(blessed); >+use Koha::DateUtils; >+use Try::Tiny; >+ >+=head1 NAME >+ >+Koha::REST::V1::Patrons::Holds >+ >+=head1 API >+ >+=head2 Methods >+ >+=head3 list >+ >+Mehtod that handles listing patron's holds >+ >+=cut >+ >+sub list { >+ my $c = shift->openapi->valid_input or return; >+ my $patron_id = $c->validation->param('patron_id'); >+ my $old = $c->validation->param('old'); >+ my $patron = Koha::Patrons->find($patron_id); >+ >+ unless ($patron) { >+ return $c->render( status => 404, openapi => { error => "Patron not found." } ); >+ } >+ >+ return try { >+ my $holds_set; >+ if($old) { >+ $holds_set = Koha::Old::Holds->new; >+ } else { >+ $holds_set = Koha::Holds->new; >+ } >+ >+ delete $c->validation->output->{old}; >+ #$c->stash_embed( {spec => $c->match->endpoint->pattern->defaults->{'openapi.op_spec'}} ); >+ my $holds = $c->objects->search( $holds_set ); >+ return $c->render( status => 200, openapi => $holds ); >+ } >+ catch { >+ if ( blessed $_ && $_->isa('Koha::Exceptions') ) { >+ return $c->render( >+ status => 500, >+ openapi => { error => "$_" } >+ ); >+ } >+ else { >+ return $c->render( >+ status => 500, >+ openapi => { error => "Something went wrong, check Koha logs for details." } >+ ); >+ } >+ }; >+} >+ >+1; >\ No newline at end of file >diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json >index 0631ecb53b..8147952f75 100644 >--- a/api/v1/swagger/paths.json >+++ b/api/v1/swagger/paths.json >@@ -104,6 +104,9 @@ > "/public/patrons/{patron_id}/guarantors/can_see_checkouts": { > "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1guarantors~1can_see_checkouts" > }, >+ "/public/patrons/{patron_id}/holds": { >+ "$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1holds" >+ }, > "/return_claims": { > "$ref": "paths/return_claims.json#/~1return_claims" > }, >diff --git a/api/v1/swagger/paths/public_patrons.json b/api/v1/swagger/paths/public_patrons.json >index 5db073c4eb..a62b8e6ede 100644 >--- a/api/v1/swagger/paths/public_patrons.json >+++ b/api/v1/swagger/paths/public_patrons.json >@@ -236,5 +236,193 @@ > "allow-owner": true > } > } >+ }, >+ "/public/patrons/{patron_id}/holds": { >+ "get": { >+ "x-mojo-to": "Patrons::Holds#list", >+ "operationId": "listPatronsOldHolds", >+ "tags": [ >+ "patron" >+ ], >+ "parameters": [ >+ { >+ "$ref": "../parameters.json#/patron_id_pp" >+ }, >+ { >+ "name": "hold_id", >+ "in": "query", >+ "description": "Internal reserve identifier", >+ "type": "integer" >+ }, >+ { >+ "name": "hold_date", >+ "in": "query", >+ "description": "Hold", >+ "type": "string", >+ "format": "date" >+ }, >+ { >+ "name": "biblio_id", >+ "in": "query", >+ "description": "Internal biblio identifier", >+ "type": "integer" >+ }, >+ { >+ "name": "pickup_library_id", >+ "in": "query", >+ "description": "Internal library identifier for the pickup library", >+ "type": "string" >+ }, >+ { >+ "name": "cancelation_date", >+ "in": "query", >+ "description": "The date the hold was cancelled", >+ "type": "string", >+ "format": "date" >+ }, >+ { >+ "name": "notes", >+ "in": "query", >+ "description": "Notes related to this hold", >+ "type": "string" >+ }, >+ { >+ "name": "priority", >+ "in": "query", >+ "description": "Where in the queue the patron sits", >+ "type": "integer" >+ }, >+ { >+ "name": "status", >+ "in": "query", >+ "description": "Found status", >+ "type": "string" >+ }, >+ { >+ "name": "timestamp", >+ "in": "query", >+ "description": "Time of latest update", >+ "type": "string" >+ }, >+ { >+ "name": "item_id", >+ "in": "query", >+ "description": "Internal item identifier", >+ "type": "integer" >+ }, >+ { >+ "name": "waiting_date", >+ "in": "query", >+ "description": "Date the item was marked as waiting for the patron", >+ "type": "string" >+ }, >+ { >+ "name": "expiration_date", >+ "in": "query", >+ "description": "Date the hold expires", >+ "type": "string" >+ }, >+ { >+ "name": "lowest_priority", >+ "in": "query", >+ "description": "Lowest priority", >+ "type": "boolean" >+ }, >+ { >+ "name": "suspended", >+ "in": "query", >+ "description": "Suspended", >+ "type": "boolean" >+ }, >+ { >+ "name": "suspended_until", >+ "in": "query", >+ "description": "Suspended until", >+ "type": "string" >+ }, >+ { >+ "name": "old", >+ "in": "query", >+ "description": "Fetch holds history", >+ "type": "boolean" >+ }, >+ { >+ "$ref": "../parameters.json#/match" >+ }, >+ { >+ "$ref": "../parameters.json#/order_by" >+ }, >+ { >+ "$ref": "../parameters.json#/page" >+ }, >+ { >+ "$ref": "../parameters.json#/per_page" >+ }, >+ { >+ "$ref": "../parameters.json#/q_param" >+ }, >+ { >+ "$ref": "../parameters.json#/q_body" >+ }, >+ { >+ "$ref": "../parameters.json#/q_header" >+ } >+ ], >+ "produces": [ >+ "application/json" >+ ], >+ "responses": { >+ "200": { >+ "description": "List of holds history", >+ "schema": { >+ "$ref": "../definitions.json#/holds" >+ } >+ }, >+ "400": { >+ "description": "Bad request", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "401": { >+ "description": "Authentication required", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "403": { >+ "description": "Access forbidden", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "404": { >+ "description": "Patron not found", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "500": { >+ "description": "Internal server error", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ }, >+ "503": { >+ "description": "Under maintenance", >+ "schema": { >+ "$ref": "../definitions.json#/error" >+ } >+ } >+ }, >+ "x-koha-authorization": { >+ "allow-owner": true >+ }, >+ "x-koha-embed": [ >+ "item", >+ "biblio", >+ "branch" >+ ] >+ } > } > } >\ No newline at end of file >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >index f1cdfa5ab2..7f06967756 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc >@@ -58,14 +58,24 @@ > <a href="/cgi-bin/koha/opac-search-history.pl">your search history</a></li> > [% END %] > >- [% IF ( opacreadinghistory ) %] >- [% IF ( readingrecview ) %] >- <li class="active"> >- [% ELSE %] >- <li> >+ [% IF opacreadinghistory || Koha.Preference('OPACHoldsHistory') == 1 %] >+ [% IF opacreadinghistory %] >+ [% IF ( readingrecview ) %] >+ <li class="active"> >+ [% ELSE %] >+ <li> >+ [% END %] >+ <a href="/cgi-bin/koha/opac-readingrecord.pl">your reading history</a></li> >+ [% END %] >+ [% IF Koha.Preference('OPACHoldsHistory') == 1 %] >+ [% IF ( holdsrecview ) %] >+ <li class="active"> >+ [% ELSE %] >+ <li> >+ [% END %] >+ <a href="/cgi-bin/koha/opac-holdsrecord.pl">your holds history</a></li> > [% END %] >- <a href="/cgi-bin/koha/opac-readingrecord.pl">your reading history</a></li> >- [% IF ( OPACPrivacy ) %] >+ [% IF ( OPACPrivacy || Koha.Preference('OPACHoldsPrivacy') == 1 ) %] > [% IF ( privacyview ) %] > <li class="active"> > [% ELSE %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-holdsrecord.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-holdsrecord.tt >new file mode 100644 >index 0000000000..b68dcbe190 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-holdsrecord.tt >@@ -0,0 +1,234 @@ >+[% USE raw %] >+[% USE Koha %] >+[% USE KohaDates %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title>[% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog › Your holds history</title> >+[% INCLUDE 'doc-head-close.inc' %] >+[% BLOCK cssinclude %] >+ <style> >+ .controls { >+ font-size: 75%; >+ } >+ >+ .controls .paginate_button { >+ font-family: 'FontAwesome'; >+ text-decoration: none; >+ } >+ >+ .controls .paginate_button:not(.disabled) { >+ cursor: pointer; >+ } >+ >+ .controls .paginate_button.disabled { >+ color: grey; >+ } >+ >+ .controls .previous:before { >+ content: "\f104"; >+ padding-right: .5em; >+ } >+ >+ .controls .next:after { >+ content: "\f105"; >+ padding-left: .5em; >+ } >+ </style> >+[% END %] >+</head> >+[% INCLUDE 'bodytag.inc' bodyid='opac-holdsrecord' %] >+[% INCLUDE 'masthead.inc' %] >+ >+<div class="main"> >+ <ul class="breadcrumb"> >+ <li><a href="/cgi-bin/koha/opac-main.pl">Home</a> <span class="divider">›</span></li> >+ <li><a href="/cgi-bin/koha/opac-user.pl">[% INCLUDE 'patron-title.inc' patron = logged_in_user %]</a> <span class="divider">›</span></li> >+ <li><a href="#">Your holds history</a></li> >+ </ul> >+ >+ <div class="container-fluid"> >+ <div class="row-fluid"> >+ <div class="span2"> >+ <div id="navigation"> >+ [% INCLUDE 'navigation.inc' IsPatronPage=1 %] >+ </div> >+ </div> >+ <div class="span10"> >+ <div id="userholdsrecord"> >+ <h3>Holds history</h3> >+ >+ [% IF READING_RECORD.size == 0 %] >+ You have never placed a hold from this library. >+ [% ELSE %] >+ <div id="opac-user-holdsrec"> >+ <div id="tabs-container" style="overflow:auto"> >+ <div class="controls"> >+ <div class="span3 info"> >+ >+ </div> >+ <div class="span2"> >+ <input type="text" class="search" placeholder="search" autofocus/> >+ </div> >+ <div class="span1"> >+ <a class="paginate_button previous disabled" aria-controls="holdsrec" data-dt-idx="1">Previous</a> >+ </div> >+ <div class="span1"> >+ <a class="paginate_button next disabled" aria-controls="holdsrec" data-dt-idx="1">Next</a> >+ </div> >+ </div> >+ <table id="holdsrec" class="table table-bordered table-striped"> >+ <thead> >+ <tr> >+ <th class="anti-the">Title</th> >+ <th class="">Author</th> >+ <th class="">Barcode</th> >+ <th class="">Library</th> >+ <th class="title-string">Hold date</th> >+ <th class="title-string">Expiration date</th> >+ <th class="title-string">Waiting date</th> >+ <th class="title-string">Cancellation date</th> >+ <th>Status</th> >+ </tr> >+ </thead> >+ <tbody> >+ >+ </tbody> >+ </table> >+ <div class="controls"> >+ <div class="span3 info"> >+ >+ </div> >+ <div class="span1"> >+ <a class="paginate_button previous disabled" aria-controls="holdsrec" data-dt-idx="1">Previous</a> >+ </div> >+ <div class="span1"> >+ <a class="paginate_button next disabled" aria-controls="holdsrec" data-dt-idx="1">Next</a> >+ </div> >+ </div> >+ </div> <!-- / .tabs-container --> >+ </div> <!-- / .opac-user-readingrec --> >+ [% END # / IF READING_RECORD.size %] >+ </div> <!-- / .userreadingrecord --> >+ </div> <!-- / .span10 --> >+ </div> <!-- / .row-fluid --> >+ </div> <!-- / .container-fluid --> >+</div> <!-- / .main --> >+ >+[% INCLUDE 'opac-bottom.inc' %] >+[% BLOCK jsinclude %] >+[% INCLUDE 'datatables.inc' %] >+[% INCLUDE 'js-date-format.inc' %] >+<script> >+ $(document).ready(function(){ >+ [% IF ( GoogleJackets ) %]KOHA.Google.GetCoverFromIsbn();[% END %] >+ $('#order').change(function() { >+ $('#sortform').submit(); >+ }); >+ >+ var table = $("#holdsrec").api({ >+ "ajax": { >+ "url": "/api/v1/public/patrons/[% borrowernumber | html %]/holds?old=true" >+ }, >+ "embed": [ >+ "item", >+ "biblio", >+ "branch" >+ ], >+ 'order': [[ 4, "desc" ]], >+ "columnDefs": [ >+ { "targets": [ "nosort" ],"sortable": false,"searchable": false }, >+ { "type": "anti-the", "targets" : [ "anti-the" ] }, >+ { "type": "title-string", "targets" : [ "title-string" ] } >+ ], >+ "columns": [ >+ { >+ "data": "biblio.title", >+ "render": function(data, type, row, meta) { >+ if (type != 'display') return data; >+ return '<a href="opac-detail.pl?biblionumber='+row.biblio_id+'">'+data+'</a>'; >+ } >+ }, >+ {"data": "biblio.author"}, >+ { >+ "data": "item.external_id", >+ "render": function(data, type, row, meta) { >+ if(data) return data; >+ return ''; >+ } >+ }, >+ {"data": "branch.name"}, >+ {"data": "hold_date"}, >+ { >+ "data": "expiration_date", >+ "render": function(data, type, row, meta) { >+ return (data&&$date(data))||''; >+ } >+ }, >+ { >+ "data": "waiting_date", >+ "render": function(data, type, row, meta) { >+ return (data&&$date(data))||''; >+ } >+ }, >+ { >+ "data": "cancelation_date", >+ "render": function(data, type, row, meta) { >+ return (data&&$date(data))||''; >+ } >+ }, >+ { >+ "data": "status", >+ "render": function(data, type, row, meta) { >+ if (type != 'display') return data; >+ if(row.cancelation_date) return 'Canceled'; >+ if (data == 'F') return 'Fulfilled'; >+ if (data == 'W') return 'Waiting'; >+ if (data == 'T') return 'In Transit'; >+ return 'Pending'; >+ } >+ } >+ ] >+ }).DataTable(); >+ >+ table.on('draw.dt', function(event, settings) { >+ var page_info = table.page.info(); >+ $(".controls .info").html(_("Showing page %s of %s").format(page_info.page+1, page_info.pages)); >+ if(!page_info.page) { >+ $('.controls .previous').addClass('disabled'); >+ } else { >+ $('.controls .previous').removeClass('disabled'); >+ } >+ if(page_info.pages-1===page_info.page) { >+ $('.controls .next').addClass('disabled'); >+ } else { >+ $('.controls .next').removeClass('disabled'); >+ } >+ }) >+ >+ $('.controls .previous').click(function() { >+ table.page('previous').draw('page'); >+ }); >+ >+ $('.controls .next').click(function() { >+ table.page('next').draw('page'); >+ }); >+ function debounce(func, wait, immediate) { >+ var timeout; >+ return function() { >+ var context = this, args = arguments; >+ var later = function() { >+ timeout = null; >+ if (!immediate) func.apply(context, args); >+ }; >+ var callNow = immediate && !timeout; >+ clearTimeout(timeout); >+ timeout = setTimeout(later, wait); >+ if (callNow) func.apply(context, args); >+ }; >+ }; >+ >+ $(".controls .search").keypress(debounce(function(event) { >+ table.search(this.value).draw(); >+ }, 300)); >+ }); >+</script> >+[% END %] >diff --git a/opac/opac-holdsrecord.pl b/opac/opac-holdsrecord.pl >new file mode 100755 >index 0000000000..c9a2d83ded >--- /dev/null >+++ b/opac/opac-holdsrecord.pl >@@ -0,0 +1,63 @@ >+#!/usr/bin/perl >+ >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <http://www.gnu.org/licenses>. >+ >+ >+use Modern::Perl; >+ >+use CGI qw ( -utf8 ); >+ >+use C4::Auth; >+use C4::Koha; >+use C4::Biblio; >+use C4::Circulation; >+use C4::Members; >+use Koha::DateUtils; >+use MARC::Record; >+ >+use C4::Output; >+use C4::Charset qw(StripNonXmlChars); >+use Koha::Patrons; >+ >+use Koha::ItemTypes; >+use Koha::Ratings; >+ >+my $query = new CGI; >+ >+# if opacreadinghistory is disabled, leave immediately >+if ( ! C4::Context->preference('OPACHoldsHistory') ) { >+ print $query->redirect("/cgi-bin/koha/errors/404.pl"); >+ exit; >+} >+ >+my ( $template, $borrowernumber, $cookie ) = get_template_and_user( >+ { >+ template_name => "opac-holdsrecord.tt", >+ query => $query, >+ type => "opac", >+ debug => 1, >+ } >+); >+ >+my $borr = Koha::Patrons->find( $borrowernumber )->unblessed; >+ >+$template->param(%{$borr}); >+ >+$template->param( >+ holdsrecview => 1, >+); >+ >+output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 }; >-- >2.25.0
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 20936
:
98629
|
98630
|
100115
|
100116
|
100117
|
100205
|
100206
|
100207
|
100471
|
100472
|
100473
|
100474
|
101724
|
101725
|
101726
|
101727
|
101772
|
101915
|
102166
|
103948
|
103949
|
103950
|
103951
|
104040
|
104041
|
104466
|
104467
|
105131
|
112667
|
112668
|
112730
|
112731
|
112732
|
112733
|
113446
|
113447
|
113448
|
113449