]> git.koha-community.org Git - koha.git/blob - Koha/REST/V1/Patrons.pm
Bug 20287: Replace occurrences of ModMember in REST API
[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 under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 use Modern::Perl;
19
20 use Mojo::Base 'Mojolicious::Controller';
21
22 use Koha::Patrons;
23
24 use Scalar::Util qw(blessed);
25 use Try::Tiny;
26
27 =head1 NAME
28
29 Koha::REST::V1::Patrons
30
31 =head1 API
32
33 =head2 Methods
34
35 =head3 list
36
37 Controller function that handles listing Koha::Patron objects
38
39 =cut
40
41 sub list {
42     my $c = shift->openapi->valid_input or return;
43
44     return try {
45         my $attributes = {};
46         my $args = $c->validation->output;
47         my ( $params, $reserved_params ) = $c->extract_reserved_params( $args );
48
49         # Merge sorting into query attributes
50         $c->dbic_merge_sorting({ attributes => $attributes, params => $reserved_params });
51
52         # Merge pagination into query attributes
53         $c->dbic_merge_pagination({ filter => $attributes, params => $reserved_params });
54
55         my $restricted = $args->{restricted};
56
57         $params = _to_model($params)
58             if defined $params;
59         # deal with string params
60         $params = $c->build_query_params( $params, $reserved_params );
61
62         # translate 'restricted' => 'debarred'
63         $params->{debarred} = { '!=' => undef }
64           if $restricted;
65
66         my $patrons = Koha::Patrons->search( $params, $attributes );
67         if ( $patrons->is_paged ) {
68             $c->add_pagination_headers(
69                 {
70                     total  => $patrons->pager->total_entries,
71                     params => $args,
72                 }
73             );
74         }
75         my @patrons = $patrons->as_list;
76         @patrons = map { _to_api( $_->TO_JSON ) } @patrons;
77         return $c->render( status => 200, openapi => \@patrons );
78     }
79     catch {
80         if ( $_->isa('DBIx::Class::Exception') ) {
81             return $c->render(
82                 status  => 500,
83                 openapi => { error => $_->{msg} }
84             );
85         }
86         else {
87             return $c->render(
88                 status  => 500,
89                 openapi => { error => "Something went wrong, check the logs." }
90             );
91         }
92     };
93 }
94
95
96 =head3 get
97
98 Controller function that handles retrieving a single Koha::Patron object
99
100 =cut
101
102 sub get {
103     my $c = shift->openapi->valid_input or return;
104
105     my $patron_id = $c->validation->param('patron_id');
106     my $patron    = Koha::Patrons->find($patron_id);
107
108     unless ($patron) {
109         return $c->render( status => 404, openapi => { error => "Patron not found." } );
110     }
111
112     return $c->render( status => 200, openapi => _to_api( $patron->TO_JSON ) );
113 }
114
115 =head3 add
116
117 Controller function that handles adding a new Koha::Patron object
118
119 =cut
120
121 sub add {
122     my $c = shift->openapi->valid_input or return;
123
124     return try {
125
126         my $body = _to_model( $c->validation->param('body') );
127
128         my $patron = Koha::Patron->new( _to_model($body) )->store;
129         $patron    = _to_api( $patron->TO_JSON );
130
131         return $c->render( status => 201, openapi => $patron );
132     }
133     catch {
134         unless ( blessed $_ && $_->can('rethrow') ) {
135             return $c->render(
136                 status  => 500,
137                 openapi => { error => "Something went wrong, check Koha logs for details." }
138             );
139         }
140         if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
141             return $c->render(
142                 status  => 409,
143                 openapi => { error => $_->error, conflict => $_->duplicate_id }
144             );
145         }
146         elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
147             return $c->render(
148                 status  => 400,
149                 openapi => {
150                           error => "Given "
151                         . $Koha::REST::V1::Patrons::to_api_mapping->{ $_->broken_fk }
152                         . " does not exist"
153                 }
154             );
155         }
156         elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
157             return $c->render(
158                 status  => 400,
159                 openapi => {
160                           error => "Given "
161                         . $Koha::REST::V1::Patrons::to_api_mapping->{ $_->parameter }
162                         . " does not exist"
163                 }
164             );
165         }
166         else {
167             return $c->render(
168                 status  => 500,
169                 openapi => { error => "Something went wrong, check Koha logs for details." }
170             );
171         }
172     };
173 }
174
175
176 =head3 update
177
178 Controller function that handles updating a Koha::Patron object
179
180 =cut
181
182 sub update {
183     my $c = shift->openapi->valid_input or return;
184
185     my $patron_id = $c->validation->param('patron_id');
186     my $patron    = Koha::Patrons->find( $patron_id );
187
188     unless ($patron) {
189          return $c->render(
190              status  => 404,
191              openapi => { error => "Patron not found" }
192          );
193      }
194
195     return try {
196         my $body = _to_model($c->validation->param('body'));
197
198         $patron->set($body)->store;
199         $patron->discard_changes;
200         return $c->render( status => 200, openapi => $patron );
201     }
202     catch {
203         unless ( blessed $_ && $_->can('rethrow') ) {
204             return $c->render(
205                 status  => 500,
206                 openapi => {
207                     error => "Something went wrong, check Koha logs for details."
208                 }
209             );
210         }
211         if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
212             return $c->render(
213                 status  => 409,
214                 openapi => { error => $_->error, conflict => $_->duplicate_id }
215             );
216         }
217         elsif ( $_->isa('Koha::Exceptions::Object::FKConstraint') ) {
218             return $c->render(
219                 status  => 400,
220                 openapi => { error => "Given " .
221                             $Koha::REST::V1::Patrons::to_api_mapping->{$_->broken_fk}
222                             . " does not exist" }
223             );
224         }
225         elsif ( $_->isa('Koha::Exceptions::MissingParameter') ) {
226             return $c->render(
227                 status  => 400,
228                 openapi => {
229                     error      => "Missing mandatory parameter(s)",
230                     parameters => $_->parameter
231                 }
232             );
233         }
234         elsif ( $_->isa('Koha::Exceptions::BadParameter') ) {
235             return $c->render(
236                 status  => 400,
237                 openapi => {
238                     error      => "Invalid parameter(s)",
239                     parameters => $_->parameter
240                 }
241             );
242         }
243         elsif ( $_->isa('Koha::Exceptions::NoChanges') ) {
244             return $c->render(
245                 status  => 204,
246                 openapi => { error => "No changes have been made" }
247             );
248         }
249         else {
250             return $c->render(
251                 status  => 500,
252                 openapi => {
253                     error =>
254                       "Something went wrong, check Koha logs for details."
255                 }
256             );
257         }
258     };
259 }
260
261 =head3 delete
262
263 Controller function that handles deleting a Koha::Patron object
264
265 =cut
266
267 sub delete {
268     my $c = shift->openapi->valid_input or return;
269
270     my $patron;
271
272     return try {
273         $patron = Koha::Patrons->find( $c->validation->param('patron_id') );
274
275         # check if loans, reservations, debarrment, etc. before deletion!
276         my $res = $patron->delete;
277         return $c->render( status => 200, openapi => {} );
278     }
279     catch {
280         unless ($patron) {
281             return $c->render(
282                 status  => 404,
283                 openapi => { error => "Patron not found" }
284             );
285         }
286         else {
287             return $c->render(
288                 status  => 500,
289                 openapi => {
290                     error =>
291                       "Something went wrong, check Koha logs for details."
292                 }
293             );
294         }
295     };
296 }
297
298 =head3 _to_api
299
300 Helper function that maps unblessed Koha::Patron objects into REST api
301 attribute names.
302
303 =cut
304
305 sub _to_api {
306     my $patron    = shift;
307     my $patron_id = $patron->{ borrowernumber };
308
309     # Rename attributes
310     foreach my $column ( keys %{ $Koha::REST::V1::Patrons::to_api_mapping } ) {
311         my $mapped_column = $Koha::REST::V1::Patrons::to_api_mapping->{$column};
312         if (    exists $patron->{ $column }
313              && defined $mapped_column )
314         {
315             # key != undef
316             $patron->{ $mapped_column } = delete $patron->{ $column };
317         }
318         elsif (    exists $patron->{ $column }
319                 && !defined $mapped_column )
320         {
321             # key == undef
322             delete $patron->{ $column };
323         }
324     }
325
326     # Calculate the 'restricted' field
327     my $patron_obj = Koha::Patrons->find( $patron_id );
328     $patron->{ restricted } = ($patron_obj->is_debarred) ? Mojo::JSON->true : Mojo::JSON->false;
329
330     return $patron;
331 }
332
333 =head3 _to_model
334
335 Helper function that maps REST api objects into Koha::Patron
336 attribute names.
337
338 =cut
339
340 sub _to_model {
341     my $patron = shift;
342
343     foreach my $attribute ( keys %{ $Koha::REST::V1::Patrons::to_model_mapping } ) {
344         my $mapped_attribute = $Koha::REST::V1::Patrons::to_model_mapping->{$attribute};
345         if (    exists $patron->{ $attribute }
346              && defined $mapped_attribute )
347         {
348             # key => !undef
349             $patron->{ $mapped_attribute } = delete $patron->{ $attribute };
350         }
351         elsif (    exists $patron->{ $attribute }
352                 && !defined $mapped_attribute )
353         {
354             # key => undef / to be deleted
355             delete $patron->{ $attribute };
356         }
357     }
358
359     # TODO: Get rid of this once write operations are based on Koha::Patron
360     if ( exists $patron->{lost} ) {
361         $patron->{lost} = ($patron->{lost}) ? 1 : 0;
362     }
363
364     if ( exists $patron->{ gonenoaddress} ) {
365         $patron->{gonenoaddress} = ($patron->{gonenoaddress}) ? 1 : 0;
366     }
367
368     return $patron;
369 }
370
371 =head2 Global variables
372
373 =head3 $to_api_mapping
374
375 =cut
376
377 our $to_api_mapping = {
378     borrowernotes       => 'staff_notes',
379     borrowernumber      => 'patron_id',
380     branchcode          => 'library_id',
381     categorycode        => 'category_id',
382     checkprevcheckout   => 'check_previous_checkout',
383     contactfirstname    => undef, # Unused
384     contactname         => undef, # Unused
385     contactnote         => 'altaddress_notes',
386     contacttitle        => undef, # Unused
387     dateenrolled        => 'date_enrolled',
388     dateexpiry          => 'expiry_date',
389     dateofbirth         => 'date_of_birth',
390     debarred            => undef, # replaced by 'restricted'
391     debarredcomment     => undef, # calculated, API consumers will use /restrictions instead
392     emailpro            => 'secondary_email',
393     flags               => undef, # permissions manipulation handled in /permissions
394     gonenoaddress       => 'incorrect_address',
395     guarantorid         => 'guarantor_id',
396     lastseen            => 'last_seen',
397     lost                => 'patron_card_lost',
398     opacnote            => 'opac_notes',
399     othernames          => 'other_name',
400     password            => undef, # password manipulation handled in /password
401     phonepro            => 'secondary_phone',
402     relationship        => 'relationship_type',
403     sex                 => 'gender',
404     smsalertnumber      => 'sms_number',
405     sort1               => 'statistics_1',
406     sort2               => 'statistics_2',
407     streetnumber        => 'street_number',
408     streettype          => 'street_type',
409     zipcode             => 'postal_code',
410     B_address           => 'altaddress_address',
411     B_address2          => 'altaddress_address2',
412     B_city              => 'altaddress_city',
413     B_country           => 'altaddress_country',
414     B_email             => 'altaddress_email',
415     B_phone             => 'altaddress_phone',
416     B_state             => 'altaddress_state',
417     B_streetnumber      => 'altaddress_street_number',
418     B_streettype        => 'altaddress_street_type',
419     B_zipcode           => 'altaddress_postal_code',
420     altcontactaddress1  => 'altcontact_address',
421     altcontactaddress2  => 'altcontact_address2',
422     altcontactaddress3  => 'altcontact_city',
423     altcontactcountry   => 'altcontact_country',
424     altcontactfirstname => 'altcontact_firstname',
425     altcontactphone     => 'altcontact_phone',
426     altcontactsurname   => 'altcontact_surname',
427     altcontactstate     => 'altcontact_state',
428     altcontactzipcode   => 'altcontact_postal_code'
429 };
430
431 =head3 $to_model_mapping
432
433 =cut
434
435 our $to_model_mapping = {
436     altaddress_notes         => 'contactnote',
437     category_id              => 'categorycode',
438     check_previous_checkout  => 'checkprevcheckout',
439     date_enrolled            => 'dateenrolled',
440     date_of_birth            => 'dateofbirth',
441     expiry_date              => 'dateexpiry',
442     gender                   => 'sex',
443     guarantor_id             => 'guarantorid',
444     incorrect_address        => 'gonenoaddress',
445     last_seen                => 'lastseen',
446     library_id               => 'branchcode',
447     opac_notes               => 'opacnote',
448     other_name               => 'othernames',
449     patron_card_lost         => 'lost',
450     patron_id                => 'borrowernumber',
451     postal_code              => 'zipcode',
452     relationship_type        => 'relationship',
453     restricted               => undef,
454     secondary_email          => 'emailpro',
455     secondary_phone          => 'phonepro',
456     sms_number               => 'smsalertnumber',
457     staff_notes              => 'borrowernotes',
458     statistics_1             => 'sort1',
459     statistics_2             => 'sort2',
460     street_number            => 'streetnumber',
461     street_type              => 'streettype',
462     altaddress_address       => 'B_address',
463     altaddress_address2      => 'B_address2',
464     altaddress_city          => 'B_city',
465     altaddress_country       => 'B_country',
466     altaddress_email         => 'B_email',
467     altaddress_phone         => 'B_phone',
468     altaddress_state         => 'B_state',
469     altaddress_street_number => 'B_streetnumber',
470     altaddress_street_type   => 'B_streettype',
471     altaddress_postal_code   => 'B_zipcode',
472     altcontact_firstname     => 'altcontactfirstname',
473     altcontact_surname       => 'altcontactsurname',
474     altcontact_address       => 'altcontactaddress1',
475     altcontact_address2      => 'altcontactaddress2',
476     altcontact_city          => 'altcontactaddress3',
477     altcontact_state         => 'altcontactstate',
478     altcontact_postal_code   => 'altcontactzipcode',
479     altcontact_country       => 'altcontactcountry',
480     altcontact_phone         => 'altcontactphone'
481 };
482
483 1;