Browse Source

Bug 14697: Add routes to handle return claims

This patch adds the /return_claims route to add new return claims, and
then routes to updates notes and the resolution code.

To test:
1. Apply this patches
2. Run:
   $ kshell
  k$ prove t/db_dependent/api/v1/return_claims.t
=> SUCCESS: Tests pass!
3. Sign off :-D

Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Andrew Fuerste-Henry <andrew@bywatersolutions.com>
Signed-off-by: Lisette Scheer <lisetteslatah@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
remotes/origin/19.11.x
Kyle Hall 3 years ago
committed by Martin Renvoize
parent
commit
a44ffa0926
Signed by: martin.renvoize GPG Key ID: 422B469130441A0F
  1. 257
      Koha/REST/V1/ReturnClaims.pm
  2. 3
      api/v1/swagger/definitions.json
  3. 93
      api/v1/swagger/definitions/return_claim.json
  4. 12
      api/v1/swagger/paths.json
  5. 355
      api/v1/swagger/paths/return_claims.json
  6. 162
      t/db_dependent/api/v1/return_claims.t

257
Koha/REST/V1/ReturnClaims.pm

@ -0,0 +1,257 @@
package Koha::REST::V1::ReturnClaims;
# 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 Mojo::Base 'Mojolicious::Controller';
use Try::Tiny;
use Koha::Checkouts::ReturnClaims;
use Koha::Checkouts;
use Koha::DateUtils qw( dt_from_string output_pref );
=head1 NAME
Koha::REST::V1::ReturnClaims
=head2 Operations
=head3 claim_returned
Claim that a checked out item was returned.
=cut
sub claim_returned {
my $c = shift->openapi->valid_input or return;
my $body = $c->validation->param('body');
return try {
my $itemnumber = $body->{item_id};
my $charge_lost_fee = $body->{charge_lost_fee} ? 1 : 0;
my $created_by = $body->{created_by};
my $notes = $body->{notes};
my $user = $c->stash('koha.user');
$created_by //= $user->borrowernumber;
my $checkout = Koha::Checkouts->find( { itemnumber => $itemnumber } );
return $c->render(
openapi => { error => "Not found - Checkout not found" },
status => 404
) unless $checkout;
my $claim = Koha::Checkouts::ReturnClaims->find(
{
issue_id => $checkout->id
}
);
return $c->render(
openapi => { error => "Bad request - claim exists" },
status => 400
) if $claim;
$claim = $checkout->claim_returned(
{
charge_lost_fee => $charge_lost_fee,
created_by => $created_by,
notes => $notes,
}
);
$c->res->headers->location($c->req->url->to_string . '/' . $claim->id );
return $c->render(
status => 201,
openapi => $claim->to_api
);
}
catch {
if ( $_->isa('Koha::Exceptions::Checkouts::ReturnClaims') ) {
return $c->render(
status => 500,
openapi => { error => "$_" }
);
}
else {
return $c->render(
status => 500,
openapi => { error => "Something went wrong, check the logs." }
);
}
};
}
=head3 update_notes
Update the notes of an existing claim
=cut
sub update_notes {
my $c = shift->openapi->valid_input or return;
my $input = $c->validation->output;
my $body = $c->validation->param('body');
return try {
my $id = $input->{claim_id};
my $updated_by = $body->{updated_by};
my $notes = $body->{notes};
$updated_by ||=
C4::Context->userenv ? C4::Context->userenv->{number} : undef;
my $claim = Koha::Checkouts::ReturnClaims->find($id);
return $c->render(
openapi => { error => "Not found - Claim not found" },
status => 404
) unless $claim;
$claim->set(
{
notes => $notes,
updated_by => $updated_by,
updated_on => dt_from_string(),
}
);
$claim->store();
my $data = $claim->unblessed;
my $c_dt = dt_from_string( $data->{created_on} );
my $u_dt = dt_from_string( $data->{updated_on} );
$data->{created_on_formatted} = output_pref( { dt => $c_dt } );
$data->{updated_on_formatted} = output_pref( { dt => $u_dt } );
$data->{created_on} = $c_dt->iso8601;
$data->{updated_on} = $u_dt->iso8601;
return $c->render( openapi => $data, status => 200 );
}
catch {
if ( $_->isa('DBIx::Class::Exception') ) {
return $c->render(
status => 500,
openapi => { error => $_->{msg} }
);
}
else {
return $c->render(
status => 500,
openapi => { error => "Something went wrong, check the logs." }
);
}
};
}
=head3 resolve_claim
Marks a claim as resolved
=cut
sub resolve_claim {
my $c = shift->openapi->valid_input or return;
my $input = $c->validation->output;
my $body = $c->validation->param('body');
return try {
my $id = $input->{claim_id};
my $resolved_by = $body->{updated_by};
my $resolution = $body->{resolution};
$resolved_by ||=
C4::Context->userenv ? C4::Context->userenv->{number} : undef;
my $claim = Koha::Checkouts::ReturnClaims->find($id);
return $c->render(
openapi => { error => "Not found - Claim not found" },
status => 404
) unless $claim;
$claim->set(
{
resolution => $resolution,
resolved_by => $resolved_by,
resolved_on => dt_from_string(),
}
);
$claim->store();
return $c->render( openapi => $claim, status => 200 );
}
catch {
if ( $_->isa('DBIx::Class::Exception') ) {
return $c->render(
status => 500,
openapi => { error => $_->{msg} }
);
}
else {
return $c->render(
status => 500,
openapi => { error => "Something went wrong, check the logs." }
);
}
};
}
=head3 delete_claim
Deletes the claim from the database
=cut
sub delete_claim {
my $c = shift->openapi->valid_input or return;
my $input = $c->validation->output;
return try {
my $id = $input->{claim_id};
my $claim = Koha::Checkouts::ReturnClaims->find($id);
return $c->render(
openapi => { error => "Not found - Claim not found" },
status => 404
) unless $claim;
$claim->delete();
return $c->render( openapi => $claim, status => 200 );
}
catch {
if ( $_->isa('DBIx::Class::Exception') ) {
return $c->render(
status => 500,
openapi => { error => $_->{msg} }
);
}
else {
return $c->render(
status => 500,
openapi => { error => "Something went wrong, check the logs." }
);
}
};
}
1;

