Bug 26132: (follow-up) Add test to show errors in max issues amount calculation
[koha.git] / acqui / basket.pl
blobae918b93d3ef6f25e2a907e72295513762089a5f
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 = CGI->new;
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 flagsrequired => { acquisition => 'order_manage' },
89 debug => 1,
93 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
95 our $basket = GetBasket($basketno);
96 $booksellerid = $basket->{booksellerid} unless $booksellerid;
97 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
98 my $schema = Koha::Database->new()->schema();
99 my $rs = $schema->resultset('VendorEdiAccount')->search(
100 { vendor_id => $booksellerid, } );
101 $template->param( ediaccount => ($rs->count > 0));
103 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
104 $template->param(
105 cannot_manage_basket => 1,
106 basketno => $basketno,
107 basketname => $basket->{basketname},
108 booksellerid => $booksellerid,
109 booksellername => $bookseller->name,
111 output_html_with_http_headers $query, $cookie, $template->output;
112 exit;
115 # FIXME : what about the "discount" percentage?
116 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
117 # if no booksellerid in parameter, get it from basket
118 # warn "=>".$basket->{booksellerid};
119 my $op = $query->param('op') // 'list';
121 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
122 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
124 my @messages;
126 if ( $op eq 'delete_confirm' ) {
128 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
129 unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
131 my $basketno = $query->param('basketno');
132 my $delbiblio = $query->param('delbiblio');
133 my $basket_obj = Koha::Acquisition::Baskets->find($basketno);
135 my $orders = $basket_obj->orders;
137 my @cannotdelbiblios;
139 while ( my $order = $orders->next ) {
140 # cancel the order
141 $order->cancel({ delete_biblio => $delbiblio });
142 my @messages = @{ $order->messages };
144 if ( scalar @messages > 0 ) {
146 my $biblio = $order->biblio;
148 push @cannotdelbiblios, {
149 biblionumber => $biblio->id,
150 title => $biblio->title // '',
151 author => $biblio->author // '',
152 countbiblio => $biblio->active_orders->count,
153 itemcount => $biblio->items->count,
154 subscriptions => $biblio->subscriptions->count,
159 $template->param( cannotdelbiblios => \@cannotdelbiblios );
161 # delete the basket
162 $basket_obj->delete;
163 $template->param(
164 delete_confirmed => 1,
165 booksellername => $bookseller->name,
166 booksellerid => $booksellerid,
168 } elsif ( !$bookseller ) {
169 $template->param( NO_BOOKSELLER => 1 );
170 } elsif ($op eq 'export') {
171 print $query->header(
172 -type => 'text/csv',
173 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
175 my $csv_profile_id = $query->param('csv_profile');
176 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
177 exit;
178 } elsif ($op eq 'email') {
179 my $err = eval {
180 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
182 if ( $@ ) {
183 push @messages, { type => 'error', code => $@ };
184 } elsif ( ref $err and exists $err->{error} ) {
185 push @messages, { type => 'error', code => $err->{error} };
186 } else {
187 push @messages, { type => 'message', code => 'email_sent' };
190 $op = 'list';
191 } elsif ($op eq 'close') {
192 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
193 if ($confirm) {
194 my $basketno = $query->param('basketno');
195 my $booksellerid = $query->param('booksellerid');
196 $basketno =~ /^\d+$/ and CloseBasket($basketno);
197 # if requested, create basket group, close it and attach the basket
198 if ($query->param('createbasketgroup')) {
199 my $branchcode;
200 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
201 $branchcode = C4::Context->userenv->{'branch'};
203 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
204 booksellerid => $booksellerid,
205 deliveryplace => $branchcode,
206 billingplace => $branchcode,
207 closed => 1,
209 ModBasket( { basketno => $basketno,
210 basketgroupid => $basketgroupid } );
211 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
212 } else {
213 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
215 exit;
216 } else {
217 $template->param(
218 confirm_close => "1",
219 booksellerid => $booksellerid,
220 booksellername => $bookseller->name,
221 basketno => $basket->{'basketno'},
222 basketname => $basket->{'basketname'},
223 basketgroupname => $basket->{'basketname'},
226 } elsif ($op eq 'reopen') {
227 ReopenBasket(scalar $query->param('basketno'));
228 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
230 elsif ( $op eq 'ediorder' ) {
231 edi_close_and_order()
232 } elsif ( $op eq 'mod_users' ) {
233 my $basketusers_ids = $query->param('users_ids');
234 my @basketusers = split( /:/, $basketusers_ids );
235 ModBasketUsers($basketno, @basketusers);
236 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
237 exit;
238 } elsif ( $op eq 'mod_branch' ) {
239 my $branch = $query->param('branch');
240 $branch = undef if(defined $branch and $branch eq '');
241 ModBasket({
242 basketno => $basket->{basketno},
243 branch => $branch
245 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
246 exit;
249 if ( $op eq 'list' ) {
250 my @branches_loop;
251 # get librarian branch...
252 if ( C4::Context->preference("IndependentBranches") ) {
253 my $userenv = C4::Context->userenv;
254 unless ( C4::Context->IsSuperLibrarian() ) {
255 my $validtest = ( $basket->{creationdate} eq '' )
256 || ( $userenv->{branch} eq $basket->{branch} )
257 || ( $userenv->{branch} eq '' )
258 || ( $basket->{branch} eq '' );
259 unless ($validtest) {
260 print $query->redirect("../mainpage.pl");
261 exit 0;
265 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
266 push @branches_loop, {
267 branchcode => $userenv->{branch},
268 branchname => $userenv->{branchname},
269 selected => 1,
272 } else {
273 # get branches
274 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
275 foreach my $branch (@$branches) {
276 my $selected = 0;
277 if (defined $basket->{branch}) {
278 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
279 } else {
280 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
282 push @branches_loop, {
283 branchcode => $branch->{branchcode},
284 branchname => $branch->{branchname},
285 selected => $selected
290 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
291 my ($basketgroup, $basketgroups);
292 my $patron = Koha::Patrons->find($loggedinuser);
293 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
294 $basketgroups = GetBasketgroups($basket->{booksellerid});
295 for my $bg ( @{$basketgroups} ) {
296 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
297 $bg->{default} = 1;
298 $basketgroup = $bg;
303 # if the basket is closed, calculate estimated delivery date
304 my $estimateddeliverydate;
305 if( $basket->{closedate} ) {
306 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
307 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
308 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
311 # if new basket, pre-fill infos
312 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
313 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
314 $debug
315 and warn sprintf
316 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
317 $basket->{creationdate}, $basket->{authorisedby};
319 my @basketusers_ids = GetBasketUsers($basketno);
320 my @basketusers;
321 foreach my $basketuser_id (@basketusers_ids) {
322 # FIXME Could be improved with a search -in
323 my $basket_patron = Koha::Patrons->find( $basketuser_id );
324 push @basketusers, $basket_patron if $basket_patron;
327 my $active_currency = Koha::Acquisition::Currencies->get_active;
329 my @orders = GetOrders( $basketno );
330 my @books_loop;
332 my @book_foot_loop;
333 my %foot;
334 my $total_quantity = 0;
335 my $total_tax_excluded = 0;
336 my $total_tax_included = 0;
337 my $total_tax_value = 0;
338 for my $order (@orders) {
339 my $line = get_order_infos( $order, $bookseller);
340 if ( $line->{uncertainprice} ) {
341 $template->param( uncertainprices => 1 );
344 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
345 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
347 push @books_loop, $line;
349 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
350 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
351 $total_tax_value += $$line{tax_value};
352 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
353 $total_quantity += $$line{quantity};
354 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
355 $total_tax_excluded += $$line{total_tax_excluded};
356 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
357 $total_tax_included += $$line{total_tax_included};
360 push @book_foot_loop, map {$_} values %foot;
362 # Get cancelled orders
363 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
364 my @cancelledorders_loop;
365 for my $order (@cancelledorders) {
366 my $line = get_order_infos( $order, $bookseller);
367 push @cancelledorders_loop, $line;
370 my $contract = GetContract({
371 contractnumber => $basket->{contractnumber}
374 if ($basket->{basketgroupid}){
375 $basketgroup = GetBasketgroup($basket->{basketgroupid});
377 my $budgets = GetBudgetHierarchy;
378 my $has_budgets = 0;
379 foreach my $r (@{$budgets}) {
380 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
382 $has_budgets = 1;
383 last;
386 $template->param(
387 basketno => $basketno,
388 basket => $basket,
389 basketname => $basket->{'basketname'},
390 basketbranchcode => $basket->{branch},
391 basketnote => $basket->{note},
392 basketbooksellernote => $basket->{booksellernote},
393 basketcontractno => $basket->{contractnumber},
394 basketcontractname => $contract->{contractname},
395 branches_loop => \@branches_loop,
396 creationdate => $basket->{creationdate},
397 authorisedby => $basket->{authorisedby},
398 authorisedbyname => $basket->{authorisedbyname},
399 users_ids => join(':', @basketusers_ids),
400 users => \@basketusers,
401 closedate => $basket->{closedate},
402 estimateddeliverydate=> $estimateddeliverydate,
403 is_standing => $basket->{is_standing},
404 deliveryplace => $basket->{deliveryplace},
405 billingplace => $basket->{billingplace},
406 active => $bookseller->active,
407 booksellerid => $bookseller->id,
408 booksellername => $bookseller->name,
409 books_loop => \@books_loop,
410 book_foot_loop => \@book_foot_loop,
411 cancelledorders_loop => \@cancelledorders_loop,
412 total_quantity => $total_quantity,
413 total_tax_excluded => $total_tax_excluded,
414 total_tax_included => $total_tax_included,
415 total_tax_value => $total_tax_value,
416 currency => $active_currency->currency,
417 listincgst => $bookseller->listincgst,
418 basketgroups => $basketgroups,
419 basketgroup => $basketgroup,
420 grouped => $basket->{basketgroupid},
421 # The double negatives and booleans here mean:
422 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
424 # (The template has another implicit restriction that the order cannot be closed if there
425 # are any orders with uncertain prices.)
426 unclosable => @orders || @cancelledorders ? $basket->{is_standing} : 1,
427 has_budgets => $has_budgets,
428 duplinbatch => $duplinbatch,
429 csv_profiles => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
430 available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
431 additional_field_values => { map {
432 $_->field->name => $_->value
433 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
437 $template->param( messages => \@messages );
438 output_html_with_http_headers $query, $cookie, $template->output;
440 sub get_order_infos {
441 my $order = shift;
442 my $bookseller = shift;
443 my $qty = $order->{'quantity'} || 0;
444 if ( !defined $order->{quantityreceived} ) {
445 $order->{quantityreceived} = 0;
447 my $budget = GetBudget($order->{budget_id});
448 my $basket = GetBasket($order->{basketno});
450 my %line = %{ $order };
451 # Don't show unreceived standing orders as received
452 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
453 $line{basketno} = $basketno;
454 $line{budget_name} = $budget->{budget_name};
456 # If we have an actual cost that should be the total, otherwise use the ecost
457 $line{unitprice_tax_included} += 0;
458 $line{unitprice_tax_excluded} += 0;
459 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
460 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
461 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
462 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
463 $line{tax_value} = $line{tax_value_on_ordering};
464 $line{tax_rate} = $line{tax_rate_on_ordering};
466 if ( $line{'title'} ) {
467 my $volume = $order->{'volume'};
468 my $seriestitle = $order->{'seriestitle'};
469 $line{'title'} .= " / $seriestitle" if $seriestitle;
470 $line{'title'} .= " / $volume" if $volume;
473 my $biblionumber = $order->{'biblionumber'};
474 if ( $biblionumber ) { # The biblio still exists
475 my $biblio = Koha::Biblios->find( $biblionumber );
476 my $countbiblio = $biblio->active_orders->count;
478 my $ordernumber = $order->{'ordernumber'};
479 my $cnt_subscriptions = $biblio->subscriptions->count;
480 my $itemcount = $biblio->items->count;
481 my $holds_count = $biblio->holds->count;
482 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
483 my $items = $order->items;
484 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
486 # 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
487 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
488 $line{items} = $itemcount - $items->count;
489 $line{left_item} = 1 if $line{items} >= 1;
490 $line{left_biblio} = 1 if $countbiblio > 1;
491 $line{biblios} = $countbiblio - 1;
492 $line{left_subscription} = 1 if $cnt_subscriptions;
493 $line{subscriptions} = $cnt_subscriptions;
494 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
495 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
496 $line{holds} = $holds_count;
497 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
498 $line{order_object} = $order;
502 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
503 $line{suggestionid} = $$suggestion{suggestionid};
504 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
505 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
507 foreach my $key (qw(transferred_from transferred_to)) {
508 if ($line{$key}) {
509 my $order = GetOrder($line{$key});
510 my $basket = GetBasket($order->{basketno});
511 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
512 $line{$key} = {
513 order => $order,
514 basket => $basket,
515 bookseller => $bookseller,
516 timestamp => $line{$key . '_timestamp'},
521 return \%line;
524 sub edi_close_and_order {
525 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
526 if ($confirm) {
527 my $edi_params = {
528 basketno => $basketno,
529 ean => $ean,
531 if ( $basket->{branch} ) {
532 $edi_params->{branchcode} = $basket->{branch};
534 if ( create_edi_order($edi_params) ) {
535 #$template->param( edifile => 1 );
537 CloseBasket($basketno);
539 # if requested, create basket group, close it and attach the basket
540 if ( $query->param('createbasketgroup') ) {
541 my $branchcode;
542 if ( C4::Context->userenv
543 and C4::Context->userenv->{'branch'} )
545 $branchcode = C4::Context->userenv->{'branch'};
547 my $basketgroupid = NewBasketgroup(
549 name => $basket->{basketname},
550 booksellerid => $booksellerid,
551 deliveryplace => $branchcode,
552 billingplace => $branchcode,
553 closed => 1,
556 ModBasket(
558 basketno => $basketno,
559 basketgroupid => $basketgroupid
562 print $query->redirect(
563 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
566 else {
567 print $query->redirect(
568 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
571 exit;
573 else {
574 $template->param(
575 edi_confirm => 1,
576 booksellerid => $booksellerid,
577 basketno => $basket->{basketno},
578 basketname => $basket->{basketname},
579 basketgroupname => $basket->{basketname},
581 if ($ean) {
582 $template->param( ean => $ean );
586 return;