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