View | Details | Raw Unified | Return to bug 40517
Collapse All | Expand All

(-)a/Koha/HoldGroup.pm (+32 lines)
Lines 31-36 Koha::HoldGroup - Koha Hold Group object class Link Here
31
31
32
=cut
32
=cut
33
33
34
=head3 to_api
35
36
    my $json = $hold_group->to_api;
37
38
Overloaded method that returns a JSON representation of the Koha::HoldGroup object,
39
suitable for API output. The related Koha::Holds objects are merged as expected
40
on the API.
41
42
=cut
43
44
sub to_api {
45
    my ( $self, $args ) = @_;
46
47
    my $json_hold_group = $self->SUPER::to_api($args);
48
    return unless $json_hold_group;
49
50
    $args = defined $args ? {%$args} : {};
51
    delete $args->{embed};
52
53
    my $holds = $self->holds;
54
55
    Koha::Exceptions::RelatedObjectNotFound->throw( accessor => 'holds', class => 'Koha::HoldGroup' )
56
        unless $holds;
57
58
    my @json_holds;
59
    for my $hold ($holds) {
60
        push @json_holds, $hold->to_api($args);
61
    }
62
63
    return { %$json_hold_group, holds => @json_holds };
64
}
65
34
=head3 holds
66
=head3 holds
35
67
36
    $holds = $hold_group->holds
68
    $holds = $hold_group->holds
(-)a/Koha/REST/V1/Patrons/HoldGroups.pm (+129 lines)
Line 0 Link Here
1
package Koha::REST::V1::Patrons::HoldGroups;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Koha::HoldGroup;
23
use Koha::Patrons;
24
25
use Try::Tiny;
26
27
=head1 NAME
28
29
Koha::REST::V1::Patrons::HoldGroups
30
31
=head1 API
32
33
=head2 Methods
34
35
=head3 list
36
37
Controller function that handles listing Koha::HoldGroup objects for the requested patron
38
39
=cut
40
41
sub list {
42
    my $c = shift->openapi->valid_input or return;
43
44
    my $patron = Koha::Patrons->find( $c->param('patron_id') );
45
46
    return $c->render_resource_not_found("Patron")
47
        unless $patron;
48
49
    return try {
50
        my $hold_groups_set = $patron->hold_groups;
51
52
        my $hold_groups = $c->objects->search($hold_groups_set);
53
        return $c->render( status => 200, openapi => $hold_groups );
54
    } catch {
55
        $c->unhandled_exception($_);
56
    };
57
}
58
59
=head3 add
60
61
Controller function that handles adding Koha::HoldGroup objects for the requested patron
62
63
=cut
64
65
sub add {
66
    my $c = shift->openapi->valid_input or return;
67
68
    my $patron = Koha::Patrons->find( $c->param('patron_id') );
69
70
    return $c->render_resource_not_found("Patron")
71
        unless $patron;
72
73
    my $body          = $c->req->json;
74
    my $hold_ids      = $body->{hold_ids};
75
    my $force_grouped = $body->{force_grouped} || 0;
76
77
    return try {
78
        my $hold_group = $patron->create_hold_group( $hold_ids, $force_grouped );
79
        $c->res->headers->location( $c->req->url->to_string . '/' . $hold_group->hold_group_id );
80
        return $c->render(
81
            status  => 201,
82
            openapi => $c->objects->to_api($hold_group),
83
        );
84
    } catch {
85
        if ( blessed $_ ) {
86
            if ( $_->isa('Koha::Exceptions::HoldGroup::HoldDoesNotExist') ) {
87
                return $c->render(
88
                    status  => 400,
89
                    openapi => {
90
                        error      => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
91
                        error_code => 'HoldDoesNotExist',
92
                        hold_ids   => $_->hold_ids
93
                    }
94
                );
95
            } elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldDoesNotBelongToPatron') ) {
96
                return $c->render(
97
                    status  => 400,
98
                    openapi => {
99
                        error      => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
100
                        error_code => 'HoldDoesNotBelongToPatron',
101
                        hold_ids   => $_->hold_ids
102
                    }
103
                );
104
            } elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldHasAlreadyBeenFound') ) {
105
                return $c->render(
106
                    status  => 400,
107
                    openapi => {
108
                        error      => $_->description . ": " . join( ', ', @{ $_->barcodes } ),
109
                        error_code => 'HoldHasAlreadyBeenFound',
110
                        barcodes   => $_->barcodes
111
                    }
112
                );
113
            } elsif ( $_->isa('Koha::Exceptions::HoldGroup::HoldAlreadyBelongsToHoldGroup') ) {
114
                return $c->render(
115
                    status  => 400,
116
                    openapi => {
117
                        error      => $_->description . ": " . join( ', ', @{ $_->hold_ids } ),
118
                        error_code => 'HoldAlreadyBelongsToHoldGroup',
119
                        hold_ids   => $_->hold_ids
120
                    }
121
                );
122
123
            }
124
        }
