Bug 40517: REST API: Hold groups endpoint
list and add actions:
/patrons/{patron_id}/hold_groups
New test file:
prove t/db_dependent/api/v1/patrons_hold_groups.t
Signed-off-by: Anneli <anneli.osterman@koha-suomi.fi>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
07275db04f
commit
dada9dad0f
|
|
@ -31,6 +31,38 @@ Koha::HoldGroup - Koha Hold Group object class
|
|||
|
||||
=cut
|
||||
|
||||
=head3 to_api
|
||||
|
||||
my $json = $hold_group->to_api;
|
||||
|
||||
Overloaded method that returns a JSON representation of the Koha::HoldGroup object,
|
||||
suitable for API output. The related Koha::Holds objects are merged as expected
|
||||
on the API.
|
||||
|
||||
=cut
|
||||
|
||||
sub to_api {
|
||||
my ( $self, $args ) = @_;
|
||||
|
||||
my $json_hold_group = $self->SUPER::to_api($args);
|
||||
return unless $json_hold_group;
|
||||
|
||||
$args = defined $args ? {%$args} : {};
|
||||
delete $args->{embed};
|
||||
|
||||
my $holds = $self->holds;
|
||||
|
||||
Koha::Exceptions::RelatedObjectNotFound->throw( accessor => 'holds', class => 'Koha::HoldGroup' )
|
||||
unless $holds;
|
||||
|
||||
my @json_holds;
|
||||
for my $hold ($holds) {
|
||||
push @json_holds, $hold->to_api($args);
|
||||
}
|
||||
|
||||
return { %$json_hold_group, holds => @json_holds };
|
||||
}
|
||||
|
||||
=head3 holds
|
||||
|
||||
$holds = $hold_group->holds
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
package Koha::REST::V1::Patrons::HoldGroups;
|
||||
|
||||
# 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 <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use Koha::HoldGroup;
|
||||
use Koha::Patrons;
|
||||
|
||||
use Try::Tiny;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Koha::REST::V1::Patrons::HoldGroups
|
||||
|
||||
=head1 API
|
||||
|
||||
=head2 Methods
|
||||
|
||||
=head3 list
|
||||
|
||||
Controller function that handles listing Koha::HoldGroup objects for the requested patron
|
||||
|
||||
=cut
|
||||
|
||||
sub list {
|
||||
my $c = shift->openapi->valid_input or return;
|
||||
|
||||
my $patron = Koha::Patrons->find( $c->param('patron_id') );
|
||||
|
||||
return $c->render_resource_not_found("Patron")
|
||||
unless $patron;
|
||||
|
||||
return try {
|
||||
my $hold_groups_set = $patron->hold_groups;
|
||||
|
||||
my $hold_groups = $c->objects->search($hold_groups_set);
|
||||
return $c->render( status => 200, openapi => $hold_groups );
|
||||
} catch {
|
||||
$c->unhandled_exception($_);
|
||||
};
|
||||
}
|
||||
|
||||
=head3 add
|
||||
|
||||
Controller function that handles adding Koha::HoldGroup objects for the requested patron
|
||||
|
||||
=cut
|
||||
|
||||
sub add {
|
||||
my $c = shift->openapi->valid_input or return;
|
||||
|
||||
my $patron = Koha::Patrons->find( $c->param('patron_id') );
|
||||
|
||||
return $c->render_resource_not_found("Patron")
|
||||
unless $patron;
|
||||
|
||||
my $body = $c->req->json;
|
||||
my $hold_ids = $body->{hold_ids};
|
||||
my $force_grouped = $body->{force_grouped} || 0;
|
||||
|
||||
return try {
|
||||
my $hold_group = $patron->create_hold_group( $hold_ids, $force_grouped );
|
||||
$c->res->headers->location( $c->req->url->to_string . '/' . $hold_group->hold_group_id );
|
||||
return $c->render(
|
||||
status => 201,
|
||||
openapi => $c->objects->to_api($hold_group),
|
||||
);
|
||||
} catch {
|
||||
if ( blessed $_ ) {
|
||||
if ( $_->isa('Koha::Exceptions::HoldGroup::HoldDoesNotExist') ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
|
||||
error_code => 'HoldDoesNotExist',
|
||||
hold_ids => $_->hold_ids
|
||||
}
|
||||
);
|
||||
} elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldDoesNotBelongToPatron') ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
|
||||
error_code => 'HoldDoesNotBelongToPatron',
|
||||
hold_ids => $_->hold_ids
|
||||
}
|
||||
);
|
||||
} elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldHasAlreadyBeenFound') ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => $_->description . ": " . join( ', ', @{ $_->barcodes } ),
|
||||
error_code => 'HoldHasAlreadyBeenFound',
|
||||
barcodes => $_->barcodes
|
||||
}
|
||||
);
|
||||
} elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldAlreadyBelongsToHoldGroup') ) {
|
||||
return $c->render(
|
||||
status => 400,
|
||||
openapi => {
|
||||
error => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
|
||||
error_code => 'HoldAlreadyBelongsToHoldGroup',
|
||||
hold_ids => $_->hold_ids
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
$c->unhandled_exception($_);
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
type: object
|
||||
properties:
|
||||
hold_group_id:
|
||||
type: integer
|
||||
description: Internal hold group identifier
|
||||
borrowernumber:
|
||||
type: integer
|
||||
description: Patron this hold group belongs to
|
||||
holds:
|
||||
type:
|
||||
- array
|
||||
- "null"
|
||||
description: The holds in this hold group (x-koha-embed)
|
||||
|
||||
additionalProperties: false
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
"/patrons/{patron_id}/hold_groups":
|
||||
get:
|
||||
x-mojo-to: Patrons::HoldGroups#list
|
||||
operationId: getPatronHoldGroups
|
||||
tags:
|
||||
- hold_groups
|
||||
summary: List hold groups for a patron
|
||||
parameters:
|
||||
- $ref: "../swagger.yaml#/parameters/patron_id_pp"
|
||||
- $ref: "../swagger.yaml#/parameters/match"
|
||||
- $ref: "../swagger.yaml#/parameters/order_by"
|
||||
- $ref: "../swagger.yaml#/parameters/page"
|
||||
- $ref: "../swagger.yaml#/parameters/per_page"
|
||||
- $ref: "../swagger.yaml#/parameters/q_param"
|
||||
- $ref: "../swagger.yaml#/parameters/q_body"
|
||||
- $ref: "../swagger.yaml#/parameters/request_id_header"
|
||||
- name: x-koha-embed
|
||||
in: header
|
||||
required: false
|
||||
description: Embed list sent as a request header
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- holds
|
||||
collectionFormat: csv
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The patron hold groups
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "../swagger.yaml#/definitions/hold_group"
|
||||
"400":
|
||||
description: |
|
||||
Bad request. Possible `error_code` attribute values:
|
||||
|
||||
* `invalid_query`
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"401":
|
||||
description: Authentication required
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"403":
|
||||
description: Access forbidden
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"404":
|
||||
description: Patron not found
|
||||
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:
|
||||
borrowers: edit_borrowers
|
||||
post:
|
||||
x-mojo-to: Patrons::HoldGroups#add
|
||||
operationId: addPatronHoldGroups
|
||||
tags:
|
||||
- hold_groups
|
||||
summary: Add hold group. May contain hold ids of existing holds
|
||||
parameters:
|
||||
- name: body
|
||||
in: body
|
||||
description: A JSON object containing informations about the new hold group
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
hold_ids:
|
||||
description: List of holds to add to hold group
|
||||
type:
|
||||
- array
|
||||
- "null"
|
||||
force_grouped:
|
||||
description: Whether to force creation of a new hold group
|
||||
type:
|
||||
- boolean
|
||||
- "null"
|
||||
additionalProperties: false
|
||||
- name: x-koha-embed
|
||||
in: header
|
||||
required: false
|
||||
description: Embed list sent as a request header
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- holds
|
||||
collectionFormat: csv
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created hold group
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/hold_group"
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"401":
|
||||
description: Authentication required
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"403":
|
||||
description: Hold not allowed
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"404":
|
||||
description: Borrower not found
|
||||
schema:
|
||||
$ref: "../swagger.yaml#/definitions/error"
|
||||
"409":
|
||||
description: |
|
||||
Conflict. Possible `error_code` attribute values:
|
||||
|
||||
* `bad_address`
|
||||
* `card_lost`
|
||||
* `debt_limit`
|
||||
* `expired`
|
||||
* `hold_limit`
|
||||
* `restricted`
|
||||
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:
|
||||
reserveforothers: "1"
|
||||
|
|
@ -100,6 +100,8 @@ definitions:
|
|||
$ref: ./definitions/hold.yaml
|
||||
holds:
|
||||
$ref: ./definitions/holds.yaml
|
||||
hold_group:
|
||||
$ref: ./definitions/hold_group.yaml
|
||||
ill_backend:
|
||||
$ref: ./definitions/ill_backend.yaml
|
||||
ill_backends:
|
||||
|
|
@ -495,6 +497,8 @@ paths:
|
|||
$ref: "./paths/patrons_extended_attributes.yaml#/~1patrons~1{patron_id}~1extended_attributes~1{extended_attribute_id}"
|
||||
"/patrons/{patron_id}/holds":
|
||||
$ref: "./paths/patrons_holds.yaml#/~1patrons~1{patron_id}~1holds"
|
||||
"/patrons/{patron_id}/hold_groups":
|
||||
$ref: "./paths/patrons_hold_groups.yaml#/~1patrons~1{patron_id}~1hold_groups"
|
||||
"/patrons/{patron_id}/checkouts":
|
||||
$ref: "./paths/patrons_checkouts.yaml#/~1patrons~1{patron_id}~1checkouts"
|
||||
"/patrons/{patron_id}/password":
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
#!/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 <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use Test::NoWarnings;
|
||||
use Test::More tests => 3;
|
||||
use Test::MockModule;
|
||||
use Test::Mojo;
|
||||
|
||||
use t::lib::TestBuilder;
|
||||
use t::lib::Mocks;
|
||||
|
||||
use Koha::Database;
|
||||
|
||||
my $schema = Koha::Database->new->schema;
|
||||
my $builder = t::lib::TestBuilder->new();
|
||||
|
||||
my $t = Test::Mojo->new('Koha::REST::V1');
|
||||
|
||||
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
|
||||
|
||||
subtest 'list() tests' => sub {
|
||||
|
||||
plan tests => 6;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { flags => 2**4 } # 'borrowers' flag == 4
|
||||
}
|
||||
);
|
||||
my $password = 'thePassword123';
|
||||
$patron->set_password( { password => $password, skip_validation => 1 } );
|
||||
my $userid = $patron->userid;
|
||||
|
||||
$t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/hold_groups' )->status_is( 200, 'REST3.2.2' )
|
||||
->json_is( [] );
|
||||
|
||||
my $hold_group_1 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::HoldGroups',
|
||||
value => { borrowernumber => $patron->id }
|
||||
}
|
||||
);
|
||||
my $hold_1 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
|
||||
}
|
||||
);
|
||||
my $hold_2 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
|
||||
}
|
||||
);
|
||||
my $hold_3 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
|
||||
}
|
||||
);
|
||||
|
||||
$t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/hold_groups' => { 'x-koha-embed' => 'holds' } )
|
||||
->status_is( 200, 'REST3.2.2' )->json_has( "/0/holds", "holds object correctly embedded" );
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
|
||||
subtest 'add() tests' => sub {
|
||||
|
||||
plan tests => 15;
|
||||
|
||||
$schema->storage->txn_begin;
|
||||
|
||||
my $patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { flags => 2**6 } # reserveforothers flag = 6
|
||||
}
|
||||
);
|
||||
my $other_patron = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Patrons',
|
||||
value => { flags => 2**6 } # reserveforothers flag = 6
|
||||
}
|
||||
);
|
||||
my $password = 'thePassword123';
|
||||
$patron->set_password( { password => $password, skip_validation => 1 } );
|
||||
my $userid = $patron->userid;
|
||||
|
||||
my $hold_1 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $patron->id, hold_group_id => undef, found => undef }
|
||||
}
|
||||
);
|
||||
my $hold_2 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $patron->id, hold_group_id => undef, found => undef }
|
||||
}
|
||||
);
|
||||
my $hold_3 = $builder->build_object(
|
||||
{
|
||||
class => 'Koha::Holds',
|
||||
value => { borrowernumber => $other_patron->id, hold_group_id => undef, found => 'W' }
|
||||
}
|
||||
);
|
||||
|
||||
$t->post_ok(
|
||||
"//$userid:$password@/api/v1/patrons/" . $patron->id . "/hold_groups" => json => { hold_ids => [333] } )
|
||||
->status_is( 400, 'REST3.2.1' )
|
||||
->json_is(
|
||||
{ error => 'One or more holds do not exist: 333', error_code => "HoldDoesNotExist", hold_ids => [333] } );
|
||||
|
||||
$t->post_ok( "//$userid:$password@/api/v1/patrons/"
|
||||
. $other_patron->id
|
||||
. "/hold_groups" => json => { hold_ids => [ $hold_3->reserve_id ] } )->status_is( 400, 'REST3.2.1' )
|
||||
->json_is(
|
||||
{
|
||||
error => 'One or more holds have already been found: ' . $hold_3->item->barcode,
|
||||
error_code => "HoldHasAlreadyBeenFound", barcodes => [ $hold_3->item->barcode ]
|
||||
}
|
||||
);
|
||||
|
||||
$t->post_ok( "//$userid:$password@/api/v1/patrons/"
|
||||
. $patron->id
|
||||
. "/hold_groups" => json => { hold_ids => [ $hold_3->reserve_id ] } )->status_is( 400, 'REST3.2.1' )
|
||||
->json_is(
|
||||
{
|
||||
error => 'One or more holds do not belong to patron: ' . $hold_3->reserve_id,
|
||||
error_code => "HoldDoesNotBelongToPatron", hold_ids => [ $hold_3->reserve_id ]
|
||||
}
|
||||
);
|
||||
|
||||
$t->post_ok( "//$userid:$password@/api/v1/patrons/"
|
||||
. $patron->id
|
||||
. "/hold_groups" => json => { hold_ids => [ $hold_1->reserve_id, $hold_2->reserve_id ] } )
|
||||
->status_is( 201, 'REST3.2.1' )->json_has( "/holds", "holds object correctly embedded" );
|
||||
|
||||
$t->post_ok( "//$userid:$password@/api/v1/patrons/"
|
||||
. $patron->id
|
||||
. "/hold_groups" => json => { hold_ids => [ $hold_1->reserve_id, $hold_2->reserve_id ] } )
|
||||
->status_is( 400, 'REST3.2.1' )->json_is(
|
||||
{
|
||||
error => "One or more holds already belong to a hold group: "
|
||||
. $hold_1->reserve_id . ", "
|
||||
. $hold_2->reserve_id,
|
||||
error_code => "HoldAlreadyBelongsToHoldGroup",
|
||||
hold_ids => [ $hold_1->reserve_id, $hold_2->reserve_id ]
|
||||
}
|
||||
);
|
||||
|
||||
$schema->storage->txn_rollback;
|
||||
};
|
||||
Loading…
Reference in New Issue