From a475d7634ec2539586fa656b71244407f7eb05b0 Mon Sep 17 00:00:00 2001 From: Jiri Kozlovsky Date: Mon, 1 Aug 2016 17:17:17 +0200 Subject: [PATCH] Bug 17005: REST API: add routes to list checkouts history GET /checkouts/history (get all old_issues' issue_id) GET /checkouts/history?borrowernumber={borrowernumber} (get all borrower's old_issues' issue_id) GET /checkouts/history/{checkout_id} (get old_issue) + unit tests in t/db_dependent/api/v1/checkoutshistory.t Test plan: 1. Open a browser tab on Koha staff and log in (to create CGISESSID cookie). You should have permission circulate_remaining_permissions. 2. Go to http://yourlibrary/api/v1/checkouts/history?borrowernumber=XXX (replace XXX with a borrowernumber that has some checkouts history) and check you receive all the issues from patron's history 3. Go to http://yourlibrary/api/v1/checkouts/history/YYY (replace YYY with an existing checkout id) and check you receive correct data 4. Run unit tests in t/db_dependent/api/v1/checkoutshistory.t This patch on top of previous changes adds the condition that the old issue included in any response must have itemnumber. Those which does not have itemnumber does not count as such old issue is completely useless. --- Koha/REST/V1/Checkout.pm | 49 ++++++++++ api/v1/definitions/checkout.json | 4 +- api/v1/swagger.json | 66 +++++++++++++ t/db_dependent/api/v1/checkoutshistory.t | 160 +++++++++++++++++++++++++++++++ 4 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 t/db_dependent/api/v1/checkoutshistory.t diff --git a/Koha/REST/V1/Checkout.pm b/Koha/REST/V1/Checkout.pm index 68046a6..a9ce7be 100644 --- a/Koha/REST/V1/Checkout.pm +++ b/Koha/REST/V1/Checkout.pm @@ -22,6 +22,7 @@ use Mojo::Base 'Mojolicious::Controller'; use C4::Auth qw( haspermission ); use C4::Circulation; use Koha::Issues; +use Koha::OldIssues; sub list { my ($c, $args, $cb) = @_; @@ -94,4 +95,52 @@ sub renew { return $c->$cb($checkout->unblessed, 200); } +sub listhistory { + my ($c, $args, $cb) = @_; + + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, { circulate => "circulate_remaining_permissions" })) { + return $c->$cb({error => "You don't have the required permission"}, 403); + } + + my $borrowernumber = $c->param('borrowernumber'); + + my %attributes = ( itemnumber => { "!=", undef } ); + if ($borrowernumber) { + return $c->$cb({ + error => "Patron doesn't exist" + }, 404) unless Koha::Patrons->find($borrowernumber); + + $attributes{borrowernumber} = $borrowernumber; + } + + # Retrieve all the issues in the history, but only the issue_id due to possible perfomance issues + my $checkouts = Koha::OldIssues->search( + \%attributes, + { columns => [qw/issue_id/]} + ); + + $c->$cb($checkouts->unblessed, 200); +} + +sub gethistory { + my ($c, $args, $cb) = @_; + + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, { circulate => "circulate_remaining_permissions" })) { + return $c->$cb({error => "You don't have the required permission"}, 403); + } + + my $checkout_id = $args->{checkout_id}; + my $checkout = Koha::OldIssues->find($checkout_id); + + if (!$checkout) { + return $c->$cb({ + error => "Checkout doesn't exist" + }, 404); + } + + return $c->$cb($checkout->unblessed, 200); +} + 1; diff --git a/api/v1/definitions/checkout.json b/api/v1/definitions/checkout.json index c56c6df..24bfec3 100644 --- a/api/v1/definitions/checkout.json +++ b/api/v1/definitions/checkout.json @@ -6,11 +6,11 @@ "description": "internally assigned checkout identifier" }, "borrowernumber": { - "type": "string", + "type": ["string", "null"], "description": "internally assigned user identifier" }, "itemnumber": { - "type": "string", + "type": ["string", "null"], "description": "internally assigned item identifier" }, "date_due": { diff --git a/api/v1/swagger.json b/api/v1/swagger.json index 68eeea6..c590613 100644 --- a/api/v1/swagger.json +++ b/api/v1/swagger.json @@ -426,6 +426,72 @@ } } } + }, + "/checkouts/history": { + "get": { + "operationId": "listhistoryCheckouts", + "tags": ["borrowers", "checkouts"], + "parameters": [ + { + "name": "borrowernumber", + "in": "query", + "description": "Internal patron identifier", + "required": false, + "type": "integer" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "A list of checkouts history", + "schema": { + "$ref": "#/definitions/checkouts" + } + }, + "403": { + "description": "Access forbidden", + "schema": { "$ref": "#/definitions/error" } + }, + "404": { + "description": "Borrower not found", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, + "/checkouts/history/{checkout_id}": { + "get": { + "operationId": "gethistoryCheckout", + "tags": ["borrowers", "checkouts"], + "parameters": [ + { + "name": "checkout_id", + "in": "path", + "description": "Internal checkout identifier", + "required": true, + "type": "integer" + } + ], + "produces": ["application/json"], + "responses": { + "200": { + "description": "Got borrower's checkout", + "schema": { "$ref": "#/definitions/checkout" } + }, + "403": { + "description": "Access forbidden", + "schema": { "$ref": "#/definitions/error" } + }, + "404": { + "description": "Checkout not found", + "schema": { "$ref": "#/definitions/error" } + } + } + } } }, "definitions": { diff --git a/t/db_dependent/api/v1/checkoutshistory.t b/t/db_dependent/api/v1/checkoutshistory.t new file mode 100644 index 0000000..c9d988a --- /dev/null +++ b/t/db_dependent/api/v1/checkoutshistory.t @@ -0,0 +1,160 @@ +#!/usr/bin/env 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Test::More tests => 27; +use Test::MockModule; +use Test::Mojo; +use t::lib::TestBuilder; + +use DateTime; +use MARC::Record; + +use C4::Context; +use C4::Biblio; +use C4::Circulation; +use C4::Items; + +use Koha::Database; +use Koha::Patron; +use Koha::OldIssue; +use Koha::OldIssues; + +my $schema = Koha::Database->schema; +$schema->storage->txn_begin; +my $dbh = C4::Context->dbh; +my $builder = t::lib::TestBuilder->new; +$dbh->{RaiseError} = 1; + +$ENV{REMOTE_ADDR} = '127.0.0.1'; +my $t = Test::Mojo->new('Koha::REST::V1'); + +$dbh->do('DELETE FROM issues'); +$dbh->do('DELETE FROM items'); +$dbh->do('DELETE FROM issuingrules'); +my $loggedinuser = $builder->build({ source => 'Borrower' }); + +$dbh->do(q{ + INSERT INTO user_permissions (borrowernumber, module_bit, code) + VALUES (?, 1, 'circulate_remaining_permissions') +}, undef, $loggedinuser->{borrowernumber}); + +my $session = C4::Auth::get_session(''); +$session->param('number', $loggedinuser->{ borrowernumber }); +$session->param('id', $loggedinuser->{ userid }); +$session->param('ip', '127.0.0.1'); +$session->param('lasttime', time()); +$session->flush; + +my $borrower = $builder->build({ source => 'Borrower' }); +my $borrowernumber = $borrower->{borrowernumber}; + +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; +my $module = new Test::MockModule('C4::Context'); +$module->mock('userenv', sub { { branch => $branchcode } }); + +my $tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history?borrowernumber=$borrowernumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_is([]); + +my $notexisting_borrowernumber = $borrowernumber + 1; +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history?borrowernumber=$notexisting_borrowernumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(404) + ->json_has('/error'); + +my $biblionumber = create_biblio('RESTful Web APIs'); +my $itemnumber1 = create_item($biblionumber, 'TEST000001'); +my $itemnumber2 = create_item($biblionumber, 'TEST000002'); + +my $date_due = DateTime->now->add(weeks => 2); + +my $issueId = Koha::OldIssues->count({}) + int rand(150000); +my $issue1; +$issue1 = Koha::OldIssue->new({ issue_id => $issueId, borrowernumber => $borrowernumber, itemnumber => $itemnumber1, date_due => $date_due}); +$issue1->store(); + +my $date_due1 = Koha::DateUtils::dt_from_string( $issue1->date_due ); + +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history?borrowernumber=$borrowernumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_is('/0/issue_id' => $issueId) + ->json_hasnt('/1') + ->json_hasnt('/error'); + +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history/" . $issue1->issue_id); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_is('/borrowernumber' => $borrowernumber) + ->json_is('/itemnumber' => $itemnumber1) + ->json_is('/date_due' => $date_due1->ymd . ' ' . $date_due1->hms) + ->json_hasnt('/error'); + +$issue1->delete(); + +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history/" . $issue1->issue_id); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(404) + ->json_has('/error'); + +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history?borrowernumber=$borrowernumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_hasnt('/0') + ->json_hasnt('/error'); + +Koha::Patrons->find($borrowernumber)->delete(); + +$tx = $t->ua->build_tx(GET => "/api/v1/checkouts/history?borrowernumber=$borrowernumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(404) + ->json_has('/error'); + +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 $item = { + barcode => $barcode, + }; + + my $itemnumber = C4::Items::AddItem($item, $biblionumber); + + return $itemnumber; +} -- 2.1.4