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:
parent
dfc6da2df4
commit
6db025bd9d
5 changed files with 268 additions and 3 deletions
|
@ -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 ) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue