Bug 23336: Add checkout API's

This patch adds API's to allow for a checkout flow using the RESTful
API.

We add an availability endpoint to check an items current availability
status. The endpoint can be found at `/checkouts/availability` and is
a GET request that requires item_id and patron_id passed as parameters.

We return an availability object that includes blockers, confirms,
warnings and a confirmation token to be used for checkout.

We also add a corresponding checkout method to the `/checkouts` endpoint.
The method accepts a POST request with checkout details including item_id
, patron_id and the confirmation token in the body.

Future work: We should properly migrate CanBookBeIssued into Koha::* and
use that here instead of refering to C4::Circulation.

Signed-off-by: Silvia Meakins <smeakins@eso.org>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Martin Renvoize 2023-05-05 15:53:50 +01:00 committed by Tomas Cohen Arazi
parent dfc6da2df4
commit 6db025bd9d
Signed by: tomascohen
GPG key ID: 0A272EA1B2F3C15F
5 changed files with 268 additions and 3 deletions

View file

@ -683,6 +683,7 @@ data is keyed in lower case!
Available keys:
override_high_holds - Ignore high holds
onsite_checkout - Checkout is an onsite checkout that will not leave the library
item - Optionally pass the object for the item we are checking out to save a lookup
=back
@ -776,7 +777,8 @@ sub CanBookBeIssued {
my $onsite_checkout = $params->{onsite_checkout} || 0;
my $override_high_holds = $params->{override_high_holds} || 0;
my $item_object = Koha::Items->find({barcode => $barcode });
my $item_object = $params->{item}
// Koha::Items->find( { barcode => $barcode } );
# MANDATORY CHECKS - unless item exists, nothing else matters
unless ( $item_object ) {

View file

@ -336,7 +336,6 @@ sub move_to_deleted {
return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
}
=head3 effective_itemtype
Returns the itemtype for the item based on whether item level itemtypes are set or not.

View file

@ -19,10 +19,13 @@ use Modern::Perl;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON;
use Mojo::JWT;
use Digest::MD5 qw( md5_base64 );
use Encode;
use C4::Auth qw( haspermission );
use C4::Context;
use C4::Circulation qw( AddRenewal CanBookBeRenewed );
use C4::Circulation qw( AddIssue AddRenewal CanBookBeRenewed );
use Koha::Checkouts;
use Koha::Old::Checkouts;
@ -99,6 +102,163 @@ sub get {
};
}
=head3 get_availablity
Controller function that handles retrieval of Checkout availability
=cut
sub get_availability {
my $c = shift->openapi->valid_input or return;
my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
my $inprocess = 0; # What does this do?
my $ignore_reserves = 0; # Don't ignore reserves
my $item = Koha::Items->find( $c->validation->param('item_id') );
my $params = {
item => $item
};
my ( $impossible, $confirmation, $alerts, $messages ) =
C4::Circulation::CanBookBeIssued( $patron, undef, undef, $inprocess, $ignore_reserves,
$params );
my $token;
if (keys %{$confirmation}) {
my $claims = { map { $_ => 1 } keys %{$confirmation} };
my $secret =
md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) );
$token = Mojo::JWT->new( claims => $claims, secret => $secret )->encode;
}
my $response = {
blockers => $impossible,
confirms => $confirmation,
warnings => { %{$alerts}, %{$messages} },
confirmation_token => $token
};
return $c->render( status => 200, openapi => $response );
}
=head3 add
Add a new checkout
=cut
sub add {
my $c = shift->openapi->valid_input or return;
my $body = $c->validation->param('body');
my $item_id = $body->{item_id};
my $patron_id = $body->{patron_id};
my $onsite = $body->{onsite_checkout};
return try {
my $item = Koha::Items->find($item_id);
unless ($item) {
return $c->render(
status => 409,
openapi => {
error => 'Item not found',
error_code => 'ITEM_NOT_FOUND',
}
);
}
my $patron = Koha::Patrons->find($patron_id);
unless ($patron) {
return $c->render(
status => 409,
openapi => {
error => 'Patron not found',
error_code => 'PATRON_NOT_FOUND',
}
);
}
my $inprocess = 0; # What does this do?
my $ignore_reserves = 0; # Don't ignore reserves
my $params = { item => $item };
# Call 'CanBookBeIssued'
my ( $impossible, $confirmation, $alerts, $messages ) =
C4::Circulation::CanBookBeIssued(
$patron,
undef,
undef,
$inprocess,
$ignore_reserves,
$params
);
# * Fail for blockers - render 403
if ( keys %{$impossible} ) {
my @errors = keys %{$impossible};
return $c->render(
status => 403,
openapi => { error => "Checkout not authorized (@errors)" }
);
}
# * If confirmation required, check variable set above
# and render 412 if variable is false
if ( keys %{$confirmation} ) {
my $confirmed = 0;
# Check for existance of confirmation token
# and if exists check validity
if ( my $token = $c->validation->param('confirmation') ) {
my $secret =
md5_base64(
Encode::encode( 'UTF-8', C4::Context->config('pass') ) );
my $claims = try {
Mojo::JWT->new( secret => $secret )->decode($token);
}
catch {
return $c->render(
status => 403,
openapi => { error => "Confirmation required" }
);
};
# check claims match
my $token_claims = join( / /, sort keys %{$claims} );
my $confirm_keys = join( / /, sort keys %{$confirmation} );
$confirmed = 1 if ( $token_claims eq $confirm_keys );
}
unless ($confirmed) {
return $c->render(
status => 412,
openapi => { error => "Confirmation error" }
);
}
}
# Call 'AddIssue'
my $checkout = AddIssue( $patron->unblessed, $item->barcode );
if ($checkout) {
$c->res->headers->location(
$c->req->url->to_string . '/' . $checkout->id );
return $c->render(
status => 201,
openapi => $checkout->to_api
);
}
else {
return $c->render(
status => 500,
openapi => { error => 'Unknown error during checkout' }
);
}
}
catch {
$c->unhandled_exception($_);
};
}
=head3 get_renewals
List Koha::Checkout::Renewals

