Bug 19200: Preventing warns when exporting a basket
[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;
25 use C4::Koha;
26 use C4::Output;
27 use CGI qw ( -utf8 );
28 use C4::Acquisition;
29 use C4::Budgets;
30 use C4::Contract;
31 use C4::Debug;
32 use C4::Biblio;
33 use C4::Items;
34 use C4::Suggestions;
35 use Koha::Biblios;
36 use Koha::Acquisition::Booksellers;
37 use Koha::Libraries;
38 use C4::Letters qw/SendAlerts/;
39 use Date::Calc qw/Add_Delta_Days/;
40 use Koha::Database;
41 use Koha::EDI qw( create_edi_order get_edifact_ean );
42 use Koha::CsvProfiles;
43 use Koha::Patrons;
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        = new CGI;
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         authnotrequired => 0,
85         flagsrequired   => { acquisition => 'order_manage' },
86         debug           => 1,
87     }
88 );
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     my $basketno = $query->param('basketno');
123     my $delbiblio = $query->param('delbiblio');
124     my @orders = GetOrders($basketno);
125 #Delete all orders included in that basket, and all items received.
126     foreach my $myorder (@orders){
127         DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
128     }
129 # if $delbiblio = 1, delete the records if possible
130     if ((defined $delbiblio)and ($delbiblio ==1)){
131         my @cannotdelbiblios ;
132         foreach my $myorder (@orders){
133             my $biblionumber = $myorder->{'biblionumber'};
134             my $biblio = Koha::Biblios->find( $biblionumber );
135             my $countbiblio = CountBiblioInOrders($biblionumber);
136             my $ordernumber = $myorder->{'ordernumber'};
137             my $cnt_subscriptions = $biblio->subscriptions->count;
138             my $itemcount = $biblio->items->count;
139             my $error;
140             if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
141                 $error = DelBiblio($myorder->{biblionumber}) }
142             else {
143                 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
144                                          title=> $myorder->{'title'},
145                                          author=> $myorder->{'author'},
146                                          countbiblio=> $countbiblio,
147                                          itemcount=>$itemcount,
148                                          subscriptions => $cnt_subscriptions};
149             }
150             if ($error) {
151                 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
152                                          title=> $myorder->{'title'},
153                                          author=> $myorder->{'author'},
154                                          othererror=> $error};
155             }
156         }
157         $template->param( cannotdelbiblios => \@cannotdelbiblios );
158     }
159  # delete the basket
160     DelBasket($basketno,);
161     $template->param(
162         delete_confirmed => 1,
163         booksellername => $bookseller->name,
164         booksellerid => $booksellerid,
165     );
166 } elsif ( !$bookseller ) {
167     $template->param( NO_BOOKSELLER => 1 );
168 } elsif ($op eq 'export') {
169     print $query->header(
170         -type       => 'text/csv',
171         -attachment => 'basket' . $basket->{'basketno'} . '.csv',
172     );
173     my $csv_profile_id = $query->param('csv_profile');
174     if (defined $csv_profile_id) {
175         # using a csv profile
176         print GetBasketAsCSV(scalar $query->param('basketno'), $query, $csv_profile_id);
177     } else {
178         # using default
179         print GetBasketAsCSV(scalar $query->param('basketno'), $query);
180     }
181     exit;
182 } elsif ($op eq 'email') {
183     my $err = eval {
184         SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
185     };
186     if ( $@ ) {
187         push @messages, { type => 'error', code => $@ };
188     } elsif ( ref $err and exists $err->{error} ) {
189         push @messages, { type => 'error', code => $err->{error} };
190     } else {
191         push @messages, { type => 'message', code => 'email_sent' };
192     }
193
194     $op = 'list';
195 } elsif ($op eq 'close') {
196     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
197     if ($confirm) {
198         my $basketno = $query->param('basketno');
199         my $booksellerid = $query->param('booksellerid');
200         $basketno =~ /^\d+$/ and CloseBasket($basketno);
201         # if requested, create basket group, close it and attach the basket
202         if ($query->param('createbasketgroup')) {
203             my $branchcode;
204             if(C4::Context->userenv and C4::Context->userenv->{'branch'}
205               and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET") {
206                 $branchcode = C4::Context->userenv->{'branch'};
207             }
208             my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
209                             booksellerid => $booksellerid,
210                             deliveryplace => $branchcode,
211                             billingplace => $branchcode,
212                             closed => 1,
213                             });
214             ModBasket( { basketno => $basketno,
215                          basketgroupid => $basketgroupid } );
216             print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
217         } else {
218             print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
219         }
220         exit;
221     } else {
222     $template->param(
223         confirm_close   => "1",
224         booksellerid    => $booksellerid,
225         booksellername  => $bookseller->name,
226         basketno        => $basket->{'basketno'},
227         basketname      => $basket->{'basketname'},
228         basketgroupname => $basket->{'basketname'},
229     );
230     }
231 } elsif ($op eq 'reopen') {
232     ReopenBasket(scalar $query->param('basketno'));
233     print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
234 }
235 elsif ( $op eq 'ediorder' ) {
236     edi_close_and_order()
237 } elsif ( $op eq 'mod_users' ) {
238     my $basketusers_ids = $query->param('users_ids');
239     my @basketusers = split( /:/, $basketusers_ids );
240     ModBasketUsers($basketno, @basketusers);
241     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
242     exit;
243 } elsif ( $op eq 'mod_branch' ) {
244     my $branch = $query->param('branch');
245     $branch = undef if(defined $branch and $branch eq '');
246     ModBasket({
247         basketno => $basket->{basketno},
248         branch   => $branch
249     });
250     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
251     exit;
252 }
253
254 if ( $op eq 'list' ) {
255     my @branches_loop;
256     # get librarian branch...
257     if ( C4::Context->preference("IndependentBranches") ) {
258         my $userenv = C4::Context->userenv;
259         unless ( C4::Context->IsSuperLibrarian() ) {
260             my $validtest = ( $basket->{creationdate} eq '' )
261               || ( $userenv->{branch} eq $basket->{branch} )
262               || ( $userenv->{branch} eq '' )
263               || ( $basket->{branch}  eq '' );
264             unless ($validtest) {
265                 print $query->redirect("../mainpage.pl");
266                 exit 0;
267             }
268         }
269
270         if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
271             push @branches_loop, {
272                 branchcode => $userenv->{branch},
273                 branchname => $userenv->{branchname},
274                 selected => 1,
275             };
276         }
277     } else {
278         # get branches
279         my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
280         foreach my $branch (@$branches) {
281             my $selected = 0;
282             if (defined $basket->{branch}) {
283                 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
284             } else {
285                 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
286             }
287             push @branches_loop, {
288                 branchcode => $branch->{branchcode},
289                 branchname => $branch->{branchname},
290                 selected => $selected
291             };
292         }
293     }
294
295 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
296     my ($basketgroup, $basketgroups);
297     my $patron = Koha::Patrons->find($loggedinuser);
298     if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
299         $basketgroups = GetBasketgroups($basket->{booksellerid});
300         for my $bg ( @{$basketgroups} ) {
301             if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
302                 $bg->{default} = 1;
303                 $basketgroup = $bg;
304             }
305         }
306     }
307
308     # if the basket is closed, calculate estimated delivery date
309     my $estimateddeliverydate;
310     if( $basket->{closedate} ) {
311         my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
312         ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
313         $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
314     }
315
316     # if new basket, pre-fill infos
317     $basket->{creationdate} = ""            unless ( $basket->{creationdate} );
318     $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
319     $debug
320       and warn sprintf
321       "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
322       $basket->{creationdate}, $basket->{authorisedby};
323
324     my @basketusers_ids = GetBasketUsers($basketno);
325     my @basketusers;
326     foreach my $basketuser_id (@basketusers_ids) {
327         # FIXME Could be improved with a search -in
328         my $basket_patron = Koha::Patrons->find( $basketuser_id );
329         push @basketusers, $basket_patron if $basket_patron;
330     }
331
332     my $active_currency = Koha::Acquisition::Currencies->get_active;
333
334     my @orders = GetOrders( $basketno );
335     my @books_loop;
336
337     my @book_foot_loop;
338     my %foot;
339     my $total_quantity = 0;
340     my $total_tax_excluded = 0;
341     my $total_tax_included = 0;
342     my $total_tax_value = 0;
343     for my $order (@orders) {
344         my $line = get_order_infos( $order, $bookseller);
345         if ( $line->{uncertainprice} ) {
346             $template->param( uncertainprices => 1 );
347         }
348
349         $line->{tax_rate} = $line->{tax_rate_on_ordering};
350         $line->{tax_value} = $line->{tax_value_on_ordering};
351
352         push @books_loop, $line;
353
354         $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
355         $foot{$$line{tax_rate}}{tax_value} += $$line{tax_value};
356         $total_tax_value += $$line{tax_value};
357         $foot{$$line{tax_rate}}{quantity}  += $$line{quantity};
358         $total_quantity += $$line{quantity};
359         $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
360         $total_tax_excluded += $$line{total_tax_excluded};
361         $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
362         $total_tax_included += $$line{total_tax_included};
363     }
364
365     push @book_foot_loop, map {$_} values %foot;
366
367     # Get cancelled orders
368     my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
369     my @cancelledorders_loop;
370     for my $order (@cancelledorders) {
371         my $line = get_order_infos( $order, $bookseller);
372         push @cancelledorders_loop, $line;
373     }
374
375     my $contract = GetContract({
376         contractnumber => $basket->{contractnumber}
377     });
378
379     if ($basket->{basketgroupid}){
380         $basketgroup = GetBasketgroup($basket->{basketgroupid});
381     }
382     my $budgets = GetBudgetHierarchy;
383     my $has_budgets = 0;
384     foreach my $r (@{$budgets}) {
385         if (!defined $r->{budget_amount} || $r->{budget_amount} == 0) {
386             next;
387         }
388         next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
389
390         $has_budgets = 1;
391         last;
392     }
393
394     $template->param(
395         basketno             => $basketno,
396         basket               => $basket,
397         basketname           => $basket->{'basketname'},
398         basketbranchcode     => $basket->{branch},
399         basketnote           => $basket->{note},
400         basketbooksellernote => $basket->{booksellernote},
401         basketcontractno     => $basket->{contractnumber},
402         basketcontractname   => $contract->{contractname},
403         branches_loop        => \@branches_loop,
404         creationdate         => $basket->{creationdate},
405         authorisedby         => $basket->{authorisedby},
406         authorisedbyname     => $basket->{authorisedbyname},
407         users_ids            => join(':', @basketusers_ids),
408         users                => \@basketusers,
409         closedate            => $basket->{closedate},
410         estimateddeliverydate=> $estimateddeliverydate,
411         is_standing          => $basket->{is_standing},
412         deliveryplace        => $basket->{deliveryplace},
413         billingplace         => $basket->{billingplace},
414         active               => $bookseller->active,
415         booksellerid         => $bookseller->id,
416         booksellername       => $bookseller->name,
417         books_loop           => \@books_loop,
418         book_foot_loop       => \@book_foot_loop,
419         cancelledorders_loop => \@cancelledorders_loop,
420         total_quantity       => $total_quantity,
421         total_tax_excluded   => $total_tax_excluded,
422         total_tax_included   => $total_tax_included,
423         total_tax_value      => $total_tax_value,
424         currency             => $active_currency->currency,
425         listincgst           => $bookseller->listincgst,
426         basketgroups         => $basketgroups,
427         basketgroup          => $basketgroup,
428         grouped              => $basket->{basketgroupid},
429         # The double negatives and booleans here mean:
430         # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
431         #
432         # (The template has another implicit restriction that the order cannot be closed if there
433         # are any orders with uncertain prices.)
434         unclosable           => @orders ? $basket->{is_standing} : 1,
435         has_budgets          => $has_budgets,
436         duplinbatch          => $duplinbatch,
437         csv_profiles         => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
438     );
439 }
440
441 $template->param( messages => \@messages );
442 output_html_with_http_headers $query, $cookie, $template->output;
443
444 sub get_order_infos {
445     my $order = shift;
446     my $bookseller = shift;
447     my $qty = $order->{'quantity'} || 0;
448     if ( !defined $order->{quantityreceived} ) {
449         $order->{quantityreceived} = 0;
450     }
451     my $budget = GetBudget($order->{budget_id});
452     my $basket = GetBasket($order->{basketno});
453
454     my %line = %{ $order };
455     # Don't show unreceived standing orders as received
456     $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
457     $line{basketno}       = $basketno;
458     $line{budget_name}    = $budget->{budget_name};
459
460     $line{total_tax_included} = $line{ecost_tax_included} * $line{quantity};
461     $line{total_tax_excluded} = $line{ecost_tax_excluded} * $line{quantity};
462     $line{tax_value} = $line{tax_value_on_ordering};
463     $line{tax_rate} = $line{tax_rate_on_ordering};
464
465     if ( $line{uncertainprice} ) {
466         $line{rrp_tax_excluded} .= ' (Uncertain)';
467     }
468     if ( $line{'title'} ) {
469         my $volume      = $order->{'volume'};
470         my $seriestitle = $order->{'seriestitle'};
471         $line{'title'} .= " / $seriestitle" if $seriestitle;
472         $line{'title'} .= " / $volume"      if $volume;
473     }
474
475     my $biblionumber = $order->{'biblionumber'};
476     if ( $biblionumber ) { # The biblio still exists
477         my $biblio = Koha::Biblios->find( $biblionumber );
478         my $countbiblio = CountBiblioInOrders($biblionumber);
479         my $ordernumber = $order->{'ordernumber'};
480         my $cnt_subscriptions = $biblio->subscriptions->count;
481         my $itemcount   = $biblio->items->count;
482         my $holds_count = $biblio->holds->count;
483         my @items = GetItemnumbersFromOrder( $ordernumber );
484         my $itemholds  = $biblio->holds->search({ itemnumber => { -in => \@items } })->count;
485
486         # 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
487         $line{can_del_bib}          = 1 if $countbiblio <= 1 && $itemcount == scalar @items && !($cnt_subscriptions) && !($holds_count);
488         $line{items}                = ($itemcount) - (scalar @items);
489         $line{left_item}            = 1 if $line{items} >= 1;
490         $line{left_biblio}          = 1 if $countbiblio > 1;
491         $line{biblios}              = $countbiblio - 1;
492         $line{left_subscription}    = 1 if $cnt_subscriptions;
493         $line{subscriptions}        = $cnt_subscriptions;
494         ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
495         $line{left_holds_on_order}  = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
496         $line{holds}                = $holds_count;
497         $line{holds_on_order}       = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
498     }
499
500
501     my $suggestion   = GetSuggestionInfoFromBiblionumber($line{biblionumber});
502     $line{suggestionid}         = $$suggestion{suggestionid};
503     $line{surnamesuggestedby}   = $$suggestion{surnamesuggestedby};
504     $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
505
506     foreach my $key (qw(transferred_from transferred_to)) {
507         if ($line{$key}) {
508             my $order = GetOrder($line{$key});
509             my $basket = GetBasket($order->{basketno});
510             my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
511             $line{$key} = {
512                 order => $order,
513                 basket => $basket,
514                 bookseller => $bookseller,
515                 timestamp => $line{$key . '_timestamp'},
516             };
517         }
518     }
519
520     return \%line;
521 }
522
523 sub edi_close_and_order {
524     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
525     if ($confirm) {
526             my $edi_params = {
527                 basketno => $basketno,
528                 ean    => $ean,
529             };
530             if ( $basket->{branch} ) {
531                 $edi_params->{branchcode} = $basket->{branch};
532             }
533             if ( create_edi_order($edi_params) ) {
534                 #$template->param( edifile => 1 );
535             }
536         CloseBasket($basketno);
537
538         # if requested, create basket group, close it and attach the basket
539         if ( $query->param('createbasketgroup') ) {
540             my $branchcode;
541             if (    C4::Context->userenv
542                 and C4::Context->userenv->{'branch'}
543                 and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET" )
544             {
545                 $branchcode = C4::Context->userenv->{'branch'};
546             }
547             my $basketgroupid = NewBasketgroup(
548                 {
549                     name          => $basket->{basketname},
550                     booksellerid  => $booksellerid,
551                     deliveryplace => $branchcode,
552                     billingplace  => $branchcode,
553                     closed        => 1,
554                 }
555             );
556             ModBasket(
557                 {
558                     basketno      => $basketno,
559                     basketgroupid => $basketgroupid
560                 }
561             );
562             print $query->redirect(
563 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
564             );
565         }
566         else {
567             print $query->redirect(
568                 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
569             );
570         }
571         exit;
572     }
573     else {
574         $template->param(
575             edi_confirm     => 1,
576             booksellerid    => $booksellerid,
577             basketno        => $basket->{basketno},
578             basketname      => $basket->{basketname},
579             basketgroupname => $basket->{basketname},
580         );
581         if ($ean) {
582             $template->param( ean => $ean );
583         }
584
585     }
586     return;
587 }