Bug 26063: Use Koha::Plugins->call for some other hooks
[koha.git] / acqui / basket.pl
blob4ef5facafa180711e51f041384a0ff5cefb0f890
1 #!/usr/bin/perl
3 #script to show display basket of orders
5 # Copyright 2000 - 2004 Katipo
6 # Copyright 2008 - 2009 BibLibre SARL
8 # This file is part of Koha.
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.
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.
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>.
23 use Modern::Perl;
24 use C4::Auth;
25 use C4::Koha;
26 use C4::Output;
27 use CGI qw ( -utf8 );
28 use C4::Acquisition;
29 use C4::Budgets;
30 use C4::Contract;
31 use C4::Debug;
32 use C4::Biblio;
33 use C4::Items;
34 use C4::Suggestions;
35 use Koha::Biblios;
36 use Koha::Acquisition::Baskets;
37 use Koha::Acquisition::Booksellers;
38 use Koha::Acquisition::Orders;
39 use Koha::Libraries;
40 use C4::Letters qw/SendAlerts/;
41 use Date::Calc qw/Add_Delta_Days/;
42 use Koha::Database;
43 use Koha::EDI qw( create_edi_order get_edifact_ean );
44 use Koha::CsvProfiles;
45 use Koha::Patrons;
47 use Koha::AdditionalFields;
49 =head1 NAME
51 basket.pl
53 =head1 DESCRIPTION
55 This script display all informations about basket for the supplier given
56 on input arg. Moreover, it allows us to add a new order for this supplier from
57 an existing record, a suggestion or a new record.
59 =head1 CGI PARAMETERS
61 =over 4
63 =item $basketno
65 The basket number.
67 =item booksellerid
69 the supplier this script have to display the basket.
71 =item order
73 =back
75 =cut
77 our $query = new CGI;
78 our $basketno = $query->param('basketno');
79 our $ean = $query->param('ean');
80 our $booksellerid = $query->param('booksellerid');
81 my $duplinbatch = $query->param('duplinbatch');
83 our ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
85 template_name => "acqui/basket.tt",
86 query => $query,
87 type => "intranet",
88 authnotrequired => 0,
89 flagsrequired => { acquisition => 'order_manage' },
90 debug => 1,
94 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
96 our $basket = GetBasket($basketno);
97 $booksellerid = $basket->{booksellerid} unless $booksellerid;
98 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
99 my $schema = Koha::Database->new()->schema();
100 my $rs = $schema->resultset('VendorEdiAccount')->search(
101 { vendor_id => $booksellerid, } );
102 $template->param( ediaccount => ($rs->count > 0));
104 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
105 $template->param(
106 cannot_manage_basket => 1,
107 basketno => $basketno,
108 basketname => $basket->{basketname},
109 booksellerid => $booksellerid,
110 booksellername => $bookseller->name,
112 output_html_with_http_headers $query, $cookie, $template->output;
113 exit;
116 # FIXME : what about the "discount" percentage?
117 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
118 # if no booksellerid in parameter, get it from basket
119 # warn "=>".$basket->{booksellerid};
120 my $op = $query->param('op') // 'list';
122 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
123 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
125 my @messages;
127 if ( $op eq 'delete_confirm' ) {
129 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
130 unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
132 my $basketno = $query->param('basketno');
133 my $delbiblio = $query->param('delbiblio');
134 my @orders = GetOrders($basketno);
135 #Delete all orders included in that basket, and all items received.
136 foreach my $myorder (@orders){
137 DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
139 # if $delbiblio = 1, delete the records if possible
140 if ((defined $delbiblio)and ($delbiblio ==1)){
141 my @cannotdelbiblios ;
142 foreach my $myorder (@orders){
143 my $biblionumber = $myorder->{'biblionumber'};
144 my $biblio = Koha::Biblios->find( $biblionumber );
145 my $countbiblio = $biblio->active_orders->count;
146 my $ordernumber = $myorder->{'ordernumber'};
147 my $cnt_subscriptions = $biblio->subscriptions->count;
148 my $itemcount = $biblio->items->count;
149 my $error;
150 if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
151 $error = DelBiblio($myorder->{biblionumber}) }
152 else {
153 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
154 title=> $myorder->{'title'},
155 author=> $myorder->{'author'},
156 countbiblio=> $countbiblio,
157 itemcount=>$itemcount,
158 subscriptions => $cnt_subscriptions};
160 if ($error) {
161 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
162 title=> $myorder->{'title'},
163 author=> $myorder->{'author'},
164 othererror=> $error};
167 $template->param( cannotdelbiblios => \@cannotdelbiblios );
169 # delete the basket
170 DelBasket($basketno,);
171 $template->param(
172 delete_confirmed => 1,
173 booksellername => $bookseller->name,
174 booksellerid => $booksellerid,
176 } elsif ( !$bookseller ) {
177 $template->param( NO_BOOKSELLER => 1 );
178 } elsif ($op eq 'export') {
179 print $query->header(
180 -type => 'text/csv',
181 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
183 my $csv_profile_id = $query->param('csv_profile');
184 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
185 exit;
186 } elsif ($op eq 'email') {
187 my $err = eval {
188 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
190 if ( $@ ) {
191 push @messages, { type => 'error', code => $@ };
192 } elsif ( ref $err and exists $err->{error} ) {
193 push @messages, { type => 'error', code => $err->{error} };
194 } else {
195 push @messages, { type => 'message', code => 'email_sent' };
198 $op = 'list';
199 } elsif ($op eq 'close') {
200 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
201 if ($confirm) {
202 my $basketno = $query->param('basketno');
203 my $booksellerid = $query->param('booksellerid');
204 $basketno =~ /^\d+$/ and CloseBasket($basketno);
205 # if requested, create basket group, close it and attach the basket
206 if ($query->param('createbasketgroup')) {
207 my $branchcode;
208 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
209 $branchcode = C4::Context->userenv->{'branch'};
211 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
212 booksellerid => $booksellerid,
213 deliveryplace => $branchcode,
214 billingplace => $branchcode,
215 closed => 1,
217 ModBasket( { basketno => $basketno,
218 basketgroupid => $basketgroupid } );
219 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
220 } else {
221 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
223 exit;
224 } else {
225 $template->param(
226 confirm_close => "1",
227 booksellerid => $booksellerid,
228 booksellername => $bookseller->name,
229 basketno => $basket->{'basketno'},
230 basketname => $basket->{'basketname'},
231 basketgroupname => $basket->{'basketname'},
234 } elsif ($op eq 'reopen') {
235 ReopenBasket(scalar $query->param('basketno'));
236 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
238 elsif ( $op eq 'ediorder' ) {
239 edi_close_and_order()
240 } elsif ( $op eq 'mod_users' ) {
241 my $basketusers_ids = $query->param('users_ids');
242 my @basketusers = split( /:/, $basketusers_ids );
243 ModBasketUsers($basketno, @basketusers);
244 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
245 exit;
246 } elsif ( $op eq 'mod_branch' ) {
247 my $branch = $query->param('branch');
248 $branch = undef if(defined $branch and $branch eq '');
249 ModBasket({
250 basketno => $basket->{basketno},
251 branch => $branch
253 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
254 exit;
257 if ( $op eq 'list' ) {
258 my @branches_loop;
259 # get librarian branch...
260 if ( C4::Context->preference("IndependentBranches") ) {
261 my $userenv = C4::Context->userenv;
262 unless ( C4::Context->IsSuperLibrarian() ) {
263 my $validtest = ( $basket->{creationdate} eq '' )
264 || ( $userenv->{branch} eq $basket->{branch} )
265 || ( $userenv->{branch} eq '' )
266 || ( $basket->{branch} eq '' );
267 unless ($validtest) {
268 print $query->redirect("../mainpage.pl");
269 exit 0;
273 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
274 push @branches_loop, {
275 branchcode => $userenv->{branch},
276 branchname => $userenv->{branchname},
277 selected => 1,
280 } else {
281 # get branches
282 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
283 foreach my $branch (@$branches) {
284 my $selected = 0;
285 if (defined $basket->{branch}) {
286 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
287 } else {
288 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
290 push @branches_loop, {
291 branchcode => $branch->{branchcode},
292 branchname => $branch->{branchname},
293 selected => $selected
298 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
299 my ($basketgroup, $basketgroups);
300 my $patron = Koha::Patrons->find($loggedinuser);
301 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
302 $basketgroups = GetBasketgroups($basket->{booksellerid});
303 for my $bg ( @{$basketgroups} ) {
304 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
305 $bg->{default} = 1;
306 $basketgroup = $bg;
311 # if the basket is closed, calculate estimated delivery date
312 my $estimateddeliverydate;
313 if( $basket->{closedate} ) {
314 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
315 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
316 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
319 # if new basket, pre-fill infos
320 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
321 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
322 $debug
323 and warn sprintf
324 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
325 $basket->{creationdate}, $basket->{authorisedby};
327 my @basketusers_ids = GetBasketUsers($basketno);
328 my @basketusers;
329 foreach my $basketuser_id (@basketusers_ids) {
330 # FIXME Could be improved with a search -in
331 my $basket_patron = Koha::Patrons->find( $basketuser_id );
332 push @basketusers, $basket_patron if $basket_patron;
335 my $active_currency = Koha::Acquisition::Currencies->get_active;
337 my @orders = GetOrders( $basketno );
338 my @books_loop;
340 my @book_foot_loop;
341 my %foot;
342 my $total_quantity = 0;
343 my $total_tax_excluded = 0;
344 my $total_tax_included = 0;
345 my $total_tax_value = 0;
346 for my $order (@orders) {
347 my $line = get_order_infos( $order, $bookseller);
348 if ( $line->{uncertainprice} ) {
349 $template->param( uncertainprices => 1 );
352 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
353 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
355 push @books_loop, $line;
357 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
358 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
359 $total_tax_value += $$line{tax_value};
360 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
361 $total_quantity += $$line{quantity};
362 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
363 $total_tax_excluded += $$line{total_tax_excluded};
364 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
365 $total_tax_included += $$line{total_tax_included};
368 push @book_foot_loop, map {$_} values %foot;
370 # Get cancelled orders
371 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
372 my @cancelledorders_loop;
373 for my $order (@cancelledorders) {
374 my $line = get_order_infos( $order, $bookseller);
375 push @cancelledorders_loop, $line;
378 my $contract = GetContract({
379 contractnumber => $basket->{contractnumber}
382 if ($basket->{basketgroupid}){
383 $basketgroup = GetBasketgroup($basket->{basketgroupid});
385 my $budgets = GetBudgetHierarchy;
386 my $has_budgets = 0;
387 foreach my $r (@{$budgets}) {
388 if (!defined $r->{budget_amount} || $r->{budget_amount} == 0) {
389 next;
391 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
393 $has_budgets = 1;
394 last;
397 $template->param(
398 basketno => $basketno,
399 basket => $basket,
400 basketname => $basket->{'basketname'},
401 basketbranchcode => $basket->{branch},
402 basketnote => $basket->{note},
403 basketbooksellernote => $basket->{booksellernote},
404 basketcontractno => $basket->{contractnumber},
405 basketcontractname => $contract->{contractname},
406 branches_loop => \@branches_loop,
407 creationdate => $basket->{creationdate},
408 authorisedby => $basket->{authorisedby},
409 authorisedbyname => $basket->{authorisedbyname},
410 users_ids => join(':', @basketusers_ids),
411 users => \@basketusers,
412 closedate => $basket->{closedate},
413 estimateddeliverydate=> $estimateddeliverydate,
414 is_standing => $basket->{is_standing},
415 deliveryplace => $basket->{deliveryplace},
416 billingplace => $basket->{billingplace},
417 active => $bookseller->active,
418 booksellerid => $bookseller->id,
419 booksellername => $bookseller->name,
420 books_loop => \@books_loop,
421 book_foot_loop => \@book_foot_loop,
422 cancelledorders_loop => \@cancelledorders_loop,
423 total_quantity => $total_quantity,
424 total_tax_excluded => $total_tax_excluded,
425 total_tax_included => $total_tax_included,
426 total_tax_value => $total_tax_value,
427 currency => $active_currency->currency,
428 listincgst => $bookseller->listincgst,
429 basketgroups => $basketgroups,
430 basketgroup => $basketgroup,
431 grouped => $basket->{basketgroupid},
432 # The double negatives and booleans here mean:
433 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
435 # (The template has another implicit restriction that the order cannot be closed if there
436 # are any orders with uncertain prices.)
437 unclosable => @orders ? $basket->{is_standing} : 1,
438 has_budgets => $has_budgets,
439 duplinbatch => $duplinbatch,
440 csv_profiles => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
441 available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
442 additional_field_values => { map {
443 $_->field->name => $_->value
444 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
448 $template->param( messages => \@messages );
449 output_html_with_http_headers $query, $cookie, $template->output;
451 sub get_order_infos {
452 my $order = shift;
453 my $bookseller = shift;
454 my $qty = $order->{'quantity'} || 0;
455 if ( !defined $order->{quantityreceived} ) {
456 $order->{quantityreceived} = 0;
458 my $budget = GetBudget($order->{budget_id});
459 my $basket = GetBasket($order->{basketno});
461 my %line = %{ $order };
462 # Don't show unreceived standing orders as received
463 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
464 $line{basketno} = $basketno;
465 $line{budget_name} = $budget->{budget_name};
467 # If we have an actual cost that should be the total, otherwise use the ecost
468 $line{unitprice_tax_included} += 0;
469 $line{unitprice_tax_excluded} += 0;
470 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
471 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
472 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
473 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
474 $line{tax_value} = $line{tax_value_on_ordering};
475 $line{tax_rate} = $line{tax_rate_on_ordering};
477 if ( $line{'title'} ) {
478 my $volume = $order->{'volume'};
479 my $seriestitle = $order->{'seriestitle'};
480 $line{'title'} .= " / $seriestitle" if $seriestitle;
481 $line{'title'} .= " / $volume" if $volume;
484 my $biblionumber = $order->{'biblionumber'};
485 if ( $biblionumber ) { # The biblio still exists
486 my $biblio = Koha::Biblios->find( $biblionumber );
487 my $countbiblio = $biblio->active_orders->count;
489 my $ordernumber = $order->{'ordernumber'};
490 my $cnt_subscriptions = $biblio->subscriptions->count;
491 my $itemcount = $biblio->items->count;
492 my $holds_count = $biblio->holds->count;
493 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
494 my $items = $order->items;
495 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
497 # if the biblio is not in other orders and if there is no items elsewhere and no subscriptions and no holds we can then show the link "Delete order and Biblio" see bug 5680
498 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
499 $line{items} = $itemcount - $items->count;
500 $line{left_item} = 1 if $line{items} >= 1;
501 $line{left_biblio} = 1 if $countbiblio > 1;
502 $line{biblios} = $countbiblio - 1;
503 $line{left_subscription} = 1 if $cnt_subscriptions;
504 $line{subscriptions} = $cnt_subscriptions;
505 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
506 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
507 $line{holds} = $holds_count;
508 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
509 $line{order_object} = $order;
513 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
514 $line{suggestionid} = $$suggestion{suggestionid};
515 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
516 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
518 foreach my $key (qw(transferred_from transferred_to)) {
519 if ($line{$key}) {
520 my $order = GetOrder($line{$key});
521 my $basket = GetBasket($order->{basketno});
522 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
523 $line{$key} = {
524 order => $order,
525 basket => $basket,
526 bookseller => $bookseller,
527 timestamp => $line{$key . '_timestamp'},
532 return \%line;
535 sub edi_close_and_order {
536 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
537 if ($confirm) {
538 my $edi_params = {
539 basketno => $basketno,
540 ean => $ean,
542 if ( $basket->{branch} ) {
543 $edi_params->{branchcode} = $basket->{branch};
545 if ( create_edi_order($edi_params) ) {
546 #$template->param( edifile => 1 );
548 CloseBasket($basketno);
550 # if requested, create basket group, close it and attach the basket
551 if ( $query->param('createbasketgroup') ) {
552 my $branchcode;
553 if ( C4::Context->userenv
554 and C4::Context->userenv->{'branch'} )
556 $branchcode = C4::Context->userenv->{'branch'};
558 my $basketgroupid = NewBasketgroup(
560 name => $basket->{basketname},
561 booksellerid => $booksellerid,
562 deliveryplace => $branchcode,
563 billingplace => $branchcode,
564 closed => 1,
567 ModBasket(
569 basketno => $basketno,
570 basketgroupid => $basketgroupid
573 print $query->redirect(
574 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
577 else {
578 print $query->redirect(
579 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
582 exit;
584 else {
585 $template->param(
586 edi_confirm => 1,
587 booksellerid => $booksellerid,
588 basketno => $basket->{basketno},
589 basketname => $basket->{basketname},
590 basketgroupname => $basket->{basketname},
592 if ($ean) {
593 $template->param( ean => $ean );
597 return;