From fdfa0a58ae3669c1a9dd9a6c9ac486ccdcd0f401 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Thu, 6 Apr 2017 10:16:58 +0000 Subject: [PATCH] Bug 18407: REST API: Add expanded checkout information GET /api/v1/checkouts/expanded?borrowernumber=1337 This endpoint includes basic checkout information as well as additionally information on renewability. To test: 1. prove t/db_dependent/api/v1/checkoutsexpanded.t --- Koha/REST/V1/Checkout.pm | 65 +++++++-- api/v1/swagger/definitions.json | 3 + api/v1/swagger/definitions/checkoutexpanded.json | 16 +++ api/v1/swagger/paths.json | 3 + api/v1/swagger/paths/checkouts.json | 57 ++++++++ t/db_dependent/api/v1/checkoutsexpanded.t | 174 +++++++++++++++++++++++ 6 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 api/v1/swagger/definitions/checkoutexpanded.json create mode 100644 t/db_dependent/api/v1/checkoutsexpanded.t diff --git a/Koha/REST/V1/Checkout.pm b/Koha/REST/V1/Checkout.pm index f99537b..c6a2749 100644 --- a/Koha/REST/V1/Checkout.pm +++ b/Koha/REST/V1/Checkout.pm @@ -19,7 +19,6 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; use Mojo::JSON; - use C4::Auth qw( haspermission ); use C4::Context; use C4::Circulation; @@ -105,14 +104,9 @@ sub renewability { my $borrowernumber = $checkout->borrowernumber; my $itemnumber = $checkout->itemnumber; - my $OpacRenewalAllowed; - if ($user->borrowernumber == $borrowernumber) { - $OpacRenewalAllowed = C4::Context->preference('OpacRenewalAllowed'); - } - - unless ($user && ($OpacRenewalAllowed - || haspermission($user->userid, { circulate => "circulate_remaining_permissions" }))) { - return $c->render(status => 403, openapi => {error => "You don't have the required permission"}); + unless (_opac_renewal_allowed($user, $borrowernumber)) { + return $c->render(status => 403, openapi => { + error => "You don't have the required permission"}); } my ($can_renew, $error) = C4::Circulation::CanBookBeRenewed( @@ -160,4 +154,57 @@ sub gethistory { return $c->render( status => 200, openapi => $checkout ); } +sub expanded { + my $c = shift->openapi->valid_input or return; + + my $borrowernumber = $c->validation->param('borrowernumber'); + my $borrower = C4::Members::GetMember( borrowernumber => $borrowernumber ) + or return; + my $user = $c->stash('koha.user'); + my $opac_renewability = _opac_renewal_allowed($user, $borrowernumber); + my $checkouts = Koha::Checkouts->search({ + borrowernumber => $borrowernumber + }); + my $checkouts_json = $checkouts->TO_JSON; + + foreach my $checkout (@{$checkouts_json}) { + my $item = Koha::Items->find($checkout->{itemnumber}); + my $branchcode = C4::Circulation::_GetCircControlBranch( $item->unblessed, + $borrower); + my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule( + { categorycode => $borrower->{categorycode}, + itemtype => $item->effective_itemtype, + branchcode => $branchcode + } + ); + $checkout->{'max_renewals'} = $issuing_rule + ? 0+$issuing_rule->renewalsallowed : 0; + my ($can_renew, $error); + if ($opac_renewability) { + ($can_renew, $error) = C4::Circulation::CanBookBeRenewed( + $borrowernumber, $checkout->{'itemnumber'}); + } + + $checkout->{'renewable'} = $can_renew + ? Mojo::JSON->true : Mojo::JSON->false; + } + + return $c->render( status => 200, openapi => $checkouts_json ); +} + +sub _opac_renewal_allowed { + my ($user, $borrowernumber) = @_; + + my $OpacRenewalAllowed; + if ($user->borrowernumber == $borrowernumber) { + $OpacRenewalAllowed = C4::Context->preference('OpacRenewalAllowed'); + } + + unless ($user && ($OpacRenewalAllowed || haspermission($user->userid, + { circulate => "circulate_remaining_permissions" }))) { + return 0; + } + return 1; +} + 1; diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json index 378c7cc..4dd6d33 100644 --- a/api/v1/swagger/definitions.json +++ b/api/v1/swagger/definitions.json @@ -32,6 +32,9 @@ "checkout": { "$ref": "definitions/checkout.json" }, + "checkoutexpanded": { + "$ref": "definitions/checkoutexpanded.json" + }, "holds": { "$ref": "definitions/holds.json" }, diff --git a/api/v1/swagger/definitions/checkoutexpanded.json b/api/v1/swagger/definitions/checkoutexpanded.json new file mode 100644 index 0000000..17bce36 --- /dev/null +++ b/api/v1/swagger/definitions/checkoutexpanded.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "allOf": [ + { "$ref": "checkout.json" } + ], + "properties": { + "renewability": { + "type": "boolean", + "description": "renewability status" + }, + "max_renewals": { + "type": "integer", + "description": "maximum number of renewals allowed" + } + } +} diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index e334437..db50edc 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -59,6 +59,9 @@ "/checkouts/{checkout_id}/renewability": { "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1renewability" }, + "/checkouts/expanded": { + "$ref": "paths/checkouts.json#/~1checkouts~1expanded" + }, "/checkouts/history": { "$ref": "paths/checkouts.json#/~1checkouts~1history" }, diff --git a/api/v1/swagger/paths/checkouts.json b/api/v1/swagger/paths/checkouts.json index 495be52..04bc311 100644 --- a/api/v1/swagger/paths/checkouts.json +++ b/api/v1/swagger/paths/checkouts.json @@ -198,6 +198,63 @@ } } }, + "/checkouts/expanded": { + "get": { + "x-mojo-to": "Checkout#expanded", + "operationId": "expandedCheckouts", + "tags": ["patrons", "checkouts"], + "parameters": [{ + "$ref": "../parameters.json#/borrowernumberQueryParam" + }], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "A list of expanded checkouts", + "schema": { + "type": "array", + "items": { + "$ref": "../definitions.json#/checkoutexpanded" + } + } + }, + "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 error", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "503": { + "description": "Under maintenance", + "schema": { + "$ref": "../definitions.json#/error" + } + } + }, + "x-koha-authorization": { + "allow-owner": true, + "allow-guarantor": true, + "permissions": { + "circulate": "circulate_remaining_permissions" + } + } + } + }, "/checkouts/history": { "get": { "x-mojo-to": "Checkout#listhistory", diff --git a/t/db_dependent/api/v1/checkoutsexpanded.t b/t/db_dependent/api/v1/checkoutsexpanded.t new file mode 100644 index 0000000..abb7b7c --- /dev/null +++ b/t/db_dependent/api/v1/checkoutsexpanded.t @@ -0,0 +1,174 @@ +#!/usr/bin/env perl + +# Copyright 2017 Koha-Suomi Oy +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Test::More tests => 1; +use Test::Mojo; +use t::lib::Mocks; +use t::lib::TestBuilder; + +use C4::Auth; +use C4::Circulation; +use C4::Context; +use C4::Items; + +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling +# this affects the other REST api tests +t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' ); + +my $remote_address = '127.0.0.1'; +my $t = Test::Mojo->new('Koha::REST::V1'); + +# Mock userenv branchcode +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; +my $module = new Test::MockModule('C4::Context'); +$module->mock('userenv', sub { { branch => $branchcode } }); + +subtest 'get() tests' => sub { + plan tests => 13; + + $schema->storage->txn_begin; + + # Create test context + my ($borrowernumber, $sessionid) = create_user_and_session(); + my ($librariannnumber, $librariansessionid) = create_user_and_session(2); + + # 1. Patrons + my $patron = Koha::Patrons->find($borrowernumber); + my $librarian = Koha::Patrons->find($librariannnumber); + + # 2. Biblios and items + my $biblionumber = create_biblio('RESTful Web APIs'); + my $itemnumber1 = create_item($biblionumber, 'TEST1001'); + my $itemnumber2 = create_item($biblionumber, 'TEST1002'); + my $itemnumber3 = create_item($biblionumber, 'TEST1003'); + my $item1 = Koha::Items->find($itemnumber1); + my $item2 = Koha::Items->find($itemnumber2); + my $item3 = Koha::Items->find($itemnumber3); + + # 3. Checkouts + my $due = DateTime->now->add(weeks => 2); + my $issue1 = C4::Circulation::AddIssue($patron->unblessed, 'TEST1001', $due); + my $due2 = Koha::DateUtils::dt_from_string( $issue1->date_due ); + my $issue2 = C4::Circulation::AddIssue($patron->unblessed, 'TEST1002', $due2); + C4::Circulation::AddRenewal($borrowernumber, $itemnumber2, $branchcode, $due2); + + # 4. Issuing rules + t::lib::Mocks::mock_preference('CircControl', 'ItemHomeLibrary'); + t::lib::Mocks::mock_preference('HomeOrHoldingBranch', 'homebranch'); + Koha::IssuingRule->new({ + categorycode => $patron->categorycode, + itemtype => $item1->effective_itemtype, + branchcode => $item1->homebranch, + renewalsallowed => 5, + })->store; + Koha::IssuingRule->new({ + categorycode => $patron->categorycode, + itemtype => $item2->effective_itemtype, + branchcode => $item2->homebranch, + renewalsallowed => 10, + })->store; + + # BEGIN TEST + + # Request my own expanded checkout infromation + my $tx = $t->ua->build_tx(GET => "/api/v1/checkouts/expanded?borrowernumber=" + .$borrowernumber); + $tx->req->cookies({name =>'CGISESSID', value => $sessionid}); + $tx->req->env({REMOTE_ADDR => '127.0.0.1'}); + $t->request_ok($tx) + ->status_is(200) + ->json_is('/0/borrowernumber' => $borrowernumber) + ->json_is('/0/itemnumber' => $itemnumber1) + ->json_like('/0/date_due' => qr/$due\+\d\d:\d\d/) + ->json_is('/0/renewals' => 0) + ->json_is('/0/max_renewals' => 5) + ->json_is('/1/borrowernumber' => $borrowernumber) + ->json_is('/1/itemnumber' => $itemnumber2) + ->json_like('/1/date_due' => qr/$due2\+\d\d:\d\d/) + ->json_is('/1/renewals' => 1) + ->json_is('/1/max_renewals' => 10) + ->json_hasnt('/2'); + + $schema->storage->txn_rollback; +}; + +sub create_user_and_session { + my ($flags) = @_; + + my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; + my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; + + my $borrower = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + flags => $flags, + } + }); + + my $borrowersession = C4::Auth::get_session(''); + $borrowersession->param('number', $borrower->{ borrowernumber }); + $borrowersession->param('id', $borrower->{ userid }); + $borrowersession->param('ip', '127.0.0.1'); + $borrowersession->param('lasttime', time()); + $borrowersession->flush; + + return ($borrower->{borrowernumber}, $borrowersession->id); +} + +sub create_biblio { + my ($title) = @_; + + my $record = new MARC::Record; + $record->append_fields( + new MARC::Field('200', ' ', ' ', a => $title), + ); + + my ($biblionumber) = C4::Biblio::AddBiblio($record, ''); + + return $biblionumber; +} + +sub create_item { + my ($biblionumber, $barcode) = @_; + + my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; + + my $itemtype = $builder->build({ + source => 'Itemtype', value => { notforloan => 0 } + }); + my $item = { + barcode => $barcode, + homebranch => $branchcode, + holdingbranch => $branchcode, + itype => $itemtype->{itemtype}, + }; + + my $itemnumber = C4::Items::AddItem($item, $biblionumber); + + return $itemnumber; +} -- 2.7.4