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