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