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