View file

@ -61,6 +61,68 @@
x-koha-authorization:
permissions:
circulate: circulate_remaining_permissions
post:
x-mojo-to: Checkouts#add
operationId: addCheckout
tags:
- checkouts
- patrons
summary: Add a new checkout
parameters:
- name: body
in: body
description: A JSON object containing information about the new checkout
required: true
schema:
$ref: "../swagger.yaml#/definitions/checkout"
- name: confirmation
in: query
description: A JWT confirmation token
required: false
type: string
consumes:
- application/json
produces:
- application/json
responses:
"201":
description: Created checkout
schema:
$ref: "../swagger.yaml#/definitions/checkout"
"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 checkout
schema:
$ref: "../swagger.yaml#/definitions/error"
"409":
description: Conflict in creating checkout
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
"/checkouts/{checkout_id}":
get:
x-mojo-to: Checkouts#get
@ -273,3 +335,38 @@
x-koha-authorization:
permissions:
circulate: circulate_remaining_permissions
"/checkouts/availability":
get:
x-mojo-to: Checkouts#get_availability
operationId: availabilityCheckouts
tags:
- checkouts
summary: Get checkout availability
parameters:
- $ref: "../swagger.yaml#/parameters/patron_id_qp"
- $ref: "../swagger.yaml#/parameters/item_id_qp"
produces:
- application/json
responses:
"200":
description: Availability
schema:
type: "object"
"403":
description: Access forbidden
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

View file

@ -197,6 +197,8 @@ paths:
$ref: "./paths/checkouts.yaml#/~1checkouts~1{checkout_id}~1renewals"
"/checkouts/{checkout_id}/renewal":
$ref: "./paths/checkouts.yaml#/~1checkouts~1{checkout_id}~1renewal"
"/checkouts/availability":
$ref: "./paths/checkouts.yaml#/~1checkouts~1availability"
/circulation-rules/kinds:
$ref: ./paths/circulation-rules.yaml#/~1circulation-rules~1kinds
/cities:
@ -521,6 +523,11 @@ parameters:
name: item_id
required: true
type: integer
item_id_qp:
description: Internal item identifier
in: query
name: item_id
type: integer
job_id_pp:
description: Job internal identifier
in: path