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