3
api/v1/swagger/definitions.json

@ -43,5 +43,8 @@
},
"fund": {
"$ref": "definitions/fund.json"
},
"return_claim": {
"$ref": "definitions/return_claim.json"
}
}

93
api/v1/swagger/definitions/return_claim.json

@ -0,0 +1,93 @@
{
"type": "object",
"properties": {
"claim_id": {
"type": [
"integer"
],
"description": "internally assigned return claim identifier"
},
"item_id": {
"type": [
"integer"
],
"description": "internal identifier of the claimed item"
},
"issue_id": {
"type": [
"integer",
"null"
],
"description": "internal identifier of the claimed checkout if still checked out"
},
"old_issue_id": {
"type": [
"integer",
"null"
],
"description": "internal identifier of the claimed checkout if not longer checked out"
},
"patron_id": {
"$ref": "../x-primitives.json#/patron_id"
},
"notes": {
"type": [
"string",
"null"
],
"description": "notes about this claim"
},
"created_on": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "date of claim creation"
},
"created_by": {
"type": [
"integer",
"null"
],
"description": "patron id of librarian who made the claim"
},
"updated_on": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "date the claim was last updated"
},
"updated_by": {
"type": [
"integer",
"null"
],
"description": "patron id of librarian who last updated the claim"
},
"resolution": {
"type": [
"string",
"null"
],
"description": "code of resolution type for this claim"
},
"resolved_on": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "date the claim was resolved"
},
"resolved_by": {
"type": [
"integer",
"null"
],
"description": "patron id of librarian who resolved this claim"
}
}
}

12
api/v1/swagger/paths.json

@ -88,5 +88,17 @@
},
"/public/patrons/{patron_id}/guarantors/can_see_checkouts": {
"$ref": "paths/public_patrons.json#/~1public~1patrons~1{patron_id}~1guarantors~1can_see_checkouts"
},
"/return_claims": {
"$ref": "paths/return_claims.json#/~1return_claims"
},
"/return_claims/{claim_id}/notes": {
"$ref": "paths/return_claims.json#/~1return_claims~1{claim_id}~1notes"
},
"/return_claims/{claim_id}/resolve": {
"$ref": "paths/return_claims.json#/~1return_claims~1{claim_id}~1resolve"
},
"/return_claims/{claim_id}": {
"$ref": "paths/return_claims.json#/~1return_claims~1{claim_id}"
}
}

