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

(-)a/Koha/REST/V1/Patron.pm (-1 / +156 lines)
Lines 20-26 use Modern::Perl; Link Here
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use C4::Auth qw( haspermission );
22
use C4::Auth qw( haspermission );
23
use Koha::AuthUtils qw(hash_password);
23
use Koha::Patrons;
24
use Koha::Patrons;
25
use Koha::Patron::Categories;
26
use Koha::Libraries;
24
27
25
sub list {
28
sub list {
26
    my ($c, $args, $cb) = @_;
29
    my ($c, $args, $cb) = @_;
Lines 30-36 sub list { Link Here
30
        return $c->$cb({error => "You don't have the required permission"}, 403);
33
        return $c->$cb({error => "You don't have the required permission"}, 403);
31
    }
34
    }
32
35
33
    my $patrons = Koha::Patrons->search;
36
    my $params = $c->req->query_params->to_hash;
37
    my $patrons;
38
    if (keys %$params) {
39
        my @valid_params = Koha::Patrons->_resultset->result_source->columns;
40
        foreach my $key (keys %$params) {
41
            delete $params->{$key} unless grep { $key eq $_ } @valid_params;
42
        }
43
        $patrons = Koha::Patrons->search($params);
44
    } else {
45
        $patrons = Koha::Patrons->search;
46
    }
34
47
35
    $c->$cb($patrons->unblessed, 200);
48
    $c->$cb($patrons->unblessed, 200);
36
}
49
}
Lines 55-58 sub get { Link Here
55
    return $c->$cb($patron->unblessed, 200);
68
    return $c->$cb($patron->unblessed, 200);
56
}
69
}
57
70
71
sub add {
72
    my ($c, $args, $cb) = @_;
73
74
    my $user = $c->stash('koha.user');
75
76
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
77
        return $c->$cb({error => "You don't have the required permission"}, 403);
78
    }
79
80
    my $body = $c->req->json;
81
82
    # patron cardnumber and/or userid unique?
83
    if ($body->{cardnumber} || $body->{userid}) {
84
        my $patron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
85
        if ($patron) {
86
            return $c->$cb({
87
                error => "Patron cardnumber and userid must be unique",
88
                conflict => { cardnumber => $patron->cardnumber, userid => $patron->userid }
89
            }, 409);
90
        }
91
    }
92
93
    my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
94
    unless ($branch) {
95
        return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
96
    }
97
    my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
98
    unless ($category) {
99
        return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
100
    }
101
    # All OK - save new patron
102
103
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
104
105
    my $patron = eval {
106
        Koha::Patron->new($body)->store;
107
    };
108
109
    unless ($patron) {
110
        return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
111
    }
112
113
    return $c->$cb($patron->unblessed, 201);
114
}
115
116
sub edit {
117
    my ($c, $args, $cb) = @_;
118
119
    my $user = $c->stash('koha.user');
120
121
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
122
        return $c->$cb({error => "You don't have the required permission"}, 403);
123
    }
124
125
    my $patron = Koha::Patrons->find($args->{borrowernumber});
126
127
    unless ($patron) {
128
        return $c->$cb({error => "Patron not found"}, 404);
129
    }
130
131
    my $body = $c->req->json;
132
133
    # Can we change userid and/or cardnumber? in that case check that they are altered first
134
    if ($body->{cardnumber} || $body->{userid}) {
135
        if ( ($body->{cardnumber} && $body->{cardnumber} ne $patron->cardnumber) || ($body->{userid} && $body->{userid} ne $patron->userid) ) {
136
            my $conflictingPatron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
137
            if ($conflictingPatron) {
138
                return $c->$cb({
139
                    error => "Patron cardnumber and userid must be unique",
140
                    conflict => { cardnumber => $conflictingPatron->cardnumber, userid => $conflictingPatron->userid }
141
                }, 409);
142
            }
143
        }
144
    }
145
146
    if ($body->{branchcode}) {
147
        my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
148
        unless ($branch) {
149
            return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
150
        }
151
    }
152
153
    if ($body->{categorycode}) {
154
        my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
155
        unless ($category) {
156
            return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
157
        }
158
    }
159
    # ALL OK - Update patron
160
    # Perhaps limit/validate what should be updated here? flags, et.al.
161
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
162
163
    my $updatedpatron = eval {
164
        $patron->set($body);
165
    };
