From 8cd177bf5ef4a77092aa73fb45837961c3c9aa6e Mon Sep 17 00:00:00 2001 From: Jan Kissig Date: Tue, 2 Jul 2024 10:27:44 +0200 Subject: [PATCH] Bug 24401: Add API endpoint to checkin items This adds a post /checkin endpoint to return checked out items either by its item_id or by barcode. To test you can use f.e. ThunderClient extension in codium/vscode. Test plan for ktd on main: a) enable system preference RESTBasicAuth b) check out an item and remember its item id or barcode. You can use the checkouts endpoint for this: Auth: username: koha & password: koha Body JSON: { "item_id" : 578, "library_id": "CPL" } POST http://localhost:8081/api/v1/checkouts c) apply patch and check in item via new checkin endpoint Auth: username: koha & password: koha Body JSON: { "item_id" : 578, "library_id": "CPL" } or { "external_id" : "39999000011418", "library_id": "CPL" } POST http://localhost:8081/api/v1/checkin The endpoint accepts item_id and/or barcode as body params. d) check JSON response for return messages e) run unit tests: prove t/db_dependent/api/v1/checkin.t Signed-off-by: Aleisha Amohia --- Koha/REST/V1/Checkouts.pm | 69 +++++++++++++ api/v1/swagger/definitions/checkin.yaml | 20 ++++ api/v1/swagger/paths/checkin.yaml | 63 ++++++++++++ api/v1/swagger/swagger.yaml | 4 + t/db_dependent/api/v1/checkin.t | 130 ++++++++++++++++++++++++ 5 files changed, 286 insertions(+) create mode 100644 api/v1/swagger/definitions/checkin.yaml create mode 100644 api/v1/swagger/paths/checkin.yaml create mode 100755 t/db_dependent/api/v1/checkin.t diff --git a/Koha/REST/V1/Checkouts.pm b/Koha/REST/V1/Checkouts.pm index 301369a45c3..1ffa501e3ae 100644 --- a/Koha/REST/V1/Checkouts.pm +++ b/Koha/REST/V1/Checkouts.pm @@ -281,6 +281,75 @@ sub add { }; } +=head3 checkin + +Check in a checked out item via item_id or barcode + +=cut + +sub checkin { + + my $c = shift->openapi->valid_input or return; + my $body = $c->req->json; + my $item_id = $body->{item_id}; + my $barcode = $body->{external_id}; + my $library_id = $body->{library_id}; + + my $exemptfine = $body->{exemptfine}; + + return try { + + unless ($item_id or $barcode){ + return $c->render( + status => 400, + openapi => { error => 'Missing or wrong parameters.' } + ); + } + + # if item_id and barcode are set barcode gets overwritten + if ( $item_id ) { + my $item = Koha::Items->find($item_id); + unless ($item) { + return $c->render( + status => 409, + openapi => { error => 'Item not found.' } + ); + } + $barcode = $item->barcode; + } + + my ( $returned, $return_messages ) = + C4::Circulation::AddReturn( $barcode, $library_id, $exemptfine ); + + my $response = { returned => $returned, messages => $return_messages }; + + if ($return_messages->{BadBarcode}){ + $response->{error} = 'Item not found.'; + return $c->render( + status => 409, + openapi => $response + ); + } + + unless ($returned) { + return $c->render( + status => 403, + openapi => $response + ); + } + + $c->res->headers->location( $c->req->url->to_string ); + return $c->render( + status => 201, + openapi => $response + ); + + } + catch { + $c->unhandled_exception($_); + }; +} + =head3 get_renewals List Koha::Checkout::Renewals diff --git a/api/v1/swagger/definitions/checkin.yaml b/api/v1/swagger/definitions/checkin.yaml new file mode 100644 index 00000000000..42d245ccfe9 --- /dev/null +++ b/api/v1/swagger/definitions/checkin.yaml @@ -0,0 +1,20 @@ +--- +type: object +properties: + returned: + type: integer + description: True if return checkin succeeded + readOnly: true + messages: + description: Messages to the returned item + type: object + item_id: + type: integer + description: Internal item identifier + external_id: + type: integer + description: Barcode of the item + exemptfine: + type: boolean + description: If true overdue charges for the item will be removed +additionalProperties: true \ No newline at end of file diff --git a/api/v1/swagger/paths/checkin.yaml b/api/v1/swagger/paths/checkin.yaml new file mode 100644 index 00000000000..0820ca322e6 --- /dev/null +++ b/api/v1/swagger/paths/checkin.yaml @@ -0,0 +1,63 @@ +--- +/checkin: + post: + x-mojo-to: Checkouts#checkin + operationId: addCheckin + tags: + - checkin + - patrons + summary: Add a checkin + parameters: + - name: body + in: body + description: A JSON object containing information about the checkin + required: true + schema: + $ref: "../swagger.yaml#/definitions/checkin" + consumes: + - application/json + produces: + - application/json + responses: + "201": + description: Created checkin + schema: + $ref: "../swagger.yaml#/definitions/checkin" + "400": + description: Missing or wrong parameters + schema: + $ref: "../swagger.yaml#/definitions/error" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Cannot create checkin + schema: + $ref: "../swagger.yaml#/definitions/checkin" + "404": + description: Item not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "409": + description: Conflict in creating checkin + schema: + $ref: "../swagger.yaml#/definitions/error" + "412": + description: Precondition failed + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: | + Internal server error. Possible `error_code` attribute values: + + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + circulate: circulate_remaining_permissions diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index dd72361380e..bcc80ea7936 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -26,6 +26,8 @@ definitions: $ref: ./definitions/cash_register.yaml cashup: $ref: ./definitions/cashup.yaml + checkin: + $ref: ./definitions/checkin.yaml checkout: $ref: ./definitions/checkout.yaml checkouts: @@ -271,6 +273,8 @@ paths: $ref: "./paths/cash_registers.yaml#/~1cash_registers~1{cash_register_id}~1cashups" "/cashups/{cashup_id}": $ref: "./paths/cash_registers.yaml#/~1cashups~1{cashup_id}" + /checkin: + $ref: ./paths/checkin.yaml#/~1checkin /checkouts: $ref: ./paths/checkouts.yaml#/~1checkouts "/checkouts/{checkout_id}": diff --git a/t/db_dependent/api/v1/checkin.t b/t/db_dependent/api/v1/checkin.t new file mode 100755 index 00000000000..d68c76b25fc --- /dev/null +++ b/t/db_dependent/api/v1/checkin.t @@ -0,0 +1,130 @@ +#!/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, see . + +use Modern::Perl; + +use Test::More tests => 17; +use Test::MockModule; +use Test::Mojo; +use t::lib::Mocks; +use t::lib::TestBuilder; + +use DateTime; + +use C4::Context; +use C4::Circulation qw( AddIssue AddReturn CanBookBeIssued ); + +use Koha::Database; +use Koha::DateUtils qw( dt_from_string output_pref ); +use Koha::Token; + +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 $branchcode = $builder->build( { source => 'Branch' } )->{branchcode}; +my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2, branchcode => $branchcode } + } +); +my $password = 'thePassword123'; +$librarian->set_password( { password => $password, skip_validation => 1 } ); +my $userid = $librarian->userid; + +my $other_branchcode = $builder->build( { source => 'Branch' } )->{branchcode}; +my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0, branchcode => $other_branchcode }, + } +); +my $unauth_password = 'thePassword000'; +$patron->set_password( + { password => $unauth_password, skip_validattion => 1 } ); +my $unauth_userid = $patron->userid; +my $patron_id = $patron->borrowernumber; + +my $item1 = $builder->build_sample_item; +my $item1_id = $item1->id; + +my $not_issued_item = $builder->build_sample_item; +my $not_issued_item_id = $not_issued_item->id; +my $not_issued_item_barcode = $not_issued_item->barcode; + +my $barcode = '321321321321'; +my $matching_items = Koha::Items->search({ barcode => $barcode }); +while (my $item = $matching_items->next) { + $item->delete; +} + +my $unknown_item1_id = 578; +my $del_item = Koha::Items->find($unknown_item1_id); +if ($del_item){ + $del_item->delete; +} + +my $checkout = $builder->build_object( + { + class => 'Koha::Checkouts', + value => { + itemnumber => $item1_id, + branchcode => $branchcode, + borrowernumber => $patron_id, + note => undef, + notedate => undef, + noteseen => undef + } + } +); +my $checkout_id = $checkout->id; + +# empty checkin +$t->post_ok( + "//$userid:$password@/api/v1/checkin" => json => { } ) + ->status_is(400); + +# checkin on unknow item id +$t->post_ok( + "//$userid:$password@/api/v1/checkin" => json => { item_id => $unknown_item1_id } ) + ->status_is(409)->json_is( { error => "Item not found." } ); + +# checkin with unknow barcode +$t->post_ok( + "//$userid:$password@/api/v1/checkin" => json => { external_id => $barcode } ) + ->status_is(409)->json_is( '/error' => "Item not found." )->json_is( '/messages' => { BadBarcode => $barcode} ); + +# not isued +$t->post_ok( + "//$userid:$password@/api/v1/checkin" => json => { item_id => $not_issued_item_id, library_id => $branchcode } ) + ->status_is(403)->json_is( '/returned' => 0 )->json_has( '/messages' => { NotIssued => $not_issued_item_barcode } ); + +# checkin okay +$t->post_ok( + "//$userid:$password@/api/v1/checkin" => json => { item_id => $item1_id, library_id => $branchcode } ) + ->status_is(201)->json_is( '/returned' => 1 )->json_has( '/messages' => { TransferTrigger => 'ReturnToHome', WasReturned => '1' } ); + + + +$schema->storage->txn_rollback; -- 2.39.5