Bug 30982: (QA follow-up) Spelling
[srvgit] / Koha / REST / V1 / Patrons.pm
1 package Koha::REST::V1::Patrons;
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::Database;
23 use Koha::Exceptions;
24 use Koha::Patrons;
25
26 use List::MoreUtils qw(any);
27 use Scalar::Util qw( blessed );
28 use Try::Tiny qw( catch try );
29
30 =head1 NAME
31
32 Koha::REST::V1::Patrons
33
34 =head1 API
35
36 =head2 Methods
37
38 =head3 list
39
40 Controller function that handles listing Koha::Patron objects
41
42 =cut
43
44 sub list {
45     my $c = shift->openapi->valid_input or return;
46
47     return try {
48
49         my $query = {};
50         my $restricted = delete $c->validation->output->{restricted};
51         $query->{debarred} = { '!=' => undef }
52             if $restricted;
53
54         my $patrons_rs = Koha::Patrons->search($query);
55         my $patrons    = $c->objects->search( $patrons_rs );
56
57         return $c->render(
58             status  => 200,
59             openapi => $patrons
60         );
61     }
62     catch {
63         $c->unhandled_exception($_);
64     };
65 }
66
67 =head3 get
68
69 Controller function that handles retrieving a single Koha::Patron object
70
71 =cut
72
73 sub get {
74     my $c = shift->openapi->valid_input or return;
75
76     return try {
77         my $patron_id = $c->validation->param('patron_id');
78         my $patron    = $c->objects->find( Koha::Patrons->search_limited, $patron_id );
79
80         unless ($patron) {
81             return $c->render(
82                 status  => 404,
83                 openapi => { error => "Patron not found." }
84             );
85         }
86
87         return $c->render(
88             status  => 200,
89             openapi => $patron
90         );
91     }
92     catch {
93         $c->unhandled_exception($_);
94     };
95 }
96
97 =head3 add
98
99 Controller function that handles adding a new Koha::Patron object
100
101 =cut
102
103 sub add {
104     my $c = shift->openapi->valid_input or return;
105
106     return try {
107
108         Koha::Database->new->schema->txn_do(
109             sub {
110
111                 my $body = $c->validation->param('body');
112
113                 my $extended_attributes = delete $body->{extended_attributes} // [];
114
115                 my $patron = Koha::Patron->new_from_api($body)->store;
116                 $patron->extended_attributes(
117                     [
118                         map { { code => $_->{type}, attribute => $_->{value} } }
119                           @$extended_attributes
120                     ]
121                 );
122                 if ( C4::Context->preference('EnhancedMessagingPreferences') ) {
123                     C4::Members::Messaging::SetMessagingPreferencesFromDefaults(
124                         {
125                             borrowernumber => $patron->borrowernumber,
126                             categorycode   => $patron->categorycode,
127                         }
128                     );
129                 }
130
131                 $c->res->headers->location($c->req->url->to_string . '/' . $patron->borrowernumber);
132                 return $c->render(
133                     status  => 201,
134                     openapi => $patron->to_api
135                 );
136             }
137         );
138     }
139     catch {
140
141         my $to_api_mapping = Koha::Patron->new->to_api_mapping;
142
143         if ( blessed $_ ) {
144             if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
145                 return $c->render(
146                     status  => 409,
147                     openapi => { error => $_->error, conflict => $_->duplicate_id }
148                 );
149             }
150             elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
151                 return $c->render(
152                     status  => 400,
153                     openapi => {
154                             error => "Given "
155                             . $to_api_mapping->{ $_->broken_fk }
156                             . " does not exist"
157                     }
158                 );
159             }
160             elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
161                 return $c->render(
162                     status  => 400,
163                     openapi => {
164                             error => "Given "
165                             . $to_api_mapping->{ $_->parameter }
166                             . " does not exist"
167                     }
168                 );
169             }
170             elsif (
171                 $_->isa('Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute')
172               )
173             {
174                 return $c->render(
175                     status  => 400,
176                     openapi => { error => "$_" }
177                 );
178             }
179             elsif (
180                 $_->isa('Koha::Exceptions::Patron::Attribute::InvalidType')
181               )
182             {
183                 return $c->render(
184                     status  => 400,
185                     openapi => { error => "$_" }
186                 );
187             }
188             elsif (
189                 $_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable')
190               )
191             {
192                 return $c->render(
193                     status  => 400,
194                     openapi => { error => "$_" }
195                 );
196             }
197             elsif (
198                 $_->isa('Koha::Exceptions::Patron::Attribute::UniqueIDConstraint')
199               )
200             {
201                 return $c->render(
202                     status  => 400,
203                     openapi => { error => "$_" }
204                 );
205             }
206         }
207
208         $c->unhandled_exception($_);
209     };
210 }
211
212
213 =head3 update
214
215 Controller function that handles updating a Koha::Patron object
216
217 =cut
218
219 sub update {
220     my $c = shift->openapi->valid_input or return;
221
222     my $patron_id = $c->validation->param('patron_id');
223     my $patron    = Koha::Patrons->find( $patron_id );
224
225     unless ($patron) {
226          return $c->render(
227              status  => 404,
228              openapi => { error => "Patron not found" }
229          );
230      }
231
232     return try {
233         my $body = $c->validation->param('body');
234         my $user = $c->stash('koha.user');
235
236         if (
237                 $patron->is_superlibrarian
238             and !$user->is_superlibrarian
239             and (  exists $body->{email}
240                 or exists $body->{secondary_email}
241                 or exists $body->{altaddress_email} )
242           )
243         {
244             foreach my $email_field ( qw(email secondary_email altaddress_email) ) {
245                 my $exists_email = exists $body->{$email_field};
246                 next unless $exists_email;
247
248                 # exists, verify if we are asked to change it
249                 my $put_email      = $body->{$email_field};
250                 # As of writing this patch, 'email' is the only unmapped field
251                 # (i.e. it preserves its name, hence this fallback)
252                 my $db_email_field = $patron->to_api_mapping->{$email_field} // 'email';
253                 my $db_email       = $patron->$db_email_field;
254
255                 return $c->render(
256                     status  => 403,
257                     openapi => { error => "Not enough privileges to change a superlibrarian's email" }
258                   )
259                   unless ( !defined $put_email and !defined $db_email )
260                   or (  defined $put_email
261                     and defined $db_email
262                     and $put_email eq $db_email );
263             }
264         }
265
266         $patron->set_from_api($c->validation->param('body'))->store;
267         $patron->discard_changes;
268         return $c->render( status => 200, openapi => $patron->to_api );
269     }
270     catch {
271         unless ( blessed $_ && $_->can('rethrow') ) {
272             return $c->render(
273                 status  => 500,
274                 openapi => {
275                     error => "Something went wrong, check Koha logs for details."
276                 }
277             );
278         }
279         if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
280             return $c->render(
281                 status  => 409,
282                 openapi => { error => $_->error, conflict => $_->duplicate_id }
283             );
284         }
285         elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
286             return $c->render(
287                 status  => 400,
288                 openapi => { error => "Given " .
289                             $patron->to_api_mapping->{$_->broken_fk}
290                             . " does not exist" }
291             );
292         }
293         elsif ( $_->isa('Koha::Exceptions::MissingParameter') ) {
294             return $c->render(
295                 status  => 400,
296                 openapi => {
297                     error      => "Missing mandatory parameter(s)",
298                     parameters => $_->parameter
299                 }
300             );
301         }
302         elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
303             return $c->render(
304                 status  => 400,
305                 openapi => {
306                     error      => "Invalid parameter(s)",
307                     parameters => $_->parameter
308                 }
309             );
310         }
311         elsif ( $_->isa('Koha::Exceptions::NoChanges') ) {
312             return $c->render(
313                 status  => 204,
314                 openapi => { error => "No changes have been made" }
315             );
316         }
317         else {
318             $c->unhandled_exception($_);
319         }
320     };
321 }
322
323 =head3 delete
324
325 Controller function that handles deleting a Koha::Patron object
326
327 =cut
328
329 sub delete {
330     my $c = shift->openapi->valid_input or return;
331
332     my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
333
334     unless ( $patron ) {
335         return $c->render(
336             status  => 404,
337             openapi => { error => "Patron not found" }
338         );
339     }
340
341     return try {
342
343         my $safe_to_delete = $patron->safe_to_delete;
344
345         if ( !$safe_to_delete ) {
346             # Pick the first error, if any
347             my ( $error ) = grep { $_->type eq 'error' } @{ $safe_to_delete->messages };
348             unless ( $error ) {
349                 Koha::Exception->throw('Koha::Patron->safe_to_delete returned false but carried no error message');
350             }
351
352             my $error_descriptions = {
353                 has_checkouts  => 'Pending checkouts prevent deletion',
354                 has_debt       => 'Pending debts prevent deletion',
355                 has_guarantees => 'Patron is a guarantor and it prevents deletion',
356                 is_anonymous_patron => 'Anonymous patron cannot be deleted',
357             };
358
359             if ( any { $error->message eq $_ } keys %{$error_descriptions} ) {
360                 return $c->render(
361                     status  => 409,
362                     openapi => {
363                         error      => $error_descriptions->{ $error->message },
364                         error_code => $error->message,
365                     }
366                 );
367             } else {
368                 Koha::Exception->throw( 'Koha::Patron->safe_to_delete carried an unexpected message: ' . $error->message );
369             }
370         }
371
372         return $patron->_result->result_source->schema->txn_do(
373             sub {
374                 $patron->move_to_deleted;
375                 $patron->delete;
376
377                 return $c->render(
378                     status  => 204,
379                     openapi => q{}
380                 );
381             }
382         );
383     } catch {
384
385         $c->unhandled_exception($_);
386     };
387 }
388
389 =head3 guarantors_can_see_charges
390
391 Method for setting whether guarantors can see the patron's charges.
392
393 =cut
394
395 sub guarantors_can_see_charges {
396     my $c = shift->openapi->valid_input or return;
397
398     return try {
399         if ( C4::Context->preference('AllowPatronToSetFinesVisibilityForGuarantor') ) {
400             my $patron = $c->stash( 'koha.user' );
401             my $privacy_setting = ($c->req->json->{allowed}) ? 1 : 0;
402
403             $patron->privacy_guarantor_fines( $privacy_setting )->store;
404
405             return $c->render(
406                 status  => 200,
407                 openapi => {}
408             );
409         }
410         else {
411             return $c->render(
412                 status  => 403,
413                 openapi => {
414                     error =>
415                       'The current configuration doesn\'t allow the requested action.'
416                 }
417             );
418         }
419     }
420     catch {
421         $c->unhandled_exception($_);
422     };
423 }
424
425 =head3 guarantors_can_see_checkouts
426
427 Method for setting whether guarantors can see the patron's checkouts.
428
429 =cut
430
431 sub guarantors_can_see_checkouts {
432     my $c = shift->openapi->valid_input or return;
433
434     return try {
435         if ( C4::Context->preference('AllowPatronToSetCheckoutsVisibilityForGuarantor') ) {
436             my $patron = $c->stash( 'koha.user' );
437             my $privacy_setting = ( $c->req->json->{allowed} ) ? 1 : 0;
438
439             $patron->privacy_guarantor_checkouts( $privacy_setting )->store;
440
441             return $c->render(
442                 status  => 200,
443                 openapi => {}
444             );
445         }
446         else {
447             return $c->render(
448                 status  => 403,
449                 openapi => {
450                     error =>
451                       'The current configuration doesn\'t allow the requested action.'
452                 }
453             );
454         }
455     }
456     catch {
457         $c->unhandled_exception($_);
458     };
459 }
460
461 1;