166
167
    if ($updatedpatron) {
168
        if ($updatedpatron->is_changed) {
169
170
            my $res = eval {
171
                $updatedpatron->store;
172
            };
173
174
            unless ($res) {
175
                return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
176
            }
177
            return $c->$cb($res->unblessed, 200);
178
179
        } else {
180
            return $c->$cb({}, 204); # No Content = No changes made
181
        }
182
    } else {
183
        return $c->$cb({error => "Something went wrong, check Koha logs for details"}, 500);
184
    }
185
}
186
187
sub delete {
188
    my ($c, $args, $cb) = @_;
189
    my $user = $c->stash('koha.user');
190
191
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
192
        return $c->$cb({error => "You don't have the required permission"}, 403);
193
    }
194
195
    my $patron = Koha::Patrons->find($args->{borrowernumber});
196
197
    unless ($patron) {
198
        return $c->$cb({error => "Patron not found"}, 404);
199
    }
200
201
    # check if loans, reservations, debarrment, etc. before deletion!
202
    my $res = $patron->delete;
203
204
    if ($res eq '1') {
205
        return $c->$cb({}, 200);
206
    } elsif ($res eq '-1') {
207
        return $c->$cb({}, 404);
208
    } else {
209
        return $c->$cb({}, 400);
210
    }
211
}
212
58
1;
213
1;
(-)a/api/v1/swagger.json (+137 lines)
Lines 38-43 Link Here
38
            }
38
            }
39
          }
39
          }
40
        }
40
        }
41
      },
42
      "post": {
43
        "operationId": "addPatron",
44
        "tags": ["patrons"],
45
        "parameters": [{
46
          "name": "body",
47
          "in": "body",
48
          "description": "A JSON object containing information about the new patron",
49
          "required": true,
50
          "schema": {
51
            "$ref": "#/definitions/patron"
52
          }
53
        }],
54
        "consumes": ["application/json"],
55
        "produces": ["application/json"],
56
        "responses": {
57
          "201": {
58
            "description": "A successfully created patron",
59
            "schema": {
60
              "items": {
61
                "$ref": "#/definitions/patron"
62
              }
63
            }
64
          },
65
          "403": {
66
            "description": "Access forbidden",
67
            "schema": {
68
              "$ref": "#/definitions/error"
69
            }
70
          },
71
          "404": {
72
            "description": "Resource not found",
73
            "schema": {
74
              "$ref": "#/definitions/error"
75
            }
76
          },
77
          "409": {
78
            "description": "Conflict in creating resource",
79
            "schema": {
80
              "$ref": "#/definitions/error"
81
            }
82
          },
83
          "500": {
84
            "description": "Internal error",
85
            "schema": {
86
              "$ref": "#/definitions/error"
87
            }
88
          }
89
        }
41
      }
90
      }
42
    },
91
    },
43
    "/patrons/{borrowernumber}": {
92
    "/patrons/{borrowernumber}": {
Lines 72-77 Link Here
72
            }
121
            }
73
          }
122
          }
74
        }
123
        }
124
      },
125
      "put": {
126
        "operationId": "editPatron",
127
        "tags": ["patrons"],
128
        "parameters": [
129
          { "$ref": "#/parameters/borrowernumberPathParam" },
130
          {
131
            "name": "body",
132
            "in": "body",
133
            "description": "A JSON object containing new information about existing patron",
134
            "required": true,
135
            "schema": {
136
              "$ref": "#/definitions/patron"
137
            }
138
          }
139
        ],
140
        "consumes": ["application/json"],
141
        "produces": ["application/json"],
142
        "responses": {
143
          "200": {
144
            "description": "A successfully updated patron",
145
            "schema": {
146
              "items": {
147
                "$ref": "#/definitions/patron"
148
              }
149
            }
150
          },
151
          "204": {
152
            "description": "No Content",
153
            "schema": {
154
              "type": "object"
155
            }
156
          },
157
          "403": {
158
            "description": "Access forbidden",
159
            "schema": {
160
              "$ref": "#/definitions/error"
161
            }
162
          },
163
          "404": {
164
            "description": "Resource not found",
165
            "schema": {
166
              "$ref": "#/definitions/error"
167
            }
168
          },
169
          "409": {
170
            "description": "Conflict in updating resource",
171
            "schema": {
172
              "$ref": "#/definitions/error"
173
            }
174
          },
175
          "500": {
176
            "description": "Internal error",
177
            "schema": {
178
              "$ref": "#/definitions/error"
179
            }
180
          }
181
        }
182
      },
