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