938ffcd659eeb33f2df1ace6948d8503eb9bb1ee
[srvgit] / Koha / REST / V1 / Items.pm
1 package Koha::REST::V1::Items;
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 C4::Circulation qw( barcodedecode );
23
24 use Koha::Items;
25
26 use List::MoreUtils qw( any );
27 use Try::Tiny qw( catch try );
28
29 =head1 NAME
30
31 Koha::REST::V1::Items - Koha REST API for handling items (V1)
32
33 =head1 API
34
35 =head2 Methods
36
37 =cut
38
39 =head3 list
40
41 Controller function that handles listing Koha::Item objects
42
43 =cut
44
45 sub list {
46     my $c = shift->openapi->valid_input or return;
47
48     return try {
49         my $items_set = Koha::Items->new;
50         my $items     = $c->objects->search( $items_set );
51         return $c->render(
52             status  => 200,
53             openapi => $items
54         );
55     }
56     catch {
57         $c->unhandled_exception($_);
58     };
59 }
60
61 =head3 list_public
62
63 Controller function that handles listing Koha::Item objects available to the opac
64
65 =cut
66
67 sub list_public {
68     my $c = shift->openapi->valid_input or return;
69
70     return try {
71         my $patron = $c->stash('koha.user');
72
73         my $items_set =
74           Koha::Items->filter_by_visible_in_opac( { patron => $patron } );
75         my $items = $c->objects->search($items_set);
76
77         return $c->render(
78             status  => 200,
79             openapi => $items
80         );
81     }
82     catch {
83         $c->unhandled_exception($_);
84     };
85 }
86
87 =head3 get
88
89 Controller function that handles retrieving a single Koha::Item
90
91 =cut
92
93 sub get {
94     my $c = shift->openapi->valid_input or return;
95
96     try {
97         my $items_rs = Koha::Items->new;
98         my $item = $c->objects->find($items_rs, $c->validation->param('item_id'));
99         unless ( $item ) {
100             return $c->render(
101                 status => 404,
102                 openapi => { error => 'Item not found'}
103             );
104         }
105         return $c->render( status => 200, openapi => $item );
106     }
107     catch {
108         $c->unhandled_exception($_);
109     };
110 }
111
112 =head3 delete
113
114 Controller function that handles deleting a single Koha::Item
115
116 =cut
117
118 sub delete {
119     my $c = shift->openapi->valid_input or return;
120
121     return try {
122         my $item = Koha::Items->find($c->validation->param('item_id'));
123         unless ( $item ) {
124             return $c->render(
125                 status => 404,
126                 openapi => { error => 'Item not found'}
127             );
128         }
129
130         my $safe_to_delete = $item->safe_to_delete;
131
132         if ( !$safe_to_delete ) {
133
134             # Pick the first error, if any
135             my ( $error ) = grep { $_->type eq 'error' } @{ $safe_to_delete->messages };
136
137             unless ( $error ) {
138                 Koha::Exception->throw('Koha::Item->safe_to_delete returned false but carried no error message');
139             }
140
141             my $errors = {
142                 book_on_loan       => { code => 'checked_out',        description => 'The item is checked out' },
143                 book_reserved      => { code => 'found_hold',         description => 'Waiting or in-transit hold for the item' },
144                 last_item_for_hold => { code => 'last_item_for_hold', description => 'The item is the last one on a record on which a biblio-level hold is placed' },
145                 linked_analytics   => { code => 'linked_analytics',   description => 'The item has linked analytic records' },
146                 not_same_branch    => { code => 'not_same_branch',    description => 'The item is blocked by independent branches' },
147             };
148
149             if ( any { $error->message eq $_ } keys %{$errors} ) {
150
151                 my $code = $error->message;
152
153                 return $c->render(
154                     status  => 409,
155                     openapi => {
156                         error      => $errors->{ $code }->{description},
157                         error_code => $errors->{ $code }->{code},
158                     }
159                 );
160             } else {
161                 Koha::Exception->throw( 'Koha::Patron->safe_to_delete carried an unexpected message: ' . $error->message );
162             }
163         }
164
165         $item->safe_delete;
166
167         return $c->render(
168             status  => 204,
169             openapi => q{}
170         );
171     }
172     catch {
173         $c->unhandled_exception($_);
174     };
175 }
176
177 =head3 pickup_locations
178
179 Method that returns the possible pickup_locations for a given item
180 used for building the dropdown selector
181
182 =cut
183
184 sub pickup_locations {
185     my $c = shift->openapi->valid_input or return;
186
187     my $item_id = $c->validation->param('item_id');
188     my $item = Koha::Items->find( $item_id );
189
190     unless ($item) {
191         return $c->render(
192             status  => 404,
193             openapi => { error => "Item not found" }
194         );
195     }
196
197     my $patron_id = delete $c->validation->output->{patron_id};
198     my $patron    = Koha::Patrons->find( $patron_id );
199
200     unless ($patron) {
201         return $c->render(
202             status  => 400,
203             openapi => { error => "Patron not found" }
204         );
205     }
206
207     return try {
208
209         my $pl_set = $item->pickup_locations( { patron => $patron } );
210
211         my @response = ();
212         if ( C4::Context->preference('AllowHoldPolicyOverride') ) {
213
214             my $libraries_rs = Koha::Libraries->search( { pickup_location => 1 } );
215             my $libraries    = $c->objects->search($libraries_rs);
216
217             @response = map {
218                 my $library = $_;
219                 $library->{needs_override} = (
220                     any { $_->branchcode eq $library->{library_id} }
221                     @{ $pl_set->as_list }
222                   )
223                   ? Mojo::JSON->false
224                   : Mojo::JSON->true;
225                 $library;
226             } @{$libraries};
227         }
228         else {
229
230             my $pickup_locations = $c->objects->search($pl_set);
231             @response = map { $_->{needs_override} = Mojo::JSON->false; $_; } @{$pickup_locations};
232         }
233
234         return $c->render(
235             status  => 200,
236             openapi => \@response
237         );
238     }
239     catch {
240         $c->unhandled_exception($_);
241     };
242 }
243
244 =head3 bundled_items
245
246 Controller function that handles bundled_items Koha::Item objects
247
248 =cut
249
250 sub bundled_items {
251     my $c = shift->openapi->valid_input or return;
252
253     my $item_id = $c->validation->param('item_id');
254     my $item = Koha::Items->find( $item_id );
255
256     unless ($item) {
257         return $c->render(
258             status  => 404,
259             openapi => { error => "Item not found" }
260         );
261     }
262
263     return try {
264         my $items_set = $item->bundle_items;
265         my $items     = $c->objects->search( $items_set );
266         return $c->render(
267             status  => 200,
268             openapi => $items
269         );
270     }
271     catch {
272         $c->unhandled_exception($_);
273     };
274 }
275
276 =head3 add_to_bundle
277
278 Controller function that handles adding items to this bundle
279
280 =cut
281
282 sub add_to_bundle {
283     my $c = shift->openapi->valid_input or return;
284
285     my $item_id = $c->validation->param('item_id');
286     my $item = Koha::Items->find( $item_id );
287
288     unless ($item) {
289         return $c->render(
290             status  => 404,
291             openapi => { error => "Item not found" }
292         );
293     }
294
295     my $bundle_item_id = $c->validation->param('body')->{'external_id'};
296     $bundle_item_id = barcodedecode($bundle_item_id);
297     my $bundle_item = Koha::Items->find( { barcode => $bundle_item_id } );
298
299     unless ($bundle_item) {
300         return $c->render(
301             status  => 404,
302             openapi => { error => "Bundle item not found" }
303         );
304     }
305
306     return try {
307         my $body = $c->validation->param('body');
308         my $options = {
309             force_checkin => $body->{force_checkin},
310             ignore_holds => $body->{ignore_holds},
311         };
312
313         my $link = $item->add_to_bundle($bundle_item, $options);
314         return $c->render(
315             status  => 201,
316             openapi => $bundle_item
317         );
318     }
319     catch {
320         if ( ref($_) eq 'Koha::Exceptions::Object::DuplicateID' ) {
321             return $c->render(
322                 status  => 409,
323                 openapi => {
324                     error      => 'Item is already bundled',
325                     error_code => 'already_bundled',
326                     key        => $_->duplicate_id
327                 }
328             );
329         }
330         elsif ( ref($_) eq 'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut' )
331         {
332             return $c->render(
333                 status  => 409,
334                 openapi => {
335                     error      => 'Item is checked out',
336                     error_code => 'checked_out'
337                 }
338             );
339         }
340         elsif ( ref($_) eq 'Koha::Exceptions::Checkin::FailedCheckin' ) {
341             return $c->render(
342                 status  => 409,
343                 openapi => {
344                     error      => 'Item cannot be checked in',
345                     error_code => 'failed_checkin'
346                 }
347             );
348         }
349         elsif ( ref($_) eq 'Koha::Exceptions::Item::Bundle::ItemHasHolds' ) {
350             return $c->render(
351                 status  => 409,
352                 openapi => {
353                     error      => 'Item is reserved',
354                     error_code => 'reserved'
355                 }
356             );
357         }
358         elsif ( ref($_) eq 'Koha::Exceptions::Item::Bundle::IsBundle' ) {
359             return $c->render(
360                 status  => 400,
361                 openapi => {
362                     error      => 'Bundles cannot be nested',
363                     error_code => 'failed_nesting'
364                 }
365             );
366         }
367         else {
368             $c->unhandled_exception($_);
369         }
370     };
371 }
372
373 =head3 remove_from_bundle
374
375 Controller function that handles removing items from this bundle
376
377 =cut
378
379 sub remove_from_bundle {
380     my $c = shift->openapi->valid_input or return;
381
382     my $item_id = $c->validation->param('item_id');
383     my $item = Koha::Items->find( $item_id );
384
385     unless ($item) {
386         return $c->render(
387             status  => 404,
388             openapi => { error => "Item not found" }
389         );
390     }
391
392     my $bundle_item_id = $c->validation->param('bundled_item_id');
393     $bundle_item_id = barcodedecode($bundle_item_id);
394     my $bundle_item = Koha::Items->find( { itemnumber => $bundle_item_id } );
395
396     unless ($bundle_item) {
397         return $c->render(
398             status  => 404,
399             openapi => { error => "Bundle item not found" }
400         );
401     }
402
403     $bundle_item->remove_from_bundle;
404     return $c->render(
405         status  => 204,
406         openapi => q{}
407     );
408 }
409
410 1;