Bug 29844: Fix ->search occurrences
[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
123                 $c->res->headers->location($c->req->url->to_string . '/' . $patron->borrowernumber);
124                 return $c->render(
125                     status  => 201,
126                     openapi => $patron->to_api
127                 );
128             }
129         );
130     }
131     catch {
132
133         my $to_api_mapping = Koha::Patron->new->to_api_mapping;
134
135         if ( blessed $_ ) {
136             if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
137                 return $c->render(
138                     status  => 409,
139                     openapi => { error => $_->error, conflict => $_->duplicate_id }
140                 );
141             }
142             elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
143                 return $c->render(
144                     status  => 400,
145                     openapi => {
146                             error => "Given "
147                             . $to_api_mapping->{ $_->broken_fk }
148                             . " does not exist"
149                     }
150                 );
151             }
152             elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
153                 return $c->render(
154                     status  => 400,
155                     openapi => {
156                             error => "Given "
157                             . $to_api_mapping->{ $_->parameter }
158                             . " does not exist"
159                     }
160                 );
161             }
162             elsif (
163                 $_->isa('Koha::Exceptions::Patron::MissingMandatoryExtendedAttribute')
164               )
165             {
166                 return $c->render(
167                     status  => 400,
168                     openapi => { error => "$_" }
169                 );
170             }
171             elsif (
172                 $_->isa('Koha::Exceptions::Patron::Attribute::InvalidType')
173               )
174             {
175                 return $c->render(
176                     status  => 400,
177                     openapi => { error => "$_" }
178                 );
179             }
180             elsif (
181                 $_->isa('Koha::Exceptions::Patron::Attribute::NonRepeatable')
182               )
183             {
184                 return $c->render(
185                     status  => 400,
186                     openapi => { error => "$_" }
187                 );
188             }
189             elsif (
190                 $_->isa('Koha::Exceptions::Patron::Attribute::UniqueIDConstraint')
191               )
192             {
193                 return $c->render(
194                     status  => 400,
195                     openapi => { error => "$_" }
196                 );
197             }
198         }
199
200         $c->unhandled_exception($_);
201     };
202 }
203
204
205 =head3 update
206
207 Controller function that handles updating a Koha::Patron object
208
209 =cut
210
211 sub update {
212     my $c = shift->openapi->valid_input or return;
213
214     my $patron_id = $c->validation->param('patron_id');
215     my $patron    = Koha::Patrons->find( $patron_id );
216
217     unless ($patron) {
218          return $c->render(
219              status  => 404,
220              openapi => { error => "Patron not found" }
221          );
222      }
223
224     return try {
225         my $body = $c->validation->param('body');
226         my $user = $c->stash('koha.user');
227
228         if (
229                 $patron->is_superlibrarian
230             and !$user->is_superlibrarian
231             and (  exists $body->{email}
232                 or exists $body->{secondary_email}
233                 or exists $body->{altaddress_email} )
234           )
235         {
236             foreach my $email_field ( qw(email secondary_email altaddress_email) ) {
237                 my $exists_email = exists $body->{$email_field};
238                 next unless $exists_email;
239
240                 # exists, verify if we are asked to change it
241                 my $put_email      = $body->{$email_field};
242                 # As of writing this patch, 'email' is the only unmapped field
243                 # (i.e. it preserves its name, hence this fallback)
244                 my $db_email_field = $patron->to_api_mapping->{$email_field} // 'email';
245                 my $db_email       = $patron->$db_email_field;
246
247                 return $c->render(
248                     status  => 403,
249                     openapi => { error => "Not enough privileges to change a superlibrarian's email" }
250                   )
251                   unless ( !defined $put_email and !defined $db_email )
252                   or (  defined $put_email
253                     and defined $db_email
254                     and $put_email eq $db_email );
255             }
256         }
257
258         $patron->set_from_api($c->validation->param('body'))->store;
259         $patron->discard_changes;
260         return $c->render( status => 200, openapi => $patron->to_api );
261     }
262     catch {
263         unless ( blessed $_ && $_->can('rethrow') ) {
264             return $c->render(
265                 status  => 500,
266                 openapi => {
267                     error => "Something went wrong, check Koha logs for details."
268                 }
269             );
270         }
271         if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
272             return $c->render(
273                 status  => 409,
274                 openapi => { error => $_->error, conflict => $_->duplicate_id }
275             );
276         }
277         elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
278             return $c->render(
279                 status  => 400,
280                 openapi => { error => "Given " .
281                             $patron->to_api_mapping->{$_->broken_fk}
282                             . " does not exist" }
283             );
284         }
285         elsif ( $_->isa('Koha::Exceptions::MissingParameter') ) {
286             return $c->render(
287                 status  => 400,
288                 openapi => {
289                     error      => "Missing mandatory parameter(s)",
290                     parameters => $_->parameter
291                 }
292             );
293         }
294         elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
295             return $c->render(
296                 status  => 400,
297                 openapi => {
298                     error      => "Invalid parameter(s)",
299                     parameters => $_->parameter
300                 }
301             );
302         }
303         elsif ( $_->isa('Koha::Exceptions::NoChanges') ) {
304             return $c->render(
305                 status  => 204,
306                 openapi => { error => "No changes have been made" }
307             );
308         }
309         else {
310             $c->unhandled_exception($_);
311         }
312     };
313 }
314
315 =head3 delete
316
317 Controller function that handles deleting a Koha::Patron object
318
319 =cut
320
321 sub delete {
322     my $c = shift->openapi->valid_input or return;
323
324     my $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
325
326     unless ( $patron ) {
327         return $c->render(
328             status  => 404,
329             openapi => { error => "Patron not found" }
330         );
331     }
332
333     return try {
334
335         my $safe_to_delete = $patron->safe_to_delete;
336
337         if ( !$safe_to_delete ) {
338             # Pick the first error, if any
339             my ( $error ) = grep { $_->type eq 'error' } @{ $safe_to_delete->messages };
340             unless ( $error ) {
341                 Koha::Exceptions::Exception->throw('Koha::Patron->safe_to_delete returned false but carried no error message');
342             }
343
344             my $error_descriptions = {
345                 has_checkouts  => 'Pending checkouts prevent deletion',
346                 has_debt       => 'Pending debts prevent deletion',
347                 has_guarantees => 'Patron is a guarantor and it prevents deletion',
348                 is_anonymous_patron => 'Anonymous patron cannot be deleted',
349             };
350
351             if ( any { $error->message eq $_ } keys %{$error_descriptions} ) {
352                 return $c->render(
353                     status  => 409,
354                     openapi => {
355                         error      => $error_descriptions->{ $error->message },
356                         error_code => $error->message,
357                     }
358                 );
359             } else {
360                 Koha::Exceptions::Exception->throw( 'Koha::Patron->safe_to_delete carried an unexpected message: ' . $error->message );
361             }
362         }
363
364         return $patron->_result->result_source->schema->txn_do(
365             sub {
366                 $patron->move_to_deleted;
367                 $patron->delete;
368
369                 return $c->render(
370                     status  => 204,
371                     openapi => q{}
372                 );
373             }
374         );
375     } catch {
376
377         $c->unhandled_exception($_);
378     };
379 }
380
381 =head3 guarantors_can_see_charges
382
383 Method for setting whether guarantors can see the patron's charges.
384
385 =cut
386
387 sub guarantors_can_see_charges {
388     my $c = shift->openapi->valid_input or return;
389
390     return try {
391         if ( C4::Context->preference('AllowPatronToSetFinesVisibilityForGuarantor') ) {
392             my $patron = $c->stash( 'koha.user' );
393             my $privacy_setting = ($c->req->json->{allowed}) ? 1 : 0;
394
395             $patron->privacy_guarantor_fines( $privacy_setting )->store;
396
397             return $c->render(
398                 status  => 200,
399                 openapi => {}
400             );
401         }
402         else {
403             return $c->render(
404                 status  => 403,
405                 openapi => {
406                     error =>
407                       'The current configuration doesn\'t allow the requested action.'
408                 }
409             );
410         }
411     }
412     catch {
413         $c->unhandled_exception($_);
414     };
415 }
416
417 =head3 guarantors_can_see_checkouts
418
419 Method for setting whether guarantors can see the patron's checkouts.
420
421 =cut
422
423 sub guarantors_can_see_checkouts {
424     my $c = shift->openapi->valid_input or return;
425
426     return try {
427         if ( C4::Context->preference('AllowPatronToSetCheckoutsVisibilityForGuarantor') ) {
428             my $patron = $c->stash( 'koha.user' );
429             my $privacy_setting = ( $c->req->json->{allowed} ) ? 1 : 0;
430
431             $patron->privacy_guarantor_checkouts( $privacy_setting )->store;
432
433             return $c->render(
434                 status  => 200,
435                 openapi => {}
436             );
437         }
438         else {
439             return $c->render(
440                 status  => 403,
441                 openapi => {
442                     error =>
443                       'The current configuration doesn\'t allow the requested action.'
444                 }
445             );
446         }
447     }
448     catch {
449         $c->unhandled_exception($_);
450     };
451 }
452
453 1;