From f90a7361c72c0435e660760c4469bcb841b22625 Mon Sep 17 00:00:00 2001 From: Agustin Moyano Date: Sun, 9 Feb 2020 17:53:54 -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 --- Koha/REST/V1/Patrons/Holds.pm | 80 ++++++ Koha/Schema/Result/Biblio.pm | 20 ++ Koha/Schema/Result/Item.pm | 27 ++ Koha/Schema/Result/OldReserve.pm | 79 ++++++ 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 | 239 ++++++++++++++++++ opac/opac-holdsrecord.pl | 64 +++++ 9 files changed, 717 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 . + +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/Koha/Schema/Result/Biblio.pm b/Koha/Schema/Result/Biblio.pm index 7c66b1dc03..b6ef833bec 100644 --- a/Koha/Schema/Result/Biblio.pm +++ b/Koha/Schema/Result/Biblio.pm @@ -428,4 +428,24 @@ __PACKAGE__->add_columns( "+serial" => { is_boolean => 1 } ); +=head2 koha_objects_class + +Returns related Koha::Objects class name + +=cut + +sub koha_objects_class { + 'Koha::Biblios'; +} + +=head2 koha_object_class + +Returns related Koha::Object class name + +=cut + +sub koha_object_class { + 'Koha::Biblio'; +} + 1; diff --git a/Koha/Schema/Result/Item.pm b/Koha/Schema/Result/Item.pm index 58cddf4502..798cd08a2f 100644 --- a/Koha/Schema/Result/Item.pm +++ b/Koha/Schema/Result/Item.pm @@ -759,6 +759,13 @@ __PACKAGE__->belongs_to( ); use C4::Context; + +=head2 effective_itemtype + +Returns item's effective itemtype + +=cut + sub effective_itemtype { my ( $self ) = @_; @@ -772,4 +779,24 @@ sub effective_itemtype { } } +=head2 koha_objects_class + +Returns related Koha::Objects class name + +=cut + +sub koha_objects_class { + 'Koha::Items'; +} + +=head2 koha_object_class + +Returns related Koha::Object class name + +=cut + +sub koha_object_class { + 'Koha::Item'; +} + 1; diff --git a/Koha/Schema/Result/OldReserve.pm b/Koha/Schema/Result/OldReserve.pm index de9fc6cb27..42d98fcb43 100644 --- a/Koha/Schema/Result/OldReserve.pm +++ b/Koha/Schema/Result/OldReserve.pm @@ -300,9 +300,88 @@ __PACKAGE__->belongs_to( # Created by DBIx::Class::Schema::Loader v0.07046 @ 2019-06-17 07:24:39 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZgGAW7ODBby3hGNJ41eeMA +=head2 item + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "item", + "Koha::Schema::Result::Item", + { itemnumber => "itemnumber" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "CASCADE", + }, +); + +=head2 branch + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "branch", + "Koha::Schema::Result::Branch", + { branchcode => "branchcode" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "CASCADE", + }, +); + +=head2 biblio + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "biblio", + "Koha::Schema::Result::Biblio", + { biblionumber => "biblionumber" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "CASCADE", + }, +); + +__PACKAGE__->add_columns( + '+item_level_hold' => { is_boolean => 1 }, + '+lowestPriority' => { is_boolean => 1 }, + '+suspend' => { is_boolean => 1 } +); + +=head2 koha_object_class + +Returns related Koha::Object class name + +=cut + sub koha_object_class { 'Koha::Old::Hold'; } + +=head2 koha_objects_class + +Returns related Koha::Objects class name + +=cut + sub koha_objects_class { 'Koha::Old::Holds'; } 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 @@ your search history [% END %] - [% IF ( opacreadinghistory ) %] - [% IF ( readingrecview ) %] -
  • - [% ELSE %] -
  • + [% IF opacreadinghistory || Koha.Preference('OPACHoldsHistory') == 1 %] + [% IF opacreadinghistory %] + [% IF ( readingrecview ) %] +
  • + [% ELSE %] +
  • + [% END %] + your reading history
  • + [% END %] + [% IF Koha.Preference('OPACHoldsHistory') == 1 %] + [% IF ( holdsrecview ) %] +
  • + [% ELSE %] +
  • + [% END %] + your holds history
  • [% END %] - your reading history - [% IF ( OPACPrivacy ) %] + [% IF ( OPACPrivacy || Koha.Preference('OPACHoldsPrivacy') == 1 ) %] [% IF ( privacyview ) %]
  • [% 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..91888b8e59 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-holdsrecord.tt @@ -0,0 +1,239 @@ +[% USE raw %] +[% USE Koha %] +[% USE KohaDates %] +[% INCLUDE 'doc-head-open.inc' %] +[% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog › Your holds history +[% INCLUDE 'doc-head-close.inc' %] +[% BLOCK cssinclude %] + +[% END %] + +[% INCLUDE 'bodytag.inc' bodyid='opac-holdsrecord' %] +[% INCLUDE 'masthead.inc' %] + +
    + + +
    +
    +
    + +
    +
    +
    +

    Holds history

    + + [% IF READING_RECORD.size == 0 %] + You have never placed a hold from this library. + [% ELSE %] +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + +
    TitleAuthorBarcodeLibraryHold dateExpiration dateWaiting dateCancellation dateStatus
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + [% END # / IF READING_RECORD.size %] +
    +
    +
    +
    +
    + +[% INCLUDE 'opac-bottom.inc' %] +[% BLOCK jsinclude %] +[% INCLUDE 'datatables.inc' %] +[% INCLUDE 'js-date-format.inc' %] + +[% END %] diff --git a/opac/opac-holdsrecord.pl b/opac/opac-holdsrecord.pl new file mode 100755 index 0000000000..2e4d75c0df --- /dev/null +++ b/opac/opac-holdsrecord.pl @@ -0,0 +1,64 @@ +#!/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 . + + +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", + authnotrequired => 0, + 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