Bug 17600: Standardize our EXPORT_OK
[koha.git] / acqui / basket.pl
1 #!/usr/bin/perl
2
3 #script to show display basket of orders
4
5 # Copyright 2000 - 2004 Katipo
6 # Copyright 2008 - 2009 BibLibre SARL
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Koha is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22
23 use Modern::Perl;
24 use C4::Auth qw( get_template_and_user haspermission );
25 use C4::Output qw( output_html_with_http_headers output_and_exit );
26 use CGI qw ( -utf8 );
27 use C4::Acquisition qw( GetBasket CanUserManageBasket GetBasketAsCSV NewBasket NewBasketgroup ModBasket ReopenBasket ModBasketUsers GetBasketgroup GetBasketgroups GetBasketUsers GetOrders GetOrder get_rounded_price );
28 use C4::Budgets qw( GetBudgetHierarchy GetBudget CanUserUseBudget );
29 use C4::Contract qw( GetContract );
30 use C4::Suggestions qw( GetSuggestion GetSuggestionInfoFromBiblionumber GetSuggestionInfo );
31 use Koha::Biblios;
32 use Koha::Acquisition::Baskets;
33 use Koha::Acquisition::Booksellers;
34 use Koha::Acquisition::Orders;
35 use Koha::Libraries;
36 use C4::Letters qw( SendAlerts );
37 use Date::Calc qw( Add_Delta_Days );
38 use Koha::Database;
39 use Koha::EDI qw( create_edi_order );
40 use Koha::CsvProfiles;
41 use Koha::Patrons;
42
43 use Koha::AdditionalFields;
44
45 =head1 NAME
46
47 basket.pl
48
49 =head1 DESCRIPTION
50
51  This script display all informations about basket for the supplier given
52  on input arg.  Moreover, it allows us to add a new order for this supplier from
53  an existing record, a suggestion or a new record.
54
55 =head1 CGI PARAMETERS
56
57 =over 4
58
59 =item $basketno
60
61 The basket number.
62
63 =item booksellerid
64
65 the supplier this script have to display the basket.
66
67 =item order
68
69 =back
70
71 =cut
72
73 our $query        = CGI->new;
74 our $basketno     = $query->param('basketno');
75 our $ean          = $query->param('ean');
76 our $booksellerid = $query->param('booksellerid');
77 my $duplinbatch =  $query->param('duplinbatch');
78
79 our ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
80     {
81         template_name   => "acqui/basket.tt",
82         query           => $query,
83         type            => "intranet",
84         flagsrequired   => { acquisition => 'order_manage' },
85     }
86 );
87
88 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
89
90 our $basket = GetBasket($basketno);
91 $booksellerid = $basket->{booksellerid} unless $booksellerid;
92 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
93 my $schema = Koha::Database->new()->schema();
94 my $rs = $schema->resultset('VendorEdiAccount')->search(
95     { vendor_id => $booksellerid, } );
96 $template->param( ediaccount => ($rs->count > 0));
97
98 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
99     $template->param(
100         cannot_manage_basket => 1,
101         basketno => $basketno,
102         basketname => $basket->{basketname},
103         booksellerid => $booksellerid,
104         booksellername => $bookseller->name,
105     );
106     output_html_with_http_headers $query, $cookie, $template->output;
107     exit;
108 }
109
110 # FIXME : what about the "discount" percentage?
111 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
112 # if no booksellerid in parameter, get it from basket
113 # warn "=>".$basket->{booksellerid};
114 my $op = $query->param('op') // 'list';
115
116 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
117 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
118
119 my @messages;
120
121 if ( $op eq 'delete_confirm' ) {
122
123     output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
124       unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
125
126     my $basketno   = $query->param('basketno');
127     my $delbiblio  = $query->param('delbiblio');
128     my $basket_obj = Koha::Acquisition::Baskets->find($basketno);
129
130     my $orders = $basket_obj->orders;
131
132     my @cannotdelbiblios;
133
134     while ( my $order = $orders->next ) {
135         # cancel the order
136         $order->cancel({ delete_biblio => $delbiblio });
137         my @messages = @{ $order->messages };
138
139         if ( scalar @messages > 0 ) {
140
141             my $biblio = $order->biblio;
142
143             push @cannotdelbiblios, {
144                 biblionumber  => $biblio->id,
145                 title         => $biblio->title // '',
146                 author        => $biblio->author // '',
147                 countbiblio   => $biblio->active_orders->count,
148                 itemcount     => $biblio->items->count,
149                 subscriptions => $biblio->subscriptions->count,
150             };
151         }
152     }
153
154     $template->param( cannotdelbiblios => \@cannotdelbiblios );
155
156     # delete the basket
157     $basket_obj->delete;
158     $template->param(
159         delete_confirmed => 1,
160         booksellername => $bookseller->name,
161         booksellerid => $booksellerid,
162     );
163 } elsif ( !$bookseller ) {
164     $template->param( NO_BOOKSELLER => 1 );
165 } elsif ($op eq 'export') {
166     print $query->header(
167         -type       => 'text/csv',
168         -attachment => 'basket' . $basket->{'basketno'} . '.csv',
169     );
170     my $csv_profile_id = $query->param('csv_profile');
171     print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
172     exit;
173 } elsif ($op eq 'email') {
174     my $err = eval {
175         SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
176     };
177     if ( $@ ) {
178         push @messages, { type => 'error', code => $@ };
179     } elsif ( ref $err and exists $err->{error} ) {
180         push @messages, { type => 'error', code => $err->{error} };
181     } else {
182         push @messages, { type => 'message', code => 'email_sent' };
183     }
184
185     $op = 'list';
186 } elsif ($op eq 'close') {
187     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
188     if ($confirm) {
189
190         # close the basket
191         # FIXME: we should fetch the object at the beginning of this script
192         #        and get rid of the hash that is passed around
193         Koha::Acquisition::Baskets->find($basketno)->close;
194
195         # if requested, create basket group, close it and attach the basket
196         if ($query->param('createbasketgroup')) {
197             my $branchcode;
198             if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
199                 $branchcode = C4::Context->userenv->{'branch'};
200             }
201             my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
202                             booksellerid => $booksellerid,
203                             deliveryplace => $branchcode,
204                             billingplace => $branchcode,
205                             closed => 1,
206                             });
207             ModBasket( { basketno => $basketno,
208                          basketgroupid => $basketgroupid } );
209             print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
210         } else {
211             print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
212         }
213         exit;
214     } else {
215     $template->param(
216         confirm_close   => "1",
217         booksellerid    => $booksellerid,
218         booksellername  => $bookseller->name,
219         basketno        => $basket->{'basketno'},
220         basketname      => $basket->{'basketname'},
221         basketgroupname => $basket->{'basketname'},
222     );
223     }
224 } elsif ($op eq 'reopen') {
225     ReopenBasket(scalar $query->param('basketno'));
226     print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
227 }
228 elsif ( $op eq 'ediorder' ) {
229     edi_close_and_order()
230 } elsif ( $op eq 'mod_users' ) {
231     my $basketusers_ids = $query->param('users_ids');
232     my @basketusers = split( /:/, $basketusers_ids );
233     ModBasketUsers($basketno, @basketusers);
234     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
235     exit;
236 } elsif ( $op eq 'mod_branch' ) {
237     my $branch = $query->param('branch');
238     $branch = undef if(defined $branch and $branch eq '');
239     ModBasket({
240         basketno => $basket->{basketno},
241         branch   => $branch
242     });
243     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
244     exit;
245 }
246
247 if ( $op eq 'list' ) {
248     my @branches_loop;
249     # get librarian branch...
250     if ( C4::Context->preference("IndependentBranches") ) {
251         my $userenv = C4::Context->userenv;
252         unless ( C4::Context->IsSuperLibrarian() ) {
253             my $validtest = ( $basket->{creationdate} eq '' )
254               || ( $userenv->{branch} eq $basket->{branch} )
255               || ( $userenv->{branch} eq '' )
256               || ( $basket->{branch}  eq '' );
257             unless ($validtest) {
258                 print $query->redirect("../mainpage.pl");
259                 exit 0;
260             }
261         }
262
263         if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
264             push @branches_loop, {
265                 branchcode => $userenv->{branch},
266                 branchname => $userenv->{branchname},
267                 selected => 1,
268             };
269         }
270     } else {
271         # get branches
272         my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
273         foreach my $branch (@$branches) {
274             my $selected = 0;
275             if (defined $basket->{branch}) {
276                 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
277             } else {
278                 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
279             }
280             push @branches_loop, {
281                 branchcode => $branch->{branchcode},
282                 branchname => $branch->{branchname},
283                 selected => $selected
284             };
285         }
286     }
287
288 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
289     my ($basketgroup, $basketgroups);
290     my $patron = Koha::Patrons->find($loggedinuser);
291     if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
292         $basketgroups = GetBasketgroups($basket->{booksellerid});
293         for my $bg ( @{$basketgroups} ) {
294             if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
295                 $bg->{default} = 1;
296                 $basketgroup = $bg;
297             }
298         }
299     }
300
301     # if the basket is closed, calculate estimated delivery date
302     my $estimateddeliverydate;
303     if( $basket->{closedate} ) {
304         my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
305         ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
306         $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
307     }
308
309     # if new basket, pre-fill infos
310     $basket->{creationdate} = ""            unless ( $basket->{creationdate} );
311     $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
312
313     my @basketusers_ids = GetBasketUsers($basketno);
314     my @basketusers;
315     foreach my $basketuser_id (@basketusers_ids) {
316         # FIXME Could be improved with a search -in
317         my $basket_patron = Koha::Patrons->find( $basketuser_id );
318         push @basketusers, $basket_patron if $basket_patron;
319     }
320
321     my $active_currency = Koha::Acquisition::Currencies->get_active;
322
323     my @orders = GetOrders( $basketno );
324     my @books_loop;
325
326     my @book_foot_loop;
327     my %foot;
328     my $total_quantity = 0;
329     my $total_tax_excluded = 0;
330     my $total_tax_included = 0;
331     my $total_tax_value = 0;
332     for my $order (@orders) {
333         my $line = get_order_infos( $order, $bookseller);
334         if ( $line->{uncertainprice} ) {
335             $template->param( uncertainprices => 1 );
336         }
337
338         $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
339         $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
340
341         push @books_loop, $line;
342
343         $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
344         $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
345         $total_tax_value += $$line{tax_value};
346         $foot{$$line{tax_rate}}{quantity}  += get_rounded_price($$line{quantity});
347         $total_quantity += $$line{quantity};
348         $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
349         $total_tax_excluded += $$line{total_tax_excluded};
350         $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
351         $total_tax_included += $$line{total_tax_included};
352     }
353
354     push @book_foot_loop, map {$_} values %foot;
355
356     # Get cancelled orders
357     my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
358     my @cancelledorders_loop;
359     for my $order (@cancelledorders) {
360         my $line = get_order_infos( $order, $bookseller);
361         push @cancelledorders_loop, $line;
362     }
363
364     my $contract = GetContract({
365         contractnumber => $basket->{contractnumber}
366     });
367
368     if ($basket->{basketgroupid}){
369         $basketgroup = GetBasketgroup($basket->{basketgroupid});
370     }
371     my $budgets = GetBudgetHierarchy;
372     my $has_budgets = 0;
373     foreach my $r (@{$budgets}) {
374         next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
375
376         $has_budgets = 1;
377         last;
378     }
379
380     $template->param(
381         basketno             => $basketno,
382         basket               => $basket,
383         basketname           => $basket->{'basketname'},
384         basketbranchcode     => $basket->{branch},
385         basketnote           => $basket->{note},
386         basketbooksellernote => $basket->{booksellernote},
387         basketcontractno     => $basket->{contractnumber},
388         basketcontractname   => $contract->{contractname},
389         branches_loop        => \@branches_loop,
390         creationdate         => $basket->{creationdate},
391         authorisedby         => $basket->{authorisedby},
392         authorisedbyname     => $basket->{authorisedbyname},
393         users_ids            => join(':', @basketusers_ids),
394         users                => \@basketusers,
395         closedate            => $basket->{closedate},
396         estimateddeliverydate=> $estimateddeliverydate,
397         is_standing          => $basket->{is_standing},
398         deliveryplace        => $basket->{deliveryplace},
399         billingplace         => $basket->{billingplace},
400         active               => $bookseller->active,
401         booksellerid         => $bookseller->id,
402         booksellername       => $bookseller->name,
403         books_loop           => \@books_loop,
404         book_foot_loop       => \@book_foot_loop,
405         cancelledorders_loop => \@cancelledorders_loop,
406         total_quantity       => $total_quantity,
407         total_tax_excluded   => $total_tax_excluded,
408         total_tax_included   => $total_tax_included,
409         total_tax_value      => $total_tax_value,
410         currency             => $active_currency->currency,
411         listincgst           => $bookseller->listincgst,
412         basketgroups         => $basketgroups,
413         basketgroup          => $basketgroup,
414         grouped              => $basket->{basketgroupid},
415         # The double negatives and booleans here mean:
416         # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
417         #
418         # (The template has another implicit restriction that the order cannot be closed if there
419         # are any orders with uncertain prices.)
420         unclosable           => @orders || @cancelledorders ? $basket->{is_standing} : 1,
421         has_budgets          => $has_budgets,
422         duplinbatch          => $duplinbatch,
423         csv_profiles         => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
424         available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
425         additional_field_values => { map {
426             $_->field->name => $_->value
427         } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
428     );
429 }
430
431 $template->param( messages => \@messages );
432 output_html_with_http_headers $query, $cookie, $template->output;
433
434 sub get_order_infos {
435     my $order = shift;
436     my $bookseller = shift;
437     my $qty = $order->{'quantity'} || 0;
438     if ( !defined $order->{quantityreceived} ) {
439         $order->{quantityreceived} = 0;
440     }
441     my $budget = GetBudget($order->{budget_id});
442     my $basket = GetBasket($order->{basketno});
443
444     my %line = %{ $order };
445     # Don't show unreceived standing orders as received
446     $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
447     $line{basketno}       = $basketno;
448     $line{budget_name}    = $budget->{budget_name};
449
450     # If we have an actual cost that should be the total, otherwise use the ecost
451     $line{unitprice_tax_included} += 0;
452     $line{unitprice_tax_excluded} += 0;
453     my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
454     my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
455     $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
456     $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
457     $line{tax_value} = $line{tax_value_on_ordering};
458     $line{tax_rate} = $line{tax_rate_on_ordering};
459
460     if ( $line{'title'} ) {
461         my $volume      = $order->{'volume'};
462         my $seriestitle = $order->{'seriestitle'};
463         $line{'title'} .= " / $seriestitle" if $seriestitle;
464         $line{'title'} .= " / $volume"      if $volume;
465     }
466
467     my $biblionumber = $order->{'biblionumber'};
468     if ( $biblionumber ) { # The biblio still exists
469         my $biblio = Koha::Biblios->find( $biblionumber );
470         my $countbiblio = $biblio->active_orders->count;
471
472         my $ordernumber = $order->{'ordernumber'};
473         my $cnt_subscriptions = $biblio->subscriptions->count;
474         my $itemcount   = $biblio->items->count;
475         my $holds_count = $biblio->holds->count;
476         my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
477         my $items = $order->items;
478         my $itemholds  = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
479
480         # if the biblio is not in other orders and if there is no items elsewhere and no subscriptions and no holds we can then show the link "Delete order and Biblio" see bug 5680
481         $line{can_del_bib}          = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
482         $line{items}                = $itemcount - $items->count;
483         $line{left_item}            = 1 if $line{items} >= 1;
484         $line{left_biblio}          = 1 if $countbiblio > 1;
485         $line{biblios}              = $countbiblio - 1;
486         $line{left_subscription}    = 1 if $cnt_subscriptions;
487         $line{subscriptions}        = $cnt_subscriptions;
488         ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
489         $line{left_holds_on_order}  = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
490         $line{holds}                = $holds_count;
491         $line{holds_on_order}       = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
492         $line{order_object}         = $order;
493     }
494
495
496     my $suggestion   = GetSuggestionInfoFromBiblionumber($line{biblionumber});
497     $line{suggestionid}         = $$suggestion{suggestionid};
498     $line{surnamesuggestedby}   = $$suggestion{surnamesuggestedby};
499     $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
500
501     foreach my $key (qw(transferred_from transferred_to)) {
502         if ($line{$key}) {
503             my $order = GetOrder($line{$key});
504             my $basket = GetBasket($order->{basketno});
505             my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
506             $line{$key} = {
507                 order => $order,
508                 basket => $basket,
509                 bookseller => $bookseller,
510                 timestamp => $line{$key . '_timestamp'},
511             };
512         }
513     }
514
515     return \%line;
516 }
517
518 sub edi_close_and_order {
519     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
520     if ($confirm) {
521         my $edi_params = {
522             basketno => $basketno,
523             ean    => $ean,
524         };
525         if ( $basket->{branch} ) {
526             $edi_params->{branchcode} = $basket->{branch};
527         }
528         if ( create_edi_order($edi_params) ) {
529             #$template->param( edifile => 1 );
530         }
531         Koha::Acquisition::Baskets->find($basketno)->close;
532
533         # if requested, create basket group, close it and attach the basket
534         if ( $query->param('createbasketgroup') ) {
535             my $branchcode;
536             if (    C4::Context->userenv
537                 and C4::Context->userenv->{'branch'} )
538             {
539                 $branchcode = C4::Context->userenv->{'branch'};
540             }
541             my $basketgroupid = NewBasketgroup(
542                 {
543                     name          => $basket->{basketname},
544                     booksellerid  => $booksellerid,
545                     deliveryplace => $branchcode,
546                     billingplace  => $branchcode,
547                     closed        => 1,
548                 }
549             );
550             ModBasket(
551                 {
552                     basketno       => $basketno,
553                     basketgroupid  => $basketgroupid
554                 }
555             );
556             print $query->redirect(
557 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
558             );
559         }
560         else {
561             print $query->redirect(
562                 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
563             );
564         }
565         exit;
566     }
567     else {
568         $template->param(
569             edi_confirm     => 1,
570             booksellerid    => $booksellerid,
571             basketno        => $basket->{basketno},
572             basketname      => $basket->{basketname},
573             basketgroupname => $basket->{basketname},
574         );
575         if ($ean) {
576             $template->param( ean => $ean );
577         }
578
579     }
580     return;
581 }