125
        $c->unhandled_exception($_);
126
    };
127
}
128
129
1;
(-)a/api/v1/swagger/definitions/hold_group.yaml (+16 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  hold_group_id:
5
    type: integer
6
    description: Internal hold group identifier
7
  borrowernumber:
8
    type: integer
9
    description: Patron this hold group belongs to
10
  holds:
11
    type:
12
      - array
13
      - "null"
14
    description: The holds in this hold group (x-koha-embed)
15
16
additionalProperties: false
(-)a/api/v1/swagger/paths/patrons_hold_groups.yaml (+155 lines)
Line 0 Link Here
1
---
2
"/patrons/{patron_id}/hold_groups":
3
  get:
4
    x-mojo-to: Patrons::HoldGroups#list
5
    operationId: getPatronHoldGroups
6
    tags:
7
      - hold_groups
8
    summary: List hold groups for a patron
9
    parameters:
10
      - $ref: "../swagger.yaml#/parameters/patron_id_pp"
11
      - $ref: "../swagger.yaml#/parameters/match"
12
      - $ref: "../swagger.yaml#/parameters/order_by"
13
      - $ref: "../swagger.yaml#/parameters/page"
14
      - $ref: "../swagger.yaml#/parameters/per_page"
15
      - $ref: "../swagger.yaml#/parameters/q_param"
16
      - $ref: "../swagger.yaml#/parameters/q_body"
17
      - $ref: "../swagger.yaml#/parameters/request_id_header"
18
      - name: x-koha-embed
19
        in: header
20
        required: false
21
        description: Embed list sent as a request header
22
        type: array
23
        items:
24
          type: string
25
          enum:
26
            - holds
27
        collectionFormat: csv
28
    produces:
29
      - application/json
30
    responses:
31
      "200":
32
        description: The patron hold groups
33
        schema:
34
          type: array
35
          items:
36
            $ref: "../swagger.yaml#/definitions/hold_group"
37
      "400":
38
        description: |
39
          Bad request. Possible `error_code` attribute values:
40
41
            * `invalid_query`
42
        schema:
43
          $ref: "../swagger.yaml#/definitions/error"
44
      "401":
45
        description: Authentication required
46
        schema:
47
          $ref: "../swagger.yaml#/definitions/error"
48
      "403":
49
        description: Access forbidden
50
        schema:
51
          $ref: "../swagger.yaml#/definitions/error"
52
      "404":
53
        description: Patron not found
54
        schema:
55
          $ref: "../swagger.yaml#/definitions/error"
56
      "500":
57
        description: |
58
          Internal server error. Possible `error_code` attribute values:
59
60
          * `internal_server_error`
61
        schema:
62
          $ref: "../swagger.yaml#/definitions/error"
63
      "503":
64
        description: Under maintenance
65
        schema:
66
          $ref: "../swagger.yaml#/definitions/error"
67
    x-koha-authorization:
68
      permissions:
69
        borrowers: edit_borrowers
70
  post:
71
    x-mojo-to: Patrons::HoldGroups#add
72
    operationId: addPatronHoldGroups
73
    tags:
74
      - hold_groups
75
    summary: Add hold group. May contain hold ids of existing holds
76
    parameters:
77
      - name: body
78
        in: body
79
        description: A JSON object containing informations about the new hold group
80
        required: true
81
        schema:
82
          type: object
83
          properties:
84
            hold_ids:
85
              description: List of holds to add to hold group
86
              type:
87
                - array
88
                - "null"
89
            force_grouped:
90
              description: Whether to force creation of a new hold group
91
              type:
92
                - boolean
93
                - "null"
94
          additionalProperties: false
95
      - name: x-koha-embed
96
        in: header
97
        required: false
98
        description: Embed list sent as a request header
99
        type: array
100
        items:
101
          type: string
102
          enum:
103
            - holds
104
        collectionFormat: csv
105
    consumes:
106
      - application/json
107
    produces:
108
      - application/json
109
    responses:
110
      "201":
111
        description: Created hold group
112
        schema:
113
          $ref: "../swagger.yaml#/definitions/hold_group"
114
      "400":
115
        description: Bad request
116
        schema:
117
          $ref: "../swagger.yaml#/definitions/error"
118
      "401":
119
        description: Authentication required
120
        schema:
121
          $ref: "../swagger.yaml#/definitions/error"
122
      "403":
123
        description: Hold not allowed
124
        schema:
125
          $ref: "../swagger.yaml#/definitions/error"
126
      "404":
127
        description: Borrower not found
128
        schema:
129
          $ref: "../swagger.yaml#/definitions/error"
130
      "409":
131
        description: |
132
          Conflict. Possible `error_code` attribute values:
133
134
            * `bad_address`
135
            * `card_lost`
136
            * `debt_limit`
137
            * `expired`
138
            * `hold_limit`
139
            * `restricted`
140
        schema:
141
          $ref: "../swagger.yaml#/definitions/error"
142
      "500":
143
        description: |
144
          Internal server error. Possible `error_code` attribute values:
145
146
          * `internal_server_error`
147
        schema:
148
          $ref: "../swagger.yaml#/definitions/error"
149
      "503":
150
        description: Under maintenance
151
        schema:
152
          $ref: "../swagger.yaml#/definitions/error"
153
    x-koha-authorization:
154
      permissions:
155
        reserveforothers: "1"
(-)a/api/v1/swagger/swagger.yaml (+4 lines)
Lines 98-103 definitions: Link Here
98
    $ref: ./definitions/hold.yaml
98
    $ref: ./definitions/hold.yaml
99
  holds:
99
  holds:
100
    $ref: ./definitions/holds.yaml
100
    $ref: ./definitions/holds.yaml
101
  hold_group:
102
    $ref: ./definitions/hold_group.yaml
101
  ill_backend:
103
  ill_backend:
102
    $ref: ./definitions/ill_backend.yaml
104
    $ref: ./definitions/ill_backend.yaml
103
  ill_backends:
105
  ill_backends:
Lines 481-486 paths: Link Here
481
    $ref: "./paths/patrons_extended_attributes.yaml#/~1patrons~1{patron_id}~1extended_attributes~1{extended_attribute_id}"
483
    $ref: "./paths/patrons_extended_attributes.yaml#/~1patrons~1{patron_id}~1extended_attributes~1{extended_attribute_id}"
482
  "/patrons/{patron_id}/holds":
484
  "/patrons/{patron_id}/holds":
483
    $ref: "./paths/patrons_holds.yaml#/~1patrons~1{patron_id}~1holds"
485
    $ref: "./paths/patrons_holds.yaml#/~1patrons~1{patron_id}~1holds"
486
  "/patrons/{patron_id}/hold_groups":
487
    $ref: "./paths/patrons_hold_groups.yaml#/~1patrons~1{patron_id}~1hold_groups"
484
  "/patrons/{patron_id}/checkouts":
488
  "/patrons/{patron_id}/checkouts":
485
    $ref: "./paths/patrons_checkouts.yaml#/~1patrons~1{patron_id}~1checkouts"
489
    $ref: "./paths/patrons_checkouts.yaml#/~1patrons~1{patron_id}~1checkouts"
486
  "/patrons/{patron_id}/password":
490
  "/patrons/{patron_id}/password":
(-)a/t/db_dependent/api/v1/patrons_hold_groups.t (-1 / +173 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/env perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::NoWarnings;
21
use Test::More tests => 3;
22
use Test::MockModule;
23
use Test::Mojo;
24
25
use t::lib::TestBuilder;
26
use t::lib::Mocks;
27
28
use Koha::Database;
29
30
my $schema  = Koha::Database->new->schema;
31
my $builder = t::lib::TestBuilder->new();
32
33
my $t = Test::Mojo->new('Koha::REST::V1');
34
35
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
36
37
subtest 'list() tests' => sub {
38
39
    plan tests => 6;
40
41
    $schema->storage->txn_begin;
42
43
    my $patron = $builder->build_object(
44
        {
45
            class => 'Koha::Patrons',
46
            value => { flags => 2**4 }    # 'borrowers' flag == 4
47
        }
48
    );
49
    my $password = 'thePassword123';
50
    $patron->set_password( { password => $password, skip_validation => 1 } );
51
    my $userid = $patron->userid;
52
53
    $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/hold_groups' )->status_is( 200, 'REST3.2.2' )
54
        ->json_is( [] );
55
56
    my $hold_group_1 = $builder->build_object(
57
        {
58
            class => 'Koha::HoldGroups',
59
            value => { borrowernumber => $patron->id }
60
        }
61
    );
62
    my $hold_1 = $builder->build_object(
63
        {
64
            class => 'Koha::Holds',
65
            value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
66
        }
67
    );
68
    my $hold_2 = $builder->build_object(
69
        {
70
            class => 'Koha::Holds',
71
            value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
72
        }
73
    );
74
    my $hold_3 = $builder->build_object(
75
        {
76
            class => 'Koha::Holds',
77
            value => { borrowernumber => $patron->id, hold_group_id => $hold_group_1->hold_group_id }
78
        }
79
    );
80
81
    $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/hold_groups' => { 'x-koha-embed' => 'holds' } )
82
        ->status_is( 200, 'REST3.2.2' )->json_has( "/0/holds", "holds object correctly embedded" );
83
84
    $schema->storage->txn_rollback;
85
};
86
87
subtest 'add() tests' => sub {
88
89
    plan tests => 15;
90
91
    $schema->storage->txn_begin;
92
93
    my $patron = $builder->build_object(
94
        {
95
            class => 'Koha::Patrons',
96
            value => { flags => 2**6 }    # reserveforothers flag = 6
97
        }
98
    );
99
    my $other_patron = $builder->build_object(
100
        {
101
            class => 'Koha::Patrons',
102
            value => { flags => 2**6 }    # reserveforothers flag = 6
103
        }
104
    );
105
    my $password = 'thePassword123';
106
    $patron->set_password( { password => $password, skip_validation => 1 } );
107
    my $userid = $patron->userid;
108
109
    my $hold_1 = $builder->build_object(
110
        {
111
            class => 'Koha::Holds',
112
            value => { borrowernumber => $patron->id, hold_group_id => undef, found => undef }
113
        }
114
    );
115
    my $hold_2 = $builder->build_object(
116
        {
117
            class => 'Koha::Holds',
118
            value => { borrowernumber => $patron->id, hold_group_id => undef, found => undef }
119
        }
120
    );
121
    my $hold_3 = $builder->build_object(
122
        {
123
            class => 'Koha::Holds',
124
            value => { borrowernumber => $other_patron->id, hold_group_id => undef, found => 'W' }
125
        }
126
    );
127
128
    $t->post_ok(
129
        "//$userid:$password@/api/v1/patrons/" . $patron->id . "/hold_groups" => json => { hold_ids => [333] } )
130
        ->status_is( 400, 'REST3.2.1' )
131
        ->json_is(
132
        { error => 'One or more holds do not exist: 333', error_code => "HoldDoesNotExist", hold_ids => [333] } );
133
134
    $t->post_ok( "//$userid:$password@/api/v1/patrons/"
135
            . $other_patron->id
136
            . "/hold_groups" => json => { hold_ids => [ $hold_3->reserve_id ] } )->status_is( 400, 'REST3.2.1' )
137
        ->json_is(
138
        {
139
            error      => 'One or more holds have already been found: ' . $hold_3->item->barcode,
140
            error_code => "HoldHasAlreadyBeenFound", barcodes => [ $hold_3->item->barcode ]
141
        }
142
        );
143
144
    $t->post_ok( "//$userid:$password@/api/v1/patrons/"
145
            . $patron->id
146
            . "/hold_groups" => json => { hold_ids => [ $hold_3->reserve_id ] } )->status_is( 400, 'REST3.2.1' )
147
        ->json_is(
148
        {
149
            error      => 'One or more holds do not belong to patron: ' . $hold_3->reserve_id,
150
            error_code => "HoldDoesNotBelongToPatron", hold_ids => [ $hold_3->reserve_id ]
151
        }
152
        );
153
154
    $t->post_ok( "//$userid:$password@/api/v1/patrons/"
155
            . $patron->id
156
            . "/hold_groups" => json => { hold_ids => [ $hold_1->reserve_id, $hold_2->reserve_id ] } )
157
        ->status_is( 201, 'REST3.2.1' )->json_has( "/holds", "holds object correctly embedded" );
158
159
    $t->post_ok( "//$userid:$password@/api/v1/patrons/"
160
            . $patron->id
161
            . "/hold_groups" => json => { hold_ids => [ $hold_1->reserve_id, $hold_2->reserve_id ] } )
162
        ->status_is( 400, 'REST3.2.1' )->json_is(
163
        {
164
                  error => "One or more holds already belong to a hold group: "
165
                . $hold_1->reserve_id . ", "
166
                . $hold_2->reserve_id,
167
            error_code => "HoldAlreadyBelongsToHoldGroup",
168
            hold_ids   => [ $hold_1->reserve_id, $hold_2->reserve_id ]
169
        }
170
        );
171
172
    $schema->storage->txn_rollback;
173
};

Return to bug 40517