183
      "delete": {
184
        "operationId": "deletePatron",
185
        "tags": ["patrons"],
186
        "parameters": [
187
          { "$ref": "#/parameters/borrowernumberPathParam" }
188
        ],
189
        "produces": ["application/json"],
190
        "responses": {
191
          "200": {
192
            "description": "Patron deleted successfully",
193
            "schema": {
194
              "type": "object"
195
            }
196
          },
197
          "400": {
198
            "description": "Patron deletion failed",
199
            "schema": { "$ref": "#/definitions/error" }
200
          },
201
          "403": {
202
            "description": "Access forbidden",
203
            "schema": {
204
              "$ref": "#/definitions/error"
205
            }
206
          },
207
          "404": {
208
            "description": "Patron not found",
209
            "schema": { "$ref": "#/definitions/error" }
210
          }
211
        }
75
      }
212
      }
76
    },
213
    },
77
    "/holds": {
214
    "/holds": {
(-)a/t/db_dependent/api/v1/patrons.t (-15 / +153 lines)
Lines 17-44 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 10;
21
use Test::Mojo;
22
use t::lib::TestBuilder;
20
use t::lib::TestBuilder;
23
21
22
use Test::More tests => 56;
23
use Test::Mojo;
24
24
use C4::Auth;
25
use C4::Auth;
25
use C4::Context;
26
use C4::Context;
26
27
use Koha::Database;
27
use Koha::Database;
28
use Koha::Patron;
29
28
30
my $builder = t::lib::TestBuilder->new();
29
BEGIN {
30
    use_ok('Koha::Object');
31
    use_ok('Koha::Patron');
32
}
31
33
32
my $dbh = C4::Context->dbh;
34
my $schema  = Koha::Database->schema;
33
$dbh->{AutoCommit} = 0;
35
my $dbh     = C4::Context->dbh;
34
$dbh->{RaiseError} = 1;
36
my $builder = t::lib::TestBuilder->new;
35
37
36
$ENV{REMOTE_ADDR} = '127.0.0.1';
38
$ENV{REMOTE_ADDR} = '127.0.0.1';
37
my $t = Test::Mojo->new('Koha::REST::V1');
39
my $t = Test::Mojo->new('Koha::REST::V1');
38
40
41
$schema->storage->txn_begin;
42
39
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
43
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
40
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
44
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
41
my $borrower = $builder->build({
45
my $patron = $builder->build({
42
    source => 'Borrower',
46
    source => 'Borrower',
43
    value => {
47
    value => {
44
        branchcode   => $branchcode,
48
        branchcode   => $branchcode,
Lines 46-55 my $borrower = $builder->build({ Link Here
46
    }
50
    }
47
});
51
});
48
52
53
### GET /api/v1/patrons
54
49
$t->get_ok('/api/v1/patrons')
55
$t->get_ok('/api/v1/patrons')
50
  ->status_is(403);
56
  ->status_is(403);
51
57
52
$t->get_ok("/api/v1/patrons/" . $borrower->{ borrowernumber })
58
$t->get_ok("/api/v1/patrons/" . $patron->{ borrowernumber })
53
  ->status_is(403);
59
  ->status_is(403);
54
60
55
my $loggedinuser = $builder->build({
61
my $loggedinuser = $builder->build({
Lines 73-84 $tx->req->cookies({name => 'CGISESSID', value => $session->id}); Link Here
73
$tx->req->env({REMOTE_ADDR => '127.0.0.1'});
79
$tx->req->env({REMOTE_ADDR => '127.0.0.1'});
74
$t->request_ok($tx)
80
$t->request_ok($tx)
75
  ->status_is(200);
81
  ->status_is(200);
82
ok(@{$tx->res->json} >= 1, 'Json response lists all when no params given');
83
84
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $patron->{ borrowernumber });
85
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
86
$t->request_ok($tx)
87
  ->status_is(200)
88
  ->json_is('/borrowernumber' => $patron->{ borrowernumber })
89
  ->json_is('/surname' => $patron->{ surname });
90
91
$tx = $t->ua->build_tx(GET => '/api/v1/patrons' => form => {surname => 'nonexistent'});
92
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
93
$tx->req->env({REMOTE_ADDR => '127.0.0.1'});
94
$t->request_ok($tx)
95
  ->status_is(200);
96
ok(@{$tx->res->json} == 0, 'Json response yields no results when params doesnt match');
76
97
77
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $borrower->{ borrowernumber });
98
$tx = $t->ua->build_tx(GET => '/api/v1/patrons' => form => {surname => $patron->{ surname }});
78
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
99
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
100
$tx->req->env({REMOTE_ADDR => '127.0.0.1'});
79
$t->request_ok($tx)
101
$t->request_ok($tx)
80
  ->status_is(200)
102
  ->status_is(200)
81
  ->json_is('/borrowernumber' => $borrower->{ borrowernumber })
103
  ->json_has($patron);
82
  ->json_is('/surname' => $borrower->{ surname });
104
ok(@{$tx->res->json} == 1, 'Json response yields expected results when params match');
105
106
### POST /api/v1/patrons
107
108
my $newpatron = {
109
  branchcode   => $branchcode,
110
  categorycode => $categorycode,
111
  surname      => "TestUser",
112
  cardnumber => "123456",
113
  userid => "testuser"
114
};
115
116
$newpatron->{ branchcode } = "nonexistent"; # Test invalid branchcode
117
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
118
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
119
$t->request_ok($tx)
120
  ->status_is(404)
121
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
122
123
$newpatron->{ branchcode } = $branchcode;
124
$newpatron->{ categorycode } = "nonexistent"; # Test invalid patron category
125
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
126
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
127
$t->request_ok($tx)
128
  ->status_is(404)
129
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
130
$newpatron->{ categorycode } = $categorycode;
131
132
$newpatron->{ falseproperty } = "Non existent property";
133
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
134
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
135
$t->request_ok($tx)
136
  ->status_is(500)
137
  ->json_is('/error' => "Something went wrong, check Koha logs for details");
138
139
delete $newpatron->{ falseproperty };
140
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
141
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
142
$t->request_ok($tx)
143
  ->status_is(201, 'Patron created successfully')
144
  ->json_has('/borrowernumber', 'got a borrowernumber')
145
  ->json_is('/cardnumber', $newpatron->{ cardnumber })
146
  ->json_is('/surname' => $newpatron->{ surname })
147
  ->json_is('/firstname' => $newpatron->{ firstname });
148
$newpatron->{borrowernumber} = $tx->res->json->{borrowernumber};
149
150
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
151
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
152
$t->request_ok($tx)
153
  ->status_is(409)
154
  ->json_has('/error', 'Fails when trying to POST duplicate cardnumber or userid')
155
  ->json_has('/conflict', { userid => $newpatron->{ userid }, cardnumber => $newpatron->{ cardnumber } });
156
157
### PUT /api/v1/patrons
158
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/0" => json => {});
159
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
160
$t->request_ok($tx)
161
  ->status_is(404)
162
  ->json_has('/error', 'Fails when trying to PUT nonexistent patron');
163
164
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {categorycode => "nonexistent"});
165
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
166
$t->request_ok($tx)
167
  ->status_is(404)
168
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
169
170
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {branchcode => "nonexistent"});
171
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
172
$t->request_ok($tx)
173
  ->status_is(404)
174
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
175
176
$newpatron->{ falseproperty } = "Non existent property";
177
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
178
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
179
$t->request_ok($tx)
180
  ->status_is(500)
181
  ->json_is('/error' => "Something went wrong, check Koha logs for details");
182
delete $newpatron->{ falseproperty };
183
184
$newpatron->{ cardnumber } = $patron-> { cardnumber };
185
$newpatron->{ userid } = $patron-> { userid };
186
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
187
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
188
$t->request_ok($tx)
189
  ->status_is(409)
190
  ->json_has('/error' => "Fails when trying to update to an existing cardnumber or userid")
191
  ->json_has('/conflict', { cardnumber => $patron->{ cardnumber }, userid => $patron->{ userid } });
192
193
$newpatron->{ cardnumber } = "123456";
194
$newpatron->{ userid } = "testuser";
195
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
196
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
197
$t->request_ok($tx)
198
  ->status_is(204, 'No changes - patron NOT updated');
199
200
$newpatron->{ cardnumber } = "234567";
201
$newpatron->{ userid } = "updatedtestuser";
202
$newpatron->{ surname } = "UpdatedTestUser";
203
204
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
205
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
206
$t->request_ok($tx)
207
  ->status_is(200, 'Patron updated successfully')
208
  ->json_has($newpatron);
209
210
### DELETE /api/v1/patrons
211
212
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/0");
213
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
214
$t->request_ok($tx)
215
  ->status_is(404, 'Patron not found');
216
217
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/" . $newpatron->{ borrowernumber });
218
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
219
$t->request_ok($tx)
220
  ->status_is(200, 'Patron deleted successfully');
221
222
$schema->storage->txn_rollback;
83
223
84
$dbh->rollback;
85
- 

Return to bug 16330