Bug 9573: Lost items report - Add items.notforloan as a filter
[koha.git] / acqui / basket.pl
1 #!/usr/bin/perl
2
3 #script to show display basket of orders
4
5 # Copyright 2000 - 2004 Katipo
6 # Copyright 2008 - 2009 BibLibre SARL
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it
11 # under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # Koha is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with Koha; if not, see <http://www.gnu.org/licenses>.
22
23 use Modern::Perl;
24 use C4::Auth;
25 use C4::Koha;
26 use C4::Output;
27 use CGI qw ( -utf8 );
28 use C4::Acquisition;
29 use C4::Budgets;
30 use C4::Contract;
31 use C4::Debug;
32 use C4::Biblio;
33 use C4::Items;
34 use C4::Suggestions;
35 use Koha::Biblios;
36 use Koha::Acquisition::Booksellers;
37 use Koha::Libraries;
38 use C4::Letters qw/SendAlerts/;
39 use Date::Calc qw/Add_Delta_Days/;
40 use Koha::Database;
41 use Koha::EDI qw( create_edi_order get_edifact_ean );
42 use Koha::CsvProfiles;
43 use Koha::Patrons;
44
45 =head1 NAME
46
47 basket.pl
48
49 =head1 DESCRIPTION
50
51  This script display all informations about basket for the supplier given
52  on input arg.  Moreover, it allows us to add a new order for this supplier from
53  an existing record, a suggestion or a new record.
54
55 =head1 CGI PARAMETERS
56
57 =over 4
58
59 =item $basketno
60
61 The basket number.
62
63 =item booksellerid
64
65 the supplier this script have to display the basket.
66
67 =item order
68
69 =back
70
71 =cut
72
73 our $query        = new CGI;
74 our $basketno     = $query->param('basketno');
75 our $ean          = $query->param('ean');
76 our $booksellerid = $query->param('booksellerid');
77 my $duplinbatch =  $query->param('duplinbatch');
78
79 our ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
80     {
81         template_name   => "acqui/basket.tt",
82         query           => $query,
83         type            => "intranet",
84         authnotrequired => 0,
85         flagsrequired   => { acquisition => 'order_manage' },
86         debug           => 1,
87     }
88 );
89
90 our $basket = GetBasket($basketno);
91 $booksellerid = $basket->{booksellerid} unless $booksellerid;
92 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
93 my $schema = Koha::Database->new()->schema();
94 my $rs = $schema->resultset('VendorEdiAccount')->search(
95     { vendor_id => $booksellerid, } );
96 $template->param( ediaccount => ($rs->count > 0));
97
98 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
99     $template->param(
100         cannot_manage_basket => 1,
101         basketno => $basketno,
102         basketname => $basket->{basketname},
103         booksellerid => $booksellerid,
104         booksellername => $bookseller->name,
105     );
106     output_html_with_http_headers $query, $cookie, $template->output;
107     exit;
108 }
109
110 # FIXME : what about the "discount" percentage?
111 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
112 # if no booksellerid in parameter, get it from basket
113 # warn "=>".$basket->{booksellerid};
114 my $op = $query->param('op') // 'list';
115
116 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
117 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
118
119 my @messages;
120
121 if ( $op eq 'delete_confirm' ) {
122     my $basketno = $query->param('basketno');
123     my $delbiblio = $query->param('delbiblio');
124     my @orders = GetOrders($basketno);
125 #Delete all orders included in that basket, and all items received.
126     foreach my $myorder (@orders){
127         DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
128     }
129 # if $delbiblio = 1, delete the records if possible
130     if ((defined $delbiblio)and ($delbiblio ==1)){
131         my @cannotdelbiblios ;
132         foreach my $myorder (@orders){
133             my $biblionumber = $myorder->{'biblionumber'};
134             my $biblio = Koha::Biblios->find( $biblionumber );
135             my $countbiblio = CountBiblioInOrders($biblionumber);
136             my $ordernumber = $myorder->{'ordernumber'};
137             my $cnt_subscriptions = $biblio->subscriptions->count;
138             my $itemcount = $biblio->items->count;
139             my $error;
140             if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
141                 $error = DelBiblio($myorder->{biblionumber}) }
142             else {
143                 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
144                                          title=> $myorder->{'title'},
145                                          author=> $myorder->{'author'},
146                                          countbiblio=> $countbiblio,
147                                          itemcount=>$itemcount,
148                                          subscriptions => $cnt_subscriptions};
149             }
150             if ($error) {
151                 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
152                                          title=> $myorder->{'title'},
153                                          author=> $myorder->{'author'},
154                                          othererror=> $error};
155             }
156         }
157         $template->param( cannotdelbiblios => \@cannotdelbiblios );
158     }
159  # delete the basket
160     DelBasket($basketno,);
161     $template->param(
162         delete_confirmed => 1,
163         booksellername => $bookseller->name,
164         booksellerid => $booksellerid,
165     );
166 } elsif ( !$bookseller ) {
167     $template->param( NO_BOOKSELLER => 1 );
168 } elsif ($op eq 'export') {
169     print $query->header(
170         -type       => 'text/csv',
171         -attachment => 'basket' . $basket->{'basketno'} . '.csv',
172     );
173     my $csv_profile_id = $query->param('csv_profile');
174     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         my $basketno = $query->param('basketno');
193         my $booksellerid = $query->param('booksellerid');
194         $basketno =~ /^\d+$/ and CloseBasket($basketno);
195         # if requested, create basket group, close it and attach the basket
196         if ($query->param('createbasketgroup')) {
197             my $branchcode;
198             if(C4::Context->userenv and C4::Context->userenv->{'branch'}
199               and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET") {
200                 $branchcode = C4::Context->userenv->{'branch'};
201             }
202             my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
203                             booksellerid => $booksellerid,
204                             deliveryplace => $branchcode,
205                             billingplace => $branchcode,
206                             closed => 1,
207                             });
208             ModBasket( { basketno => $basketno,
209                          basketgroupid => $basketgroupid } );
210             print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
211         } else {
212             print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
213         }
214         exit;
215     } else {
216     $template->param(
217         confirm_close   => "1",
218         booksellerid    => $booksellerid,
219         booksellername  => $bookseller->name,
220         basketno        => $basket->{'basketno'},
221         basketname      => $basket->{'basketname'},
222         basketgroupname => $basket->{'basketname'},
223     );
224     }
225 } elsif ($op eq 'reopen') {
226     ReopenBasket(scalar $query->param('basketno'));
227     print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
228 }
229 elsif ( $op eq 'ediorder' ) {
230     edi_close_and_order()
231 } elsif ( $op eq 'mod_users' ) {
232     my $basketusers_ids = $query->param('users_ids');
233     my @basketusers = split( /:/, $basketusers_ids );
234     ModBasketUsers($basketno, @basketusers);
235     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
236     exit;
237 } elsif ( $op eq 'mod_branch' ) {
238     my $branch = $query->param('branch');
239     $branch = undef if(defined $branch and $branch eq '');
240     ModBasket({
241         basketno => $basket->{basketno},
242         branch   => $branch
243     });
244     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
245     exit;
246 }
247
248 if ( $op eq 'list' ) {
249     my @branches_loop;
250     # get librarian branch...
251     if ( C4::Context->preference("IndependentBranches") ) {
252         my $userenv = C4::Context->userenv;
253         unless ( C4::Context->IsSuperLibrarian() ) {
254             my $validtest = ( $basket->{creationdate} eq '' )
255               || ( $userenv->{branch} eq $basket->{branch} )
256               || ( $userenv->{branch} eq '' )
257               || ( $basket->{branch}  eq '' );
258             unless ($validtest) {
259                 print $query->redirect("../mainpage.pl");
260                 exit 0;
261             }
262         }
263
264         if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
265             push @branches_loop, {
266                 branchcode => $userenv->{branch},
267                 branchname => $userenv->{branchname},
268                 selected => 1,
269             };
270         }
271     } else {
272         # get branches
273         my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
274         foreach my $branch (@$branches) {
275             my $selected = 0;
276             if (defined $basket->{branch}) {
277                 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
278             } else {
279                 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
280             }
281             push @branches_loop, {
282                 branchcode => $branch->{branchcode},
283                 branchname => $branch->{branchname},
284                 selected => $selected
285             };
286         }
287     }
288
289 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
290     my ($basketgroup, $basketgroups);
291     my $patron = Koha::Patrons->find($loggedinuser);
292     if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
293         $basketgroups = GetBasketgroups($basket->{booksellerid});
294         for my $bg ( @{$basketgroups} ) {
295             if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
296                 $bg->{default} = 1;
297                 $basketgroup = $bg;
298             }
299         }
300     }
301
302     # if the basket is closed, calculate estimated delivery date
303     my $estimateddeliverydate;
304     if( $basket->{closedate} ) {
305         my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
306         ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
307         $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
308     }
309
310     # if new basket, pre-fill infos
311     $basket->{creationdate} = ""            unless ( $basket->{creationdate} );
312     $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
313     $debug
314       and warn sprintf
315       "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
316       $basket->{creationdate}, $basket->{authorisedby};
317
318     my @basketusers_ids = GetBasketUsers($basketno);
319     my @basketusers;
320     foreach my $basketuser_id (@basketusers_ids) {
321         # FIXME Could be improved with a search -in
322         my $basket_patron = Koha::Patrons->find( $basketuser_id );
323         push @basketusers, $basket_patron if $basket_patron;
324     }
325
326     my $active_currency = Koha::Acquisition::Currencies->get_active;
327
328     my @orders = GetOrders( $basketno );
329     my @books_loop;
330
331     my @book_foot_loop;
332     my %foot;
333     my $total_quantity = 0;
334     my $total_tax_excluded = 0;
335     my $total_tax_included = 0;
336     my $total_tax_value = 0;
337     for my $order (@orders) {
338         my $line = get_order_infos( $order, $bookseller);
339         if ( $line->{uncertainprice} ) {
340             $template->param( uncertainprices => 1 );
341         }
342
343         $line->{tax_rate} = $line->{tax_rate_on_ordering};
344         $line->{tax_value} = $line->{tax_value_on_ordering};
345
346         push @books_loop, $line;
347
348         $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
349         $foot{$$line{tax_rate}}{tax_value} += $$line{tax_value};
350         $total_tax_value += $$line{tax_value};
351         $foot{$$line{tax_rate}}{quantity}  += $$line{quantity};
352         $total_quantity += $$line{quantity};
353         $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
354         $total_tax_excluded += $$line{total_tax_excluded};
355         $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
356         $total_tax_included += $$line{total_tax_included};
357     }
358
359     push @book_foot_loop, map {$_} values %foot;
360
361     # Get cancelled orders
362     my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
363     my @cancelledorders_loop;
364     for my $order (@cancelledorders) {
365         my $line = get_order_infos( $order, $bookseller);
366         push @cancelledorders_loop, $line;
367     }
368
369     my $contract = GetContract({
370         contractnumber => $basket->{contractnumber}
371     });
372
373     if ($basket->{basketgroupid}){
374         $basketgroup = GetBasketgroup($basket->{basketgroupid});
375     }
376     my $budgets = GetBudgetHierarchy;
377     my $has_budgets = 0;
378     foreach my $r (@{$budgets}) {
379         if (!defined $r->{budget_amount} || $r->{budget_amount} == 0) {
380             next;
381         }
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     );
433 }
434
435 $template->param( messages => \@messages );
436 output_html_with_http_headers $query, $cookie, $template->output;
437
438 sub get_order_infos {
439     my $order = shift;
440     my $bookseller = shift;
441     my $qty = $order->{'quantity'} || 0;
442     if ( !defined $order->{quantityreceived} ) {
443         $order->{quantityreceived} = 0;
444     }
445     my $budget = GetBudget($order->{budget_id});
446     my $basket = GetBasket($order->{basketno});
447
448     my %line = %{ $order };
449     # Don't show unreceived standing orders as received
450     $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
451     $line{basketno}       = $basketno;
452     $line{budget_name}    = $budget->{budget_name};
453
454     $line{total_tax_included} = $line{ecost_tax_included} * $line{quantity};
455     $line{total_tax_excluded} = $line{ecost_tax_excluded} * $line{quantity};
456     $line{tax_value} = $line{tax_value_on_ordering};
457     $line{tax_rate} = $line{tax_rate_on_ordering};
458
459     if ( $line{uncertainprice} ) {
460         $line{rrp_tax_excluded} .= ' (Uncertain)';
461     }
462     if ( $line{'title'} ) {
463         my $volume      = $order->{'volume'};
464         my $seriestitle = $order->{'seriestitle'};
465         $line{'title'} .= " / $seriestitle" if $seriestitle;
466         $line{'title'} .= " / $volume"      if $volume;
467     }
468
469     my $biblionumber = $order->{'biblionumber'};
470     if ( $biblionumber ) { # The biblio still exists
471         my $biblio = Koha::Biblios->find( $biblionumber );
472         my $countbiblio = CountBiblioInOrders($biblionumber);
473         my $ordernumber = $order->{'ordernumber'};
474         my $cnt_subscriptions = $biblio->subscriptions->count;
475         my $itemcount   = $biblio->items->count;
476         my $holds_count = $biblio->holds->count;
477         my @items = GetItemnumbersFromOrder( $ordernumber );
478         my $itemholds  = $biblio->holds->search({ itemnumber => { -in => \@items } })->count;
479
480         # 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
481         $line{can_del_bib}          = 1 if $countbiblio <= 1 && $itemcount == scalar @items && !($cnt_subscriptions) && !($holds_count);
482         $line{items}                = ($itemcount) - (scalar @items);
483         $line{left_item}            = 1 if $line{items} >= 1;
484         $line{left_biblio}          = 1 if $countbiblio > 1;
485         $line{biblios}              = $countbiblio - 1;
486         $line{left_subscription}    = 1 if $cnt_subscriptions;
487         $line{subscriptions}        = $cnt_subscriptions;
488         ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
489         $line{left_holds_on_order}  = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
490         $line{holds}                = $holds_count;
491         $line{holds_on_order}       = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
492     }
493
494
495     my $suggestion   = GetSuggestionInfoFromBiblionumber($line{biblionumber});
496     $line{suggestionid}         = $$suggestion{suggestionid};
497     $line{surnamesuggestedby}   = $$suggestion{surnamesuggestedby};
498     $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
499
500     foreach my $key (qw(transferred_from transferred_to)) {
501         if ($line{$key}) {
502             my $order = GetOrder($line{$key});
503             my $basket = GetBasket($order->{basketno});
504             my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
505             $line{$key} = {
506                 order => $order,
507                 basket => $basket,
508                 bookseller => $bookseller,
509                 timestamp => $line{$key . '_timestamp'},
510             };
511         }
512     }
513
514     return \%line;
515 }
516
517 sub edi_close_and_order {
518     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
519     if ($confirm) {
520             my $edi_params = {
521                 basketno => $basketno,
522                 ean    => $ean,
523             };
524             if ( $basket->{branch} ) {
525                 $edi_params->{branchcode} = $basket->{branch};
526             }
527             if ( create_edi_order($edi_params) ) {
528                 #$template->param( edifile => 1 );
529             }
530         CloseBasket($basketno);
531
532         # if requested, create basket group, close it and attach the basket
533         if ( $query->param('createbasketgroup') ) {
534             my $branchcode;
535             if (    C4::Context->userenv
536                 and C4::Context->userenv->{'branch'}
537                 and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET" )
538             {
539                 $branchcode = C4::Context->userenv->{'branch'};
540             }
541             my $basketgroupid = NewBasketgroup(
542                 {
543                     name          => $basket->{basketname},
544                     booksellerid  => $booksellerid,
545                     deliveryplace => $branchcode,
546                     billingplace  => $branchcode,
547                     closed        => 1,
548                 }
549             );
550             ModBasket(
551                 {
552                     basketno      => $basketno,
553                     basketgroupid => $basketgroupid
554                 }
555             );
556             print $query->redirect(
557 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
558             );
559         }
560         else {
561             print $query->redirect(
562                 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
563             );
564         }
565         exit;
566     }
567     else {
568         $template->param(
569             edi_confirm     => 1,
570             booksellerid    => $booksellerid,
571             basketno        => $basket->{basketno},
572             basketname      => $basket->{basketname},
573             basketgroupname => $basket->{basketname},
574         );
575         if ($ean) {
576             $template->param( ean => $ean );
577         }
578
579     }
580     return;
581 }