355
api/v1/swagger/paths/return_claims.json

@ -0,0 +1,355 @@
{
"/return_claims": {
"post": {
"x-mojo-to": "ReturnClaims#claim_returned",
"operationId": "claimReturned",
"tags": [
"claims",
"returned",
"return",
"claim"
],
"parameters": [
{
"name": "body",
"in": "body",
"description": "A JSON object containing fields to modify",
"required": true,
"schema": {
"type": "object",
"properties": {
"item_id" : {
"description": "Internal item id to claim as returned",
"type": "integer"
},
"notes": {
"description": "Notes about this return claim",
"type": "string"
},
"created_by": {
"description": "User id for the librarian submitting this claim",
"type": "string"
},
"charge_lost_fee": {
"description": "Charge a lost fee if true and Koha is set to allow a choice. Ignored otherwise.",
"type": "boolean"
}
}
}
}
],
"produces": [
"application/json"
],
"responses": {
"201": {
"description": "Created claim",
"schema": {
"$ref": "../definitions.json#/return_claim"
}
},
"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": "Checkout 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": {
"permissions": {
"circulate": "circulate_remaining_permissions"
}
}
}
},
"/return_claims/{claim_id}/notes": {
"put": {
"x-mojo-to": "ReturnClaims#update_notes",
"operationId": "updateClaimNotes",
"tags": [
"claims",
"returned",
"return",
"claim",
"notes"
],
"parameters": [
{
"name": "claim_id",
"in": "path",
"required": true,
"description": "Unique identifier for the claim whose notes are to be updated",
"type": "integer"
},
{
"name": "body",
"in": "body",
"description": "A JSON object containing fields to modify",
"required": true,
"schema": {
"type": "object",
"properties": {
"notes": {
"description": "Notes about this return claim",
"type": "string"
},
"updated_by": {
"description": "User id for the librarian updating the claim notes",
"type": "string"
}
}
}
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "Claim notes updated",
"schema": {
"$ref": "../definitions.json#/return_claim"
}
},
"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": "Claim 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": {
"permissions": {
"circulate": "circulate_remaining_permissions"
}
}
}
},
"/return_claims/{claim_id}": {
"delete": {
"x-mojo-to": "ReturnClaims#delete_claim",
"operationId": "deletedClaim",
"tags": [
"claims",
"returned",
"return",
"claim",
"delete"
],
"parameters": [
{
"name": "claim_id",
"in": "path",
"required": true,
"description": "Unique identifier for the claim to be deleted",
"type": "integer"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "Claim deleted",
"schema": {
"$ref": "../definitions.json#/return_claim"
}
},
"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": "Claim 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": {
"permissions": {
"circulate": "circulate_remaining_permissions"
}
}
}
},
"/return_claims/{claim_id}/resolve": {
"put": {
"x-mojo-to": "ReturnClaims#resolve_claim",
"operationId": "updateClaimResolve",
"tags": [
"claims",
"returned",
"return",
"claim",
"notes"
],
"parameters": [
{
"name": "claim_id",
"in": "path",
"required": true,
"description": "Unique identifier for the claim to be resolved",
"type": "integer"
},
{
"name": "body",
"in": "body",
"description": "A JSON object containing fields to modify",
"required": true,
"schema": {
"type": "object",
"properties": {
"resolution": {
"description": "The RETURN_CLAIM_RESOLUTION code to be used to resolve the calim",
"type": "string"
},
"resolved_by": {
"description": "User id for the librarian resolving the claim",
"type": "string"
}
}
}
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "Claim resolved",
"schema": {
"$ref": "../definitions.json#/return_claim"
}
},
"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": "Claim 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": {
"permissions": {
"circulate": "circulate_remaining_permissions"
}
}
}
}
}

162
t/db_dependent/api/v1/return_claims.t

@ -0,0 +1,162 @@
#!/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 => 25;
use Test::MockModule;
use Test::Mojo;
use t::lib::Mocks;
use t::lib::TestBuilder;
use DateTime;
use C4::Context;
use C4::Circulation;
use Koha::Checkouts::ReturnClaims;
use Koha::Database;
use Koha::DateUtils;
my $schema = Koha::Database->schema;
my $builder = t::lib::TestBuilder->new;
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
my $t = Test::Mojo->new('Koha::REST::V1');
$schema->storage->txn_begin;
my $dbh = C4::Context->dbh;
my $librarian = $builder->build_object(
{
class => 'Koha::Patrons',
value => { flags => 1 }
}
);
my $password = 'thePassword123';
$librarian->set_password( { password => $password, skip_validation => 1 } );
my $userid = $librarian->userid;
my $patron = $builder->build_object(
{
class => 'Koha::Patrons',
value => { flags => 0 }
}
);
my $unauth_password = 'thePassword000';
$patron->set_password(
{ password => $unauth_password, skip_validattion => 1 } );
my $unauth_userid = $patron->userid;
my $patron_id = $patron->borrowernumber;
my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
my $module = new Test::MockModule('C4::Context');
$module->mock( 'userenv', sub { { branch => $branchcode } } );
my $item1 = $builder->build_sample_item;
my $itemnumber1 = $item1->itemnumber;
my $date_due = DateTime->now->add( weeks => 2 );
my $issue1 =
C4::Circulation::AddIssue( $patron->unblessed, $item1->barcode, $date_due );
t::lib::Mocks::mock_preference( 'ClaimReturnedChargeFee', 'ask' );
t::lib::Mocks::mock_preference( 'ClaimReturnedLostValue', '99' );
# Test creating a return claim
## Invalid id
$t->post_ok(
"//$userid:$password@/api/v1/return_claims" => json => {
item_id => 1,
charge_lost_fee => Mojo::JSON->false,
created_by => $librarian->id,
notes => "This is a test note."
}
)->status_is(404);
## Valid id
$t->post_ok(
"//$userid:$password@/api/v1/return_claims" => json => {
item_id => $itemnumber1,
charge_lost_fee => Mojo::JSON->false,
created_by => $librarian->id,
notes => "This is a test note."
}
)->status_is(201);
my $claim_id = $t->tx->res->json->{claim_id};
## Duplicate id
$t->post_ok(
"//$userid:$password@/api/v1/return_claims" => json => {
item_id => $itemnumber1,
charge_lost_fee => Mojo::JSON->false,
created_by => $librarian->id,
notes => "This is a test note."
}
)->status_is(400);
# Test editing a claim note
## Valid claim id
$t->put_ok(
"//$userid:$password@/api/v1/return_claims/$claim_id/notes" => json => {
notes => "This is a different test note.",
updated_by => $librarian->id,
}
)->status_is(200);
my $claim = Koha::Checkouts::ReturnClaims->find($claim_id);
is( $claim->notes, "This is a different test note." );
is( $claim->updated_by, $librarian->id );
ok( $claim->updated_on );
## Bad claim id
$t->put_ok(
"//$userid:$password@/api/v1/return_claims/99999999999/notes" => json => {
notes => "This is a different test note.",
updated_by => $librarian->id,
}
)->status_is(404);
# Resolve a claim
## Valid claim id
$t->put_ok(
"//$userid:$password@/api/v1/return_claims/$claim_id/resolve" => json => {
resolved_by => $librarian->id,
resolution => "FOUNDINLIB",
}
)->status_is(200);
$claim = Koha::Checkouts::ReturnClaims->find($claim_id);
is( $claim->resolution, "FOUNDINLIB" );
is( $claim->updated_by, $librarian->id );
ok( $claim->resolved_on );
## Invalid claim id
$t->put_ok(
"//$userid:$password@/api/v1/return_claims/999999999999/resolve" => json => {
resolved_by => $librarian->id,
resolution => "FOUNDINLIB",
}
)->status_is(404);
# Test deleting a return claim
$t = $t->delete_ok("//$userid:$password@/api/v1/return_claims/$claim_id")
->status_is(200);
$claim = Koha::Checkouts::ReturnClaims->find($claim_id);
isnt( $claim, "Return claim was deleted" );
$t->delete_ok("//$userid:$password@/api/v1/return_claims/$claim_id")
->status_is(404);
Loading…
Cancel
Save