Bug 36908: (QA follow-up) Proposed improvement to prefernce description
[koha.git] / tools / stockrotation.pl
1 #!/usr/bin/perl
2
3 # Copyright 2016 PTFS-Europe Ltd
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 =head1 stockrotation.pl
21
22  Script to handle stockrotation. Including rotas, their associated stages
23  and items
24
25 =cut
26
27 use Modern::Perl;
28 use CGI;
29
30 use C4::Auth qw( get_template_and_user );
31 use C4::Context;
32 use C4::Output qw( output_html_with_http_headers );
33
34 use Koha::Libraries;
35 use Koha::StockRotationRotas;
36 use Koha::StockRotationItems;
37 use Koha::StockRotationStages;
38 use Koha::Item;
39 use Koha::Util::StockRotation qw( get_branches get_stages move_to_next_stage toggle_indemand remove_from_stage add_items_to_rota get_barcodes_status );
40
41 my $input = CGI->new;
42
43 unless (C4::Context->preference('StockRotation')) {
44     # redirect to Intranet home if self-check is not enabled
45     print $input->redirect("/cgi-bin/koha/mainpage.pl");
46     exit;
47 }
48
49 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
50     {
51         template_name   => 'tools/stockrotation.tt',
52         query           => $input,
53         type            => 'intranet',
54         flagsrequired   => {
55             tools => '*',
56             stockrotation => '*',
57         },
58     }
59 );
60
61 # Grab all passed data
62 # 'our' since Plack changes the scoping
63 # of 'my'
64 our %params = $input->Vars();
65
66 my $op = $params{op};
67
68 if (!defined $op) {
69
70     # No operation is supplied, we're just displaying the list of rotas
71     my $rotas = Koha::StockRotationRotas->search(
72         undef,
73         {
74             order_by => { -asc => 'title' }
75         }
76     )->as_list;
77
78     $template->param(
79         existing_rotas => $rotas,
80         no_op_set      => 1
81     );
82
83 } elsif ($op eq 'create_edit_rota') {
84
85     # Edit an existing rota or define a new one
86     my $rota_id = $params{rota_id};
87
88     my $rota = {};
89
90     if (!defined $rota_id) {
91
92         # No ID supplied, we're creating a new rota
93         # Create a shell rota hashref
94         $rota = {
95             cyclical => 1
96         };
97
98     } else {
99
100         # ID supplied, we're editing an existing rota
101         $rota = Koha::StockRotationRotas->find($rota_id);
102
103     }
104
105     $template->param(
106         rota => $rota,
107         op   => $op
108     );
109
110 } elsif ($op eq 'cud-toggle_rota') {
111
112     # Find and update the active status of the rota
113     my $rota = Koha::StockRotationRotas->find($params{rota_id});
114
115     my $new_active = ($rota->active == 1) ? 0 : 1;
116
117     $rota->active($new_active)->store;
118
119     # Return to rotas page
120     print $input->redirect('stockrotation.pl');
121
122 } elsif ($op eq 'cud-process_rota') {
123
124     # Get a hashref of the submitted rota data
125     my $rota = get_rota_from_form();
126
127     if (!process_rota($rota)) {
128
129         # The submitted rota was invalid
130         $template->param(
131             error => 'invalid_form',
132             rota => $rota,
133             op   => 'create_edit_rota'
134         );
135
136     } else {
137
138         # All was well, return to the rotas list
139         print $input->redirect('stockrotation.pl');
140
141     }
142
143 } elsif ($op eq 'manage_stages') {
144
145     my $rota = Koha::StockRotationRotas->find($params{rota_id});
146
147     $template->param(
148         rota            => $rota,
149         branches        => get_branches(),
150         existing_stages => get_stages($rota),
151         rota_id         => $params{rota_id},
152         op              => $op
153     );
154
155 } elsif ($op eq 'create_edit_stage') {
156
157     # Edit an existing stage or define a new one
158     my $stage_id = $params{stage_id};
159
160     my $rota_id = $params{rota_id};
161
162     if (!defined $stage_id) {
163
164         my $rota = Koha::StockRotationRotas->find($rota_id);
165         # No ID supplied, we're creating a new stage
166         $template->param(
167             branches => get_branches(),
168             stage    => {},
169             rota     => $rota,
170             rota_id  => $rota_id,
171             op       => $op
172         );
173
174     } else {
175
176         # ID supplied, we're editing an existing stage
177         my $stage = Koha::StockRotationStages->find($stage_id);
178         my $rota  = Koha::StockRotationRotas->find( $stage->rota->rota_id );
179
180         $template->param(
181             branches => get_branches(),
182             stage    => $stage,
183             rota     => $rota,
184             rota_id  => $stage->rota->rota_id,
185             op       => $op
186         );
187
188     }
189
190 } elsif ($op eq 'confirm_remove_from_rota') {
191
192     # Get the stage we're deleting
193     $template->param(
194         op       => $op,
195         rota_id  => $params{rota_id},
196         stage_id => $params{stage_id},
197         item_id  => $params{item_id}
198     );
199
200 } elsif ($op eq 'confirm_delete_rota') {
201
202     # Get the rota we're deleting
203     my $rota = Koha::StockRotationRotas->find($params{rota_id});
204
205     # Get all items on this rota, for each prefetch their
206     # stage and biblio objects
207     my $sritems = Koha::StockRotationItems->search(
208         { 'stage.rota_id' => $params{rota_id} },
209         {
210             prefetch => {
211                 stage => {
212                     'stockrotationitems' => {
213                         'itemnumber' => 'biblionumber'
214                     }
215                 }
216             }
217         }
218     );
219
220     $template->param(
221         rota_id  => $params{rota_id},
222         sritemstotal  => $sritems->count,
223         op       => $op
224     );
225
226 } elsif ($op eq 'cud-delete_rota') {
227
228     # Get the rota we're deleting
229     my $rota = Koha::StockRotationRotas->find($params{rota_id});
230
231     $rota->delete;
232
233     # Return to the rotas list
234     print $input->redirect("/cgi-bin/koha/tools/stockrotation.pl");
235
236 } elsif ($op eq 'confirm_delete_stage') {
237
238     # Get the stage we're deleting
239     my $stage = Koha::StockRotationStages->find($params{stage_id});
240
241     $template->param(
242         op    => $op,
243         stage => $stage
244     );
245
246 } elsif ($op eq 'cud-delete_stage') {
247
248     # Get the stage we're deleting
249     my $stage = Koha::StockRotationStages->find($params{stage_id});
250
251     # Get the ID of the rota with which this stage is associated
252     # (so we can return to the "Manage stages" page after deletion)
253     my $rota_id = $stage->rota->rota_id;
254
255     $stage->delete;
256
257     # Return to the stages list
258     print $input->redirect("?op=manage_stages&rota_id=$rota_id");
259
260 } elsif ($op eq 'cud-process_stage') {
261
262     # Get a hashref of the submitted stage data
263     my $stage = get_stage_from_form();
264
265     # The rota we're managing
266     my $rota_id = $params{rota_id};
267
268     if (!process_stage($stage, $rota_id)) {
269
270         # The submitted stage was invalid
271         # Get all branches
272         my $branches = get_branches();
273
274         $template->param(
275             error        => 'invalid_form',
276             all_branches => $branches,
277             stage        => $stage,
278             rota_id      => $rota_id,
279             op           => 'create_edit_stage'
280         );
281
282     } else {
283
284         # All was well, return to the stages list
285         print $input->redirect("?op=manage_stages&rota_id=$rota_id");
286
287     }
288
289 } elsif ($op eq 'manage_items') {
290
291     my $rota = Koha::StockRotationRotas->find($params{rota_id});
292
293     # Get all items on this rota, for each prefetch their
294     # stage and biblio objects
295     my $sritems = Koha::StockRotationItems->search(
296         { 'stage.rota_id' => $params{rota_id} },
297         {
298             prefetch => {
299                 stage => {
300                     'stockrotationitems' => {
301                         'itemnumber' => 'biblionumber'
302                     }
303                 }
304             }
305         }
306     );
307
308     $template->param(
309         rota_id  => $params{rota_id},
310         error    => $params{error},
311         sritems  => $sritems,
312         branches => get_branches(),
313         stages   => get_stages($rota),
314         rota     => $rota,
315         op       => $op
316     );
317
318 } elsif ($op eq 'cud-move_to_next_stage') {
319
320     move_to_next_stage($params{item_id}, $params{stage_id});
321
322     # Return to the items list
323     print $input->redirect("?op=manage_items&rota_id=" . $params{rota_id});
324
325 } elsif ($op eq 'cud-toggle_in_demand') {
326
327     # Toggle the item's in_demand
328     toggle_indemand($params{item_id}, $params{stage_id});
329
330     # Return to the items list
331     print $input->redirect("?op=manage_items&rota_id=".$params{rota_id});
332
333 } elsif ($op eq 'cud-remove_item_from_stage') {
334
335     # Remove the item from the stage
336     remove_from_stage($params{item_id}, $params{stage_id});
337
338     # Return to the items list
339     print $input->redirect("?op=manage_items&rota_id=".$params{rota_id});
340
341 } elsif ($op eq 'cud-add_items_to_rota') {
342
343     # The item's barcode,
344     # which we may or may not have been passed
345     my $barcode = $params{barcode};
346
347     # The rota we're adding the item to
348     my $rota_id = $params{rota_id};
349     my $rota    = Koha::StockRotationRotas->find($rota_id);
350
351     # The uploaded file filehandle,
352     # which we may or may not have been passed
353     my $barcode_file = $input->upload("barcodefile");
354
355     # We need to create an array of one or more barcodes to
356     # insert
357     my @barcodes = ();
358
359     # If the barcode input box was populated, use it
360     push @barcodes, $barcode if $barcode;
361
362     # Only parse the uploaded file if necessary
363     if ($barcode_file) {
364
365         # Call binmode on the filehandle as we want to set a
366         # UTF-8 layer on it
367         binmode($barcode_file, ":encoding(UTF-8)");
368         # Parse the file into an array of barcodes
369         while (my $barcode = <$barcode_file>) {
370             $barcode =~ s/\r/\n/g;
371             $barcode =~ s/\n+/\n/g;
372             my @data = split(/\n/, $barcode);
373             push @barcodes, @data;
374         }
375
376     }
377
378     # A hashref to hold the status of each barcode
379     my $barcode_status = {
380         ok        => [],
381         on_other  => [],
382         on_this   => [],
383         not_found => []
384     };
385
386     # If we have something to work with, do it
387     get_barcodes_status($rota_id, \@barcodes, $barcode_status) if (@barcodes);
388
389     # Now we know the status of each barcode, add those that
390     # need it
391     if (scalar @{$barcode_status->{ok}} > 0) {
392
393         add_items_to_rota($rota_id, $barcode_status->{ok});
394
395     }
396     # If we were only passed one barcode and it was successfully
397     # added, redirect back to ourselves, we don't want to display
398     # a report, redirect also if we were passed no barcodes
399     if (
400         scalar @barcodes == 0 ||
401         (scalar @barcodes == 1 && scalar @{$barcode_status->{ok}} == 1)
402     ) {
403
404         print $input->redirect("?op=manage_items&rota_id=$rota_id");
405
406     } else {
407
408         # Report on the outcome
409         $template->param(
410             barcode_status => $barcode_status,
411             rota_id        => $rota_id,
412             rota           => $rota,
413             op             => $op
414         );
415
416     }
417
418 } elsif ($op eq 'cud-move_items_to_rota') {
419
420     # The barcodes of the items we're moving
421     my @move = $input->multi_param('move_item');
422
423     foreach my $item(@move) {
424
425         # The item we're moving
426         my $item = Koha::Items->find($item);
427
428         # Move it to the new rota
429         $item->add_to_rota($params{rota_id});
430
431     }
432
433     # Return to the items list
434     print $input->redirect("?op=manage_items&rota_id=".$params{rota_id});
435
436 }
437
438 output_html_with_http_headers $input, $cookie, $template->output;
439
440 sub get_rota_from_form {
441
442     return {
443         id          => $params{id},
444         title       => $params{title},
445         cyclical    => $params{cyclical},
446         description => $params{description}
447     };
448 }
449
450 sub get_stage_from_form {
451
452     return {
453         stage_id    => $params{stage_id},
454         branchcode  => $params{branchcode},
455         duration    => $params{duration}
456     };
457 }
458
459 sub process_rota {
460
461     my $sub_rota = shift;
462
463     # Fields we require
464     my @required = ('title','cyclical');
465
466     # Count of the number of required fields we have
467     my $valid = 0;
468
469     # Ensure we have everything we require
470     foreach my $req(@required) {
471
472         if (exists $sub_rota->{$req}) {
473
474             chomp(my $value = $sub_rota->{$req});
475             if (length $value > 0) {
476                 $valid++;
477             }
478
479         }
480
481     }
482
483     # If we don't have everything we need
484     return 0 if $valid != scalar @required;
485
486     # Passed validation
487     # Find the rota we're updating
488     my $rota = Koha::StockRotationRotas->find($sub_rota->{id});
489
490     if ($rota) {
491
492         $rota->title(
493             $sub_rota->{title}
494         )->cyclical(
495             $sub_rota->{cyclical}
496         )->description(
497             $sub_rota->{description}
498         )->store;
499
500     } else {
501
502         $rota = Koha::StockRotationRota->new({
503             title       => $sub_rota->{title},
504             cyclical    => $sub_rota->{cyclical},
505             active      => 0,
506             description => $sub_rota->{description}
507         })->store;
508
509     }
510
511     return 1;
512 }
513
514 sub process_stage {
515
516     my ($sub_stage, $rota_id) = @_;
517
518     # Fields we require
519     my @required = ('branchcode','duration');
520
521     # Count of the number of required fields we have
522     my $valid = 0;
523
524     # Ensure we have everything we require
525     foreach my $req(@required) {
526
527         if (exists $sub_stage->{$req}) {
528
529             chomp(my $value = $sub_stage->{$req});
530             if (length $value > 0) {
531                 $valid++;
532             }
533
534         }
535
536     }
537
538     # If we don't have everything we need
539     return 0 if $valid != scalar @required;
540
541     # Passed validation
542     # Find the stage we're updating
543     my $stage = Koha::StockRotationStages->find($sub_stage->{stage_id});
544
545     if ($stage) {
546
547         # Updating an existing stage
548         $stage->branchcode_id(
549             $sub_stage->{branchcode}
550         )->duration(
551             $sub_stage->{duration}
552         )->store;
553
554     } else {
555
556         # Creating a new stage
557         $stage = Koha::StockRotationStage->new({
558             branchcode_id  => $sub_stage->{branchcode},
559             rota_id        => $rota_id,
560             duration       => $sub_stage->{duration}
561         })->store;
562
563     }
564
565     return 1;
566 }
567
568 =head1 AUTHOR
569
570 Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
571
572 =cut