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

(-)a/Koha/REST/V1/Patrons.pm (-1 / +1 lines)
Lines 198-204 sub update { Link Here
198
198
199
        $patron->set($body)->store;
199
        $patron->set($body)->store;
200
        $patron->discard_changes;
200
        $patron->discard_changes;
201
        return $c->render( status => 200, openapi => $patron );
201
        return $c->render( status => 200, openapi => _to_api( $patron->TO_JSON ) );
202
    }
202
    }
203
    catch {
203
    catch {
204
        unless ( blessed $_ && $_->can('rethrow') ) {
204
        unless ( blessed $_ && $_->can('rethrow') ) {
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 32-37 Link Here
32
  "patron_balance": {
32
  "patron_balance": {
33
    "$ref": "definitions/patron_balance.json"
33
    "$ref": "definitions/patron_balance.json"
34
  },
34
  },
35
  "patron_patch": {
36
    "$ref": "definitions/patron_patch.json"
37
  },
35
  "allows_renewal": {
38
  "allows_renewal": {
36
    "$ref": "definitions/allows_renewal.json"
39
    "$ref": "definitions/allows_renewal.json"
37
  },
40
  },
(-)a/api/v1/swagger/definitions/patron_patch.json (+283 lines)
Line 0 Link Here
1
{
2
  "type": "object",
3
  "properties": {
4
    "patron_id": {
5
      "$ref": "../x-primitives.json#/patron_id"
6
    },
7
    "cardnumber": {
8
      "$ref": "../x-primitives.json#/cardnumber"
9
    },
10
    "surname": {
11
      "$ref": "../x-primitives.json#/surname"
12
    },
13
    "firstname": {
14
      "$ref": "../x-primitives.json#/firstname"
15
    },
16
    "title": {
17
      "type": ["string", "null"],
18
      "description": "patron's title"
19
    },
20
    "other_name": {
21
      "type": ["string", "null"],
22
      "description": "any other names associated with the patron"
23
    },
24
    "initials": {
25
      "type": ["string", "null"],
26
      "description": "initials of the patron"
27
    },
28
    "street_number": {
29
      "type": ["string", "null"],
30
      "description": "street number of patron's primary address"
31
    },
32
    "street_type": {
33
      "type": ["string", "null"],
34
      "description": "street type of patron's primary address"
35
    },
36
    "address": {
37
      "type": ["string", "null"],
38
      "description": "first address line of patron's primary address"
39
    },
40
    "address2": {
41
      "type": ["string", "null"],
42
      "description": "second address line of patron's primary address"
43
    },
44
    "city": {
45
      "type": ["string", "null"],
46
      "description": "city or town of patron's primary address"
47
    },
48
    "state": {
49
      "type": ["string", "null"],
50
      "description": "state or province of patron's primary address"
51
    },
52
    "postal_code": {
53
      "type": ["string", "null"],
54
      "description": "zip or postal code of patron's primary address"
55
    },
56
    "country": {
57
      "type": ["string", "null"],
58
      "description": "country of patron's primary address"
59
    },
60
    "email": {
61
      "$ref": "../x-primitives.json#/email"
62
    },
63
    "phone": {
64
      "$ref": "../x-primitives.json#/phone"
65
    },
66
    "mobile": {
67
      "type": ["string", "null"],
68
      "description": "the other phone number for patron's primary address"
69
    },
70
    "fax": {
71
      "type": ["string", "null"],
72
      "description": "fax number for patron's primary address"
73
    },
74
    "secondary_email": {
75
      "type": ["string", "null"],
76
      "description": "secondary email address for patron's primary address"
77
    },
78
    "secondary_phone": {
79
      "type": ["string", "null"],
80
      "description": "secondary phone number for patron's primary address"
81
    },
82
    "altaddress_street_number": {
83
      "type": ["string", "null"],
84
      "description": "street number of patron's alternate address"
85
    },
86
    "altaddress_street_type": {
87
      "type": ["string", "null"],
88
      "description": "street type of patron's alternate address"
89
    },
90
    "altaddress_address": {
91
      "type": ["string", "null"],
92
      "description": "first address line of patron's alternate address"
93
    },
94
    "altaddress_address2": {
95
      "type": ["string", "null"],
96
      "description": "second address line of patron's alternate address"
97
    },
98
    "altaddress_city": {
99
      "type": ["string", "null"],
100
      "description": "city or town of patron's alternate address"
101
    },
102
    "altaddress_state": {
103
      "type": ["string", "null"],
104
      "description": "state or province of patron's alternate address"
105
    },
106
    "altaddress_postal_code": {
107
      "type": ["string", "null"],
108
      "description": "zip or postal code of patron's alternate address"
109
    },
110
    "altaddress_country": {
111
      "type": ["string", "null"],
112
      "description": "country of patron's alternate address"
113
    },
114
    "altaddress_email": {
115
      "type": ["string", "null"],
116
      "description": "email address for patron's alternate address"
117
    },
118
    "altaddress_phone": {
119
      "type": ["string", "null"],
120
      "description": "phone number for patron's alternate address"
121
    },
122
    "date_of_birth": {
123
      "type": ["string", "null"],
124
      "format": "date",
125
      "description": "patron's date of birth"
126
    },
127
    "library_id": {
128
      "type": "string",
129
      "description": "Internal identifier for the patron's home library"
130
    },
131
    "category_id": {
132
      "type": "string",
133
      "description": "Internal identifier for the patron's category"
134
    },
135
    "date_enrolled": {
136
      "type": ["string", "null"],
137
      "format": "date",
138
      "description": "date the patron was added to Koha"
139
    },
140
    "expiry_date": {
141
      "type": ["string", "null"],
142
      "format": "date",
143
      "description": "date the patron's card is set to expire"
144
    },
145
    "date_renewed": {
146
      "type": ["string", "null"],
147
      "description": "date the patron's card was last renewed"
148
    },
149
    "incorrect_address": {
150
      "type": ["boolean", "null"],
151
      "description": "set to 1 if library marked this patron as having an unconfirmed address"
152
    },
153
    "patron_card_lost": {
154
      "type": ["boolean", "null"],
155
      "description": "set to 1 if library marked this patron as having lost his card"
156
    },
157
    "restricted": {
158
      "type": "boolean",
159
      "readOnly": true,
160
      "description": "If any restriction applies to the patron"
161
    },
162
    "guarantor_id": {
163
      "type": ["integer", "null"],
164
      "description": "patron_id used for children or professionals to link them to guarantor or organizations"
165
    },
166
    "staff_notes": {
167
      "type": ["string", "null"],
168
      "description": "a note on the patron's account"
169
    },
170
    "relationship_type": {
171
      "type": ["string", "null"],
172
      "description": "used for children to include the relationship to their guarantor"
173
    },
174
    "gender": {
175
      "type": ["string", "null"],
176
      "description": "patron's gender"
177
    },
178
    "userid": {
179
      "type": ["string", "null"],
180
      "description": "patron's login"
181
    },
182
    "opac_notes": {
183
      "type": ["string", "null"],
184
      "description": "a note on the patron's account visible in OPAC and staff client"
185
    },
186
    "altaddress_notes": {
187
      "type": ["string", "null"],
188
      "description": "a note related to patron's alternate address"
189
    },
190
    "statistics_1": {
191
      "type": ["string", "null"],
192
      "description": "a field that can be used for any information unique to the library"
193
    },
194
    "statistics_2": {
195
      "type": ["string", "null"],
196
      "description": "a field that can be used for any information unique to the library"
197
    },
198
    "altcontact_firstname": {
199
      "type": ["string", "null"],
200
      "description": "first name of alternate contact for the patron"
201
    },
202
    "altcontact_surname": {
203
      "type": ["string", "null"],
204
      "description": "surname or last name of the alternate contact for the patron"
205
    },
206
    "altcontact_address": {
207
      "type": ["string", "null"],
208
      "description": "the first address line for the alternate contact for the patron"
209
    },
210
    "altcontact_address2": {
211
      "type": ["string", "null"],
212
      "description": "the second address line for the alternate contact for the patron"
213
    },
214
    "altcontact_city": {
215
      "type": ["string", "null"],
216
      "description": "the city for the alternate contact for the patron"
217
    },
218
    "altcontact_state": {
219
      "type": ["string", "null"],
220
      "description": "the state for the alternate contact for the patron"
221
    },
222
    "altcontact_postal_code": {
223
      "type": ["string", "null"],
224
      "description": "the zipcode for the alternate contact for the patron"
225
    },
226
    "altcontact_country": {
227
      "type": ["string", "null"],
228
      "description": "the country for the alternate contact for the patron"
229
    },
230
    "altcontact_phone": {
231
      "type": ["string", "null"],
232
      "description": "the phone number for the alternate contact for the patron"
233
    },
234
    "sms_number": {
235
      "type": ["string", "null"],
236
      "description": "the mobile phone number where the patron would like to receive notices (if SMS turned on)"
237
    },
238
    "sms_provider_id": {
239
      "type": ["integer", "null"],
240
      "description": "the provider of the mobile phone number defined in smsalertnumber"
241
    },
242
    "privacy": {
243
      "type": "integer",
244
      "description": "patron's privacy settings related to their reading history"
245
    },
246
    "privacy_guarantor_checkouts": {
247
      "type": "integer",
248
      "description": "controls if relatives can see this patron's checkouts"
249
    },
250
    "check_previous_checkout": {
251
      "type": "string",
252
      "description": "produce a warning for this patron if this item has previously been checked out to this patron if 'yes', not if 'no', defer to category setting if 'inherit'"
253
    },
254
    "updated_on": {
255
      "type": "string",
256
      "format": "date-time",
257
      "description": "time of last change could be useful for synchronization with external systems (among others)"
258
    },
259
    "last_seen": {
260
      "type": ["string", "null"],
261
      "format": "date-time",
262
      "description": "last time a patron has been seen (connected at the OPAC or staff interface)"
263
    },
264
    "lang": {
265
      "type": "string",
266
      "description": "lang to use to send notices to this patron"
267
    },
268
    "login_attempts": {
269
      "type": ["integer", "null"],
270
      "description": "number of failed login attemps"
271
    },
272
    "overdrive_auth_token": {
273
      "type": ["string", "null"],
274
      "description": "persist OverDrive auth token"
275
    },
276
    "anonymized": {
277
      "type": "boolean",
278
      "readOnly": true,
279
      "description": "If the patron has been anonymized"
280
    }
281
  },
282
  "additionalProperties": false
283
}
(-)a/api/v1/swagger/paths/patrons.json (+78 lines)
Lines 654-659 Link Here
654
        }
654
        }
655
      }
655
      }
656
    },
656
    },
657
    "patch": {
658
      "x-mojo-to": "Patrons#update",
659
      "operationId": "patchPatron",
660
      "tags": ["patrons"],
661
      "parameters": [
662
        {
663
          "$ref": "../parameters.json#/patron_id_pp"
664
        },
665
        {
666
          "name": "body",
667
          "in": "body",
668
          "description": "A JSON object containing new information about existing patron",
669
          "required": true,
670
          "schema": {
671
            "$ref": "../definitions.json#/patron_patch"
672
          }
673
        }
674
      ],
675
      "consumes": ["application/json"],
676
      "produces": ["application/json"],
677
      "responses": {
678
        "200": {
679
          "description": "A successfully updated patron",
680
          "schema": {
681
            "items": {
682
              "$ref": "../definitions.json#/patron"
683
            }
684
          }
685
        },
686
        "202": {
687
          "description": "Accepted and waiting for librarian verification",
688
          "schema": {
689
            "type": "object"
690
          }
691
        },
692
        "204": {
693
          "description": "No Content",
694
          "schema": {
695
            "type": "object"
696
          }
697
        },
698
        "400": {
699
          "description": "Bad parameter",
700
          "schema": {
701
            "$ref": "../definitions.json#/error"
702
          }
703
        },
704
        "403": {
705
          "description": "Access forbidden",
706
          "schema": {
707
            "$ref": "../definitions.json#/error"
708
          }
709
        },
710
        "404": {
711
          "description": "Resource not found",
712
          "schema": {
713
            "$ref": "../definitions.json#/error"
714
          }
715
        },
716
        "409": {
717
          "description": "Conflict in updating resource",
718
          "schema": {
719
            "$ref": "../definitions.json#/error"
720
          }
721
        },
722
        "500": {
723
          "description": "Internal server error",
724
          "schema": {
725
            "$ref": "../definitions.json#/error"
726
          }
727
        }
728
      },
729
      "x-koha-authorization": {
730
        "permissions": {
731
          "borrowers": "1"
732
        }
733
      }
734
    },
657
    "delete": {
735
    "delete": {
658
      "x-mojo-to": "Patrons#delete",
736
      "x-mojo-to": "Patrons#delete",
659
      "operationId": "deletePatron",
737
      "operationId": "deletePatron",
(-)a/t/db_dependent/api/v1/patrons.t (-2 / +65 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 5;
20
use Test::More tests => 6;
21
use Test::Mojo;
21
use Test::Mojo;
22
use Test::Warn;
22
use Test::Warn;
23
23
Lines 313-318 subtest 'update() tests' => sub { Link Here
313
    };
313
    };
314
};
314
};
315
315
316
subtest 'patch() tests' => sub {
317
    plan tests => 2;
318
319
    $schema->storage->txn_begin;
320
    unauthorized_access_tests('PATCH', 123, {email => 'nobody@example.com'});
321
    $schema->storage->txn_rollback;
322
323
    subtest 'librarian access tests' => sub {
324
        plan tests => 15;
325
326
        $schema->storage->txn_begin;
327
328
        t::lib::Mocks::mock_preference('minPasswordLength', 1);
329
        my ( $patron_id_1, $session_id ) = create_user_and_session({ authorized => 1 });
330
        my ( $patron_id_2, undef )       = create_user_and_session({ authorized => 0 });
331
332
        my $patron_1  = Koha::Patrons->find($patron_id_1);
333
        my $patron_2  = Koha::Patrons->find($patron_id_2);
334
        my $newpatron = {firstname => "myfirstname"};
335
336
        my $tx = $t->ua->build_tx(PATCH => "/api/v1/patrons/-1" => json => $newpatron );
337
        $tx->req->cookies({name => 'CGISESSID', value => $session_id});
338
        $t->request_ok($tx)
339
          ->status_is(404)
340
          ->json_has('/error', 'Fails when trying to PATCH nonexistent patron');
341
342
        $newpatron->{falseproperty} = "Non existent property";
343
        $tx = $t->ua->build_tx(PATCH => "/api/v1/patrons/" . $patron_2->id => json => $newpatron );
344
        $tx->req->cookies({name => 'CGISESSID', value => $session_id});
345
        $t->request_ok($tx)
346
          ->status_is(400)
347
          ->json_is('/errors/0/message' =>
348
                    'Properties not allowed: falseproperty.');
349
        delete $newpatron->{falseproperty};
350
351
        # Set both cardnumber and userid to already existing values
352
        $newpatron->{cardnumber} = $patron_1->cardnumber;
353
        $newpatron->{userid}     = $patron_1->userid;
354
355
        $tx = $t->ua->build_tx( PATCH => "/api/v1/patrons/" . $patron_2->id => json => $newpatron );
356
        $tx->req->cookies({ name => 'CGISESSID', value => $session_id });
357
        warning_like {
358
            $t->request_ok($tx)
359
              ->status_is(409)
360
              ->json_has( '/error' => "Fails when trying to update to an existing cardnumber or userid")
361
              ->json_is(  '/conflict', 'cardnumber' ); }
362
            qr/^DBD::mysql::st execute failed: Duplicate entry '(.*?)' for key 'cardnumber'/;
363
364
        delete $newpatron->{cardnumber};
365
        delete $newpatron->{userid};
366
367
        #Update only firstname field
368
        $tx = $t->ua->build_tx(PATCH => "/api/v1/patrons/" . $patron_2->id => json => $newpatron);
369
        $tx->req->cookies({name => 'CGISESSID', value => $session_id});
370
        $t->request_ok($tx)
371
          ->status_is(200, 'Patron updated successfully')
372
          ->json_has($newpatron);
373
        is(Koha::Patrons->find( $patron_2->id )->firstname,
374
           $newpatron->{ firstname }, 'Patron is really updated!');
375
376
        $schema->storage->txn_rollback;
377
    };
378
};
379
316
subtest 'delete() tests' => sub {
380
subtest 'delete() tests' => sub {
317
    plan tests => 2;
381
    plan tests => 2;
318
382
319
- 

Return to bug 23285