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

(-)a/Koha/REST/V1/Patron.pm (+125 lines)
Lines 16-26 package Koha::REST::V1::Patron; Link Here
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
use Switch;
19
20
20
use Mojo::Base 'Mojolicious::Controller';
21
use Mojo::Base 'Mojolicious::Controller';
21
22
22
use C4::Auth qw( haspermission );
23
use C4::Auth qw( haspermission );
24
use Koha::AuthUtils qw(hash_password);
23
use Koha::Patrons;
25
use Koha::Patrons;
26
use Koha::Patron::Categories;
27
use Koha::Libraries;
24
28
25
sub list {
29
sub list {
26
    my ($c, $args, $cb) = @_;
30
    my ($c, $args, $cb) = @_;
Lines 55-58 sub get { Link Here
55
    return $c->$cb($patron->unblessed, 200);
59
    return $c->$cb($patron->unblessed, 200);
56
}
60
}
57
61
62
sub add {
63
    my ($c, $args, $cb) = @_;
64
65
    my $user = $c->stash('koha.user');
66
67
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
68
        return $c->$cb({error => "You don't have the required permission"}, 403);
69
    }
70
71
    my $body = $c->req->json;
72
73
    # patron cardnumber and/or userid unique?
74
    if ($body->{cardnumber} || $body->{userid}) {
75
        my $patron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
76
        if ($patron) {
77
            return $c->$cb({
78
                error => "Patron cardnumber and userid must be unique",
79
                conflict => { cardnumber => $patron->cardnumber, userid => $patron->userid }
80
            }, 409);
81
        }
82
    }
83
84
    my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
85
    unless ($branch) {
86
        return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
87
    }
88
    my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
89
    unless ($category) {
90
        return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
91
    }
92
    # All OK - save new patron
93
94
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
95
    my $patron = Koha::Patron->new($body)->store;
96
97
    unless ($patron) {
98
        return $c->$cb({error => "Something went wrong"}, 404);
99
    }
100
101
    return $c->$cb($patron->unblessed, 201);
102
}
103
104
sub edit {
105
    my ($c, $args, $cb) = @_;
106
107
    my $user = $c->stash('koha.user');
108
109
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
110
        return $c->$cb({error => "You don't have the required permission"}, 403);
111
    }
112
113
    my $patron = Koha::Patrons->find($args->{borrowernumber});
114
115
    unless ($patron) {
116
        return $c->$cb({error => "Patron not found"}, 404);
117
    }
118
119
    my $body = $c->req->json;
120
121
    # Can we change userid and/or cardnumber? in that case check that they are altered first
122
    if ($body->{cardnumber} || $body->{userid}) {
123
        if ( ($body->{cardnumber} && $body->{cardnumber} ne $patron->cardnumber) || ($body->{userid} && $body->{userid} ne $patron->userid) ) {
124
            my $conflictingPatron = Koha::Patrons->find({cardnumber => $body->{cardnumber}, userid => $body->{userid} });
125
            if ($conflictingPatron) {
126
                return $c->$cb({
127
                    error => "Patron cardnumber and userid must be unique",
128
                    conflict => { cardnumber => $conflictingPatron->cardnumber, userid => $conflictingPatron->userid }
129
                }, 409);
130
            }
131
        }
132
    }
133
134
    if ($body->{branchcode}) {
135
        my $branch = Koha::Libraries->find({branchcode => $body->{branchcode} });
136
        unless ($branch) {
137
            return $c->$cb({error => "Library with branchcode \"" . $body->{branchcode} . "\" does not exist"}, 404);
138
        }
139
    }
140
141
    if ($body->{categorycode}) {
142
        my $category = Koha::Patron::Categories->find({ categorycode => $body->{categorycode} });
143
        unless ($category) {
144
            return $c->$cb({error => "Patron category \"" . $body->{categorycode} . "\" does not exist"}, 404);
145
        }
146
    }
147
    # ALL OK - Update patron
148
    # Perhaps limit/validate what should be updated here? flags, et.al.
149
    if ($body->{password}) { $body->{password} = hash_password($body->{password}) }; # bcrypt password if given
150
    my $updatedpatron = $patron->set($body);
151
    if ($updatedpatron->is_changed) {
152
        my $res = $updatedpatron->store;
153
        return $c->$cb($res->unblessed, 200);
154
    } else {
155
        return $c->$cb({}, 204); # No Content = No changes made
156
    }
157
}
158
159
sub delete {
160
    my ($c, $args, $cb) = @_;
161
    my $user = $c->stash('koha.user');
162
163
    unless ( $user && haspermission($user->userid, {borrowers => 1}) ) {
164
        return $c->$cb({error => "You don't have the required permission"}, 403);
165
    }
166
167
    my $patron = Koha::Patrons->find($args->{borrowernumber});
168
169
    unless ($patron) {
170
        return $c->$cb({error => "Patron not found"}, 404);
171
    }
172
173
    # check if loans, reservations, debarrment, etc. before deletion!
174
    my $res = $patron->delete;
175
    switch ($res) {
176
        case '1'  { return $c->$cb({}, 200); }
177
        case '0'  { return $c->$cb({}, 400); }
178
        case '-1' { return $c->$cb({}, 404); }
179
        else      { return $c->$cb({}, 400); }
180
    }
181
}
182
58
1;
183
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
  },
214
  },
(-)a/t/db_dependent/api/v1/patrons.t (-15 / +120 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 => 50;
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 49-55 my $borrower = $builder->build({ Link Here
49
$t->get_ok('/api/v1/patrons')
53
$t->get_ok('/api/v1/patrons')
50
  ->status_is(403);
54
  ->status_is(403);
51
55
52
$t->get_ok("/api/v1/patrons/" . $borrower->{ borrowernumber })
56
$t->get_ok("/api/v1/patrons/" . $patron->{ borrowernumber })
53
  ->status_is(403);
57
  ->status_is(403);
54
58
55
my $loggedinuser = $builder->build({
59
my $loggedinuser = $builder->build({
Lines 74-84 $tx->req->env({REMOTE_ADDR => '127.0.0.1'}); Link Here
74
$t->request_ok($tx)
78
$t->request_ok($tx)
75
  ->status_is(200);
79
  ->status_is(200);
76
80
77
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $borrower->{ borrowernumber });
81
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . $patron->{ borrowernumber });
78
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
82
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
79
$t->request_ok($tx)
83
$t->request_ok($tx)
80
  ->status_is(200)
84
  ->status_is(200)
81
  ->json_is('/borrowernumber' => $borrower->{ borrowernumber })
85
  ->json_is('/borrowernumber' => $patron->{ borrowernumber })
82
  ->json_is('/surname' => $borrower->{ surname });
86
  ->json_is('/surname' => $patron->{ surname });
87
88
### POST /api/v1/patrons
89
90
my $newpatron = {
91
  branchcode   => $branchcode,
92
  categorycode => $categorycode,
93
  surname      => "TestUser",
94
  cardnumber => "123456",
95
  userid => "testuser"
96
};
97
98
$newpatron->{ branchcode } = "nonexistent"; # Test invalid branchcode
99
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
100
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
101
$t->request_ok($tx)
102
  ->status_is(404)
103
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
104
105
$newpatron->{ branchcode } = $branchcode;
106
$newpatron->{ categorycode } = "nonexistent"; # Test invalid patron category
107
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
108
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
109
$t->request_ok($tx)
110
  ->status_is(404)
111
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
112
113
$newpatron->{ categorycode } = $categorycode;
114
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
115
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
116
$t->request_ok($tx)
117
  ->status_is(201, 'Patron created successfully')
118
  ->json_has('/borrowernumber', 'got a borrowernumber')
119
  ->json_is('/cardnumber', $newpatron->{ cardnumber })
120
  ->json_is('/surname' => $newpatron->{ surname })
121
  ->json_is('/firstname' => $newpatron->{ firstname });
122
$newpatron->{borrowernumber} = $tx->res->json->{borrowernumber};
123
124
$tx = $t->ua->build_tx(POST => "/api/v1/patrons" => json => $newpatron);
125
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
126
$t->request_ok($tx)
127
  ->status_is(409)
128
  ->json_has('/error', 'Fails when trying to POST duplicate cardnumber or userid')
129
  ->json_has('/conflict', { userid => $newpatron->{ userid }, cardnumber => $newpatron->{ cardnumber } });
130
131
### PUT /api/v1/patrons
132
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/0" => json => {});
133
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
134
$t->request_ok($tx)
135
  ->status_is(404)
136
  ->json_has('/error', 'Fails when trying to PUT nonexistent patron');
137
138
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {categorycode => "nonexistent"});
139
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
140
$t->request_ok($tx)
141
  ->status_is(404)
142
  ->json_is('/error' => "Patron category \"nonexistent\" does not exist");
143
144
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => {branchcode => "nonexistent"});
145
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
146
$t->request_ok($tx)
147
  ->status_is(404)
148
  ->json_is('/error' => "Library with branchcode \"nonexistent\" does not exist");
149
150
$newpatron->{ cardnumber } = $patron-> { cardnumber };
151
$newpatron->{ userid } = $patron-> { userid };
152
153
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
154
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
155
$t->request_ok($tx)
156
  ->status_is(409)
157
  ->json_has('/error' => "Fails when trying to update to an existing cardnumber or userid")
158
  ->json_has('/conflict', { cardnumber => $patron->{ cardnumber }, userid => $patron->{ userid } });
159
160
$newpatron->{ cardnumber } = "123456";
161
$newpatron->{ userid } = "testuser";
162
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
163
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
164
$t->request_ok($tx)
165
  ->status_is(204, 'No changes - patron NOT updated');
166
167
$newpatron->{ cardnumber } = "234567";
168
$newpatron->{ userid } = "updatedtestuser";
169
$newpatron->{ surname } = "UpdatedTestUser";
170
171
$tx = $t->ua->build_tx(PUT => "/api/v1/patrons/" . $newpatron->{ borrowernumber } => json => $newpatron);
172
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
173
$t->request_ok($tx)
174
  ->status_is(200, 'Patron updated successfully')
175
  ->json_has($newpatron);
176
177
### DELETE /api/v1/patrons
178
179
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/0");
180
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
181
$t->request_ok($tx)
182
  ->status_is(404, 'Patron not found');
183
184
$tx = $t->ua->build_tx(DELETE => "/api/v1/patrons/" . $newpatron->{ borrowernumber });
185
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
186
$t->request_ok($tx)
187
  ->status_is(200, 'Patron deleted successfully');
188
189
$schema->storage->txn_rollback;
83
190
84
$dbh->rollback;
85
- 

Return to bug 16330