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