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