Bug 31797: Add DELETE /items/:item_id endpoint
[koha.git] / 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 get
62
63 Controller function that handles retrieving a single Koha::Item
64
65 =cut
66
67 sub get {
68     my $c = shift->openapi->valid_input or return;
69
70     try {
71         my $item = Koha::Items->find($c->validation->param('item_id'));
72         unless ( $item ) {
73             return $c->render(
74                 status => 404,
75                 openapi => { error => 'Item not found'}
76             );
77         }
78         return $c->render( status => 200, openapi => $item->to_api );
79     }
80     catch {
81         $c->unhandled_exception($_);
82     };
83 }
84
85 =head3 delete
86
87 Controller function that handles deleting a single Koha::Item
88
89 =cut
90
91 sub delete {
92     my $c = shift->openapi->valid_input or return;
93
94     return try {
95         my $item = Koha::Items->find($c->validation->param('item_id'));
96         unless ( $item ) {
97             return $c->render(
98                 status => 404,
99                 openapi => { error => 'Item not found'}
100             );
101         }
102
103         my $safe_to_delete = $item->safe_to_delete;
104
105         if ( !$safe_to_delete ) {
106
107             # Pick the first error, if any
108             my ( $error ) = grep { $_->type eq 'error' } @{ $safe_to_delete->messages };
109
110             unless ( $error ) {
111                 Koha::Exception->throw('Koha::Item->safe_to_delete returned false but carried no error message');
112             }
113
114             my $errors = {
115                 book_on_loan       => { code => 'checked_out',        description => 'The item is checked out' },
116                 book_reserved      => { code => 'found_hold',         description => 'Waiting or in-transit hold for the item' },
117                 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' },
118                 linked_analytics   => { code => 'linked_analytics',   description => 'The item has linked analytic records' },
119                 not_same_branch    => { code => 'not_same_branch',    description => 'The item is blocked by independent branches' },
120             };
121
122             if ( any { $error->message eq $_ } keys %{$errors} ) {
123
124                 my $code = $error->message;
125
126                 return $c->render(
127                     status  => 409,
128                     openapi => {
129                         error      => $errors->{ $code }->{description},
130                         error_code => $errors->{ $code }->{code},
131                     }
132                 );
133             } else {
134                 Koha::Exception->throw( 'Koha::Patron->safe_to_delete carried an unexpected message: ' . $error->message );
135             }
136         }
137
138         $item->safe_delete;
139
140         return $c->render(
141             status  => 204,
142             openapi => q{}
143         );
144     }
145     catch {
146         $c->unhandled_exception($_);
147     };
148 }
149
150 =head3 pickup_locations
151
152 Method that returns the possible pickup_locations for a given item
153 used for building the dropdown selector
154
155 =cut
156
157 sub pickup_locations {
158     my $c = shift->openapi->valid_input or return;
159
160     my $item_id = $c->validation->param('item_id');
161     my $item = Koha::Items->find( $item_id );
162
163     unless ($item) {
164         return $c->render(
165             status  => 404,
166             openapi => { error => "Item not found" }
167         );
168     }
169
170     my $patron_id = delete $c->validation->output->{patron_id};
171     my $patron    = Koha::Patrons->find( $patron_id );
172
173     unless ($patron) {
174         return $c->render(
175             status  => 400,
176             openapi => { error => "Patron not found" }
177         );
178     }
179
180     return try {
181
182         my $pl_set = $item->pickup_locations( { patron => $patron } );
183
184         my @response = ();
185         if ( C4::Context->preference('AllowHoldPolicyOverride') ) {
186
187             my $libraries_rs = Koha::Libraries->search( { pickup_location => 1 } );
188             my $libraries    = $c->objects->search($libraries_rs);
189
190             @response = map {
191                 my $library = $_;
192                 $library->{needs_override} = (
193                     any { $_->branchcode eq $library->{library_id} }
194                     @{ $pl_set->as_list }
195                   )
196                   ? Mojo::JSON->false
197                   : Mojo::JSON->true;
198                 $library;
199             } @{$libraries};
200         }
201         else {
202
203             my $pickup_locations = $c->objects->search($pl_set);
204             @response = map { $_->{needs_override} = Mojo::JSON->false; $_; } @{$pickup_locations};
205         }
206
207         return $c->render(
208             status  => 200,
209             openapi => \@response
210         );
211     }
212     catch {
213         $c->unhandled_exception($_);
214     };
215 }
216
217 =head3 bundled_items
218
219 Controller function that handles bundled_items Koha::Item objects
220
221 =cut
222
223 sub bundled_items {
224     my $c = shift->openapi->valid_input or return;
225
226     my $item_id = $c->validation->param('item_id');
227     my $item = Koha::Items->find( $item_id );
228
229     unless ($item) {
230         return $c->render(
231             status  => 404,
232             openapi => { error => "Item not found" }
233         );
234     }
235
236     return try {
237         my $items_set = $item->bundle_items;
238         my $items     = $c->objects->search( $items_set );
239         return $c->render(
240             status  => 200,
241             openapi => $items
242         );
243     }
244     catch {
245         $c->unhandled_exception($_);
246     };
247 }
248
249 =head3 add_to_bundle
250
251 Controller function that handles adding items to this bundle
252
253 =cut
254
255 sub add_to_bundle {
256     my $c = shift->openapi->valid_input or return;
257
258     my $item_id = $c->validation->param('item_id');
259     my $item = Koha::Items->find( $item_id );
260
261     unless ($item) {
262         return $c->render(
263             status  => 404,
264             openapi => { error => "Item not found" }
265         );
266     }
267
268     my $bundle_item_id = $c->validation->param('body')->{'external_id'};
269     $bundle_item_id = barcodedecode($bundle_item_id);
270     my $bundle_item = Koha::Items->find( { barcode => $bundle_item_id } );
271
272     unless ($bundle_item) {
273         return $c->render(
274             status  => 404,
275             openapi => { error => "Bundle item not found" }
276         );
277     }
278
279     return try {
280         my $link = $item->add_to_bundle($bundle_item);
281         return $c->render(
282             status  => 201,
283             openapi => $bundle_item
284         );
285     }
286     catch {
287         if ( ref($_) eq 'Koha::Exceptions::Object::DuplicateID' ) {
288             return $c->render(
289                 status  => 409,
290                 openapi => {
291                     error => 'Item is already bundled',
292                     key   => $_->duplicate_id
293                 }
294             );
295         }
296         elsif ( ref($_) eq 'Koha::Exceptions::Item::Bundle::IsBundle' ) {
297             return $c->render(
298                 status => 400,
299                 openapi => {
300                     error => 'Bundles cannot be nested'
301                 }
302             );
303         }
304         else {
305             $c->unhandled_exception($_);
306         }
307     };
308 }
309
310 =head3 remove_from_bundle
311
312 Controller function that handles removing items from this bundle
313
314 =cut
315
316 sub remove_from_bundle {
317     my $c = shift->openapi->valid_input or return;
318
319     my $item_id = $c->validation->param('item_id');
320     my $item = Koha::Items->find( $item_id );
321
322     unless ($item) {
323         return $c->render(
324             status  => 404,
325             openapi => { error => "Item not found" }
326         );
327     }
328
329     my $bundle_item_id = $c->validation->param('bundled_item_id');
330     $bundle_item_id = barcodedecode($bundle_item_id);
331     my $bundle_item = Koha::Items->find( { itemnumber => $bundle_item_id } );
332
333     unless ($bundle_item) {
334         return $c->render(
335             status  => 404,
336             openapi => { error => "Bundle item not found" }
337         );
338     }
339
340     $bundle_item->remove_from_bundle;
341     return $c->render(
342         status  => 204,
343         openapi => q{}
344     );
345 }
346
347 1;