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>.
24 use C4::Auth qw( get_template_and_user haspermission );
25 use C4::Output qw( output_html_with_http_headers output_and_exit );
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 );
32 use Koha::Acquisition::Baskets;
33 use Koha::Acquisition::Booksellers;
34 use Koha::Acquisition::Orders;
36 use C4::Letters qw( SendAlerts );
37 use Date::Calc qw( Add_Delta_Days );
39 use Koha::EDI qw( create_edi_order );
40 use Koha::CsvProfiles;
43 use Koha::AdditionalFields;
44 use Koha::Old::Biblios;
52 This script display all informations about basket for the supplier given
53 on input arg. Moreover, it allows us to add a new order for this supplier from
54 an existing record, a suggestion or a new record.
66 the supplier this script have to display the basket.
74 our $query = CGI->new;
75 our $basketno = $query->param('basketno');
76 our $ean = $query->param('ean');
77 our $booksellerid = $query->param('booksellerid');
78 my $duplinbatch = $query->param('duplinbatch');
80 our ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
82 template_name => "acqui/basket.tt",
85 flagsrequired => { acquisition => 'order_manage' },
89 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
91 our $basket = GetBasket($basketno);
92 $booksellerid = $basket->{booksellerid} unless $booksellerid;
93 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
94 my $schema = Koha::Database->new()->schema();
95 my $rs = $schema->resultset('VendorEdiAccount')->search(
96 { vendor_id => $booksellerid, } );
97 $template->param( ediaccount => ($rs->count > 0));
99 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
101 cannot_manage_basket => 1,
102 basketno => $basketno,
103 basketname => $basket->{basketname},
104 booksellerid => $booksellerid,
105 booksellername => $bookseller->name,
107 output_html_with_http_headers $query, $cookie, $template->output;
111 # FIXME : what about the "discount" percentage?
112 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
113 # if no booksellerid in parameter, get it from basket
114 # warn "=>".$basket->{booksellerid};
115 my $op = $query->param('op') // 'list';
117 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
118 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
122 if ( $op eq 'cud-delete-order' ) {
123 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
124 unless $logged_in_patron->has_permission( { acquisition => 'order_manage' } );
126 # We only allow deleting cancelled line without biblionumber for now
127 my $ordernumber = $query->param('ordernumber');
128 my $order = Koha::Acquisition::Orders->search(
130 biblionumber => undef,
131 ordernumber => $ordernumber, orderstatus => 'cancelled'
134 $order->delete if $order;
137 } elsif ( $op eq 'cud-delete_confirm' ) {
139 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
140 unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
142 my $basketno = $query->param('basketno');
143 my $delbiblio = $query->param('delbiblio');
144 my $basket_obj = Koha::Acquisition::Baskets->find($basketno);
146 my $orders = $basket_obj->orders->filter_by_current;
148 my @cannotdelbiblios;
150 while ( my $order = $orders->next ) {
152 $order->cancel({ delete_biblio => $delbiblio });
153 my @messages = @{ $order->object_messages };
155 if ( scalar @messages > 0 ) {
157 my $biblio = $order->biblio;
159 push @cannotdelbiblios, {
160 biblionumber => $biblio->id,
161 title => $biblio->title // '',
162 author => $biblio->author // '',
163 countbiblio => $biblio->active_orders->count,
164 itemcount => $biblio->items->count,
165 subscriptions => $biblio->subscriptions->count,
170 $template->param( cannotdelbiblios => \@cannotdelbiblios );
175 delete_confirmed => 1,
176 booksellername => $bookseller->name,
177 booksellerid => $booksellerid,
179 } elsif ( !$bookseller ) {
180 $template->param( NO_BOOKSELLER => 1 );
181 } elsif ($op eq 'export') {
182 print $query->header(
184 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
186 my $csv_profile_id = $query->param('csv_profile');
187 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
189 } elsif ($op eq 'cud-email') {
191 SendAlerts( 'orderacquisition', scalar $query->param('basketno'), 'ACQORDER' );
194 push @messages, { type => 'error', code => $@ };
195 } elsif ( ref $err and exists $err->{error} ) {
196 push @messages, { type => 'error', code => $err->{error} };
198 push @messages, { type => 'message', code => 'email_sent' };
202 } elsif ($op eq 'cud-close') {
203 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
207 # FIXME: we should fetch the object at the beginning of this script
208 # and get rid of the hash that is passed around
209 Koha::Acquisition::Baskets->find($basketno)->close;
211 # if requested, create basket group, close it and attach the basket
212 if ($query->param('createbasketgroup')) {
214 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
215 $branchcode = C4::Context->userenv->{'branch'};
217 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
218 booksellerid => $booksellerid,
219 deliveryplace => $branchcode,
220 billingplace => $branchcode,
223 ModBasket( { basketno => $basketno,
224 basketgroupid => $basketgroupid } );
225 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
227 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
232 confirm_close => "1",
233 booksellerid => $booksellerid,
234 booksellername => $bookseller->name,
235 basketno => $basket->{'basketno'},
236 basketname => $basket->{'basketname'},
237 basketgroupname => $basket->{'basketname'},
240 } elsif ($op eq 'cud-reopen') {
241 ReopenBasket(scalar $query->param('basketno'));
242 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
244 elsif ( $op eq 'cud-ediorder' ) {
245 edi_close_and_order()
246 } elsif ( $op eq 'cud-mod_users' ) {
247 my $basketusers_ids = $query->param('users_ids');
248 my @basketusers = split( /:/, $basketusers_ids );
249 ModBasketUsers($basketno, @basketusers);
250 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
252 } elsif ( $op eq 'cud-mod_branch' ) {
253 my $branch = $query->param('branch');
254 $branch = undef if(defined $branch and $branch eq '');
256 basketno => $basket->{basketno},
259 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
263 if ( $op eq 'list' ) {
265 # get librarian branch...
266 if ( C4::Context->preference("IndependentBranches") ) {
267 my $userenv = C4::Context->userenv;
268 unless ( C4::Context->IsSuperLibrarian() ) {
269 my $validtest = ( $basket->{creationdate} eq '' )
270 || ( $userenv->{branch} eq $basket->{branch} )
271 || ( $userenv->{branch} eq '' )
272 || ( $basket->{branch} eq '' );
273 unless ($validtest) {
274 print $query->redirect("../mainpage.pl");
279 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
280 push @branches_loop, {
281 branchcode => $userenv->{branch},
282 branchname => $userenv->{branchname},
288 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
289 foreach my $branch (@$branches) {
291 if (defined $basket->{branch}) {
292 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
294 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
296 push @branches_loop, {
297 branchcode => $branch->{branchcode},
298 branchname => $branch->{branchname},
299 selected => $selected
304 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
305 my ($basketgroup, $basketgroups);
306 my $patron = Koha::Patrons->find($loggedinuser);
307 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
308 $basketgroups = GetBasketgroups($basket->{booksellerid});
309 for my $bg ( @{$basketgroups} ) {
310 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
317 # if the basket is closed, calculate estimated delivery date
318 my $estimateddeliverydate;
319 if( $basket->{closedate} ) {
320 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
321 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
322 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
325 # if new basket, pre-fill infos
326 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
327 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
329 my @basketusers_ids = GetBasketUsers($basketno);
331 foreach my $basketuser_id (@basketusers_ids) {
332 # FIXME Could be improved with a search -in
333 my $basket_patron = Koha::Patrons->find( $basketuser_id );
334 push @basketusers, $basket_patron if $basket_patron;
337 my $active_currency = Koha::Acquisition::Currencies->get_active;
339 my @orders = GetOrders( $basketno );
344 my $total_quantity = 0;
345 my $total_tax_excluded = 0;
346 my $total_tax_included = 0;
347 my $total_tax_value = 0;
348 for my $order (@orders) {
349 my $line = get_order_infos( $order, $bookseller);
350 if ( $line->{uncertainprice} ) {
351 $template->param( uncertainprices => 1 );
354 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
355 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
357 push @books_loop, $line;
359 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
360 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
361 $total_tax_value += $$line{tax_value};
362 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
363 $total_quantity += $$line{quantity};
364 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
365 $total_tax_excluded += $$line{total_tax_excluded};
366 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
367 $total_tax_included += $$line{total_tax_included};
370 push @book_foot_loop, map {$_} values %foot;
372 # Get cancelled orders
373 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
374 my @cancelledorders_loop;
375 for my $order (@cancelledorders) {
376 my $line = get_order_infos( $order, $bookseller);
377 push @cancelledorders_loop, $line;
380 my $contract = GetContract({
381 contractnumber => $basket->{contractnumber}
384 if ($basket->{basketgroupid}){
385 $basketgroup = GetBasketgroup($basket->{basketgroupid});
387 my $budgets = GetBudgetHierarchy;
389 foreach my $r (@{$budgets}) {
390 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
396 my $basket_obj = Koha::Acquisition::Baskets->find($basketno);
397 my $edi_order = $basket_obj->edi_order;
400 basketno => $basketno,
402 basketname => $basket->{'basketname'},
403 basketbranchcode => $basket->{branch},
404 basketnote => $basket->{note},
405 basketbooksellernote => $basket->{booksellernote},
406 basketcontractno => $basket->{contractnumber},
407 basketcontractname => $contract->{contractname},
408 branches_loop => \@branches_loop,
409 creationdate => $basket->{creationdate},
410 edi_order => $edi_order,
411 authorisedby => $basket->{authorisedby},
412 authorisedbyname => $basket->{authorisedbyname},
413 users_ids => join(':', @basketusers_ids),
414 users => \@basketusers,
415 closedate => $basket->{closedate},
416 estimateddeliverydate=> $estimateddeliverydate,
417 is_standing => $basket->{is_standing},
418 deliveryplace => $basket->{deliveryplace},
419 billingplace => $basket->{billingplace},
420 active => $bookseller->active,
421 booksellerid => $bookseller->id,
422 booksellername => $bookseller->name,
423 books_loop => \@books_loop,
424 book_foot_loop => \@book_foot_loop,
425 cancelledorders_loop => \@cancelledorders_loop,
426 total_quantity => $total_quantity,
427 total_tax_excluded => $total_tax_excluded,
428 total_tax_included => $total_tax_included,
429 total_tax_value => $total_tax_value,
430 currency => $active_currency->currency,
431 listincgst => $bookseller->listincgst,
432 basketgroups => $basketgroups,
433 basketgroup => $basketgroup,
434 grouped => $basket->{basketgroupid},
435 # The double negatives and booleans here mean:
436 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
438 # (The template has another implicit restriction that the order cannot be closed if there
439 # are any orders with uncertain prices.)
440 unclosable => @orders || @cancelledorders ? $basket->{is_standing} : 1,
441 has_budgets => $has_budgets,
442 duplinbatch => $duplinbatch,
443 csv_profiles => Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }),
444 available_additional_fields => Koha::AdditionalFields->search( { tablename => 'aqbasket' } ),
445 additional_field_values => { map {
446 $_->field->name => $_->value
447 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
451 $template->param( messages => \@messages );
452 output_html_with_http_headers $query, $cookie, $template->output;
454 sub get_order_infos {
456 my $bookseller = shift;
457 my $qty = $order->{'quantity'} || 0;
458 if ( !defined $order->{quantityreceived} ) {
459 $order->{quantityreceived} = 0;
461 my $budget = GetBudget($order->{budget_id});
462 my $basket = GetBasket($order->{basketno});
464 my %line = %{ $order };
465 # Don't show unreceived standing orders as received
466 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
467 $line{basketno} = $basketno;
468 $line{budget_name} = $budget->{budget_name};
469 $line{sort1_authcat} = $budget->{sort1_authcat};
470 $line{sort2_authcat} = $budget->{sort2_authcat};
472 # If we have an actual cost that should be the total, otherwise use the ecost
473 $line{unitprice_tax_included} += 0;
474 $line{unitprice_tax_excluded} += 0;
475 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
476 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
477 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
478 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
479 $line{tax_value} = $line{tax_value_on_ordering};
480 $line{tax_rate} = $line{tax_rate_on_ordering};
482 if ( $line{'title'} ) {
483 my $volume = $order->{'volume'};
484 my $seriestitle = $order->{'seriestitle'};
485 $line{'title'} .= " / $seriestitle" if $seriestitle;
486 $line{'title'} .= " / $volume" if $volume;
489 my $biblionumber = $order->{'biblionumber'};
490 if ( $biblionumber ) { # The biblio still exists
491 my $biblio = Koha::Biblios->find( $biblionumber );
492 my $countbiblio = $biblio->active_orders->count;
494 my $ordernumber = $order->{'ordernumber'};
495 my $cnt_subscriptions = $biblio->subscriptions->count;
496 my $itemcount = $biblio->items->count;
497 my $holds_count = $biblio->holds->count;
498 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
499 my $items = $order->items;
500 my $invoice = $order->invoice;
502 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
504 # 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
505 $line{can_del_bib} = 1
506 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
507 $line{items} = $itemcount - $items->count;
508 $line{left_item} = 1 if $line{items} >= 1;
509 $line{left_biblio} = 1 if $countbiblio > 1;
510 $line{biblios} = $countbiblio - 1;
511 $line{left_subscription} = 1 if $cnt_subscriptions;
512 $line{subscriptions} = $cnt_subscriptions;
513 ( $holds_count >= 1 ) ? $line{left_holds} = 1 : $line{left_holds} = 0;
514 $line{left_holds_on_order} = 1 if $line{left_holds} == 1 && ( $line{items} == 0 || $itemholds );
515 $line{holds} = $holds_count;
516 $line{holds_on_order} = $itemholds ? $itemholds : $holds_count if $line{left_holds_on_order};
517 $line{order_object} = $order;
518 $line{invoice_object} = $invoice;
520 $line{deleted_biblio} = Koha::Old::Biblios->find( $order->{deleted_biblionumber} );
523 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
524 $line{suggestionid} = $$suggestion{suggestionid};
525 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
526 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
528 $line{estimated_delivery_date} = $order->{estimated_delivery_date};
530 foreach my $key (qw(transferred_from transferred_to)) {
532 my $order = GetOrder($line{$key});
533 my $basket = GetBasket($order->{basketno});
534 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
538 bookseller => $bookseller,
539 timestamp => $line{$key . '_timestamp'},
547 sub edi_close_and_order {
548 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
551 basketno => $basketno,
554 if ( $basket->{branch} ) {
555 $edi_params->{branchcode} = $basket->{branch};
557 if ( create_edi_order($edi_params) ) {
558 #$template->param( edifile => 1 );
560 Koha::Acquisition::Baskets->find($basketno)->close;
562 # if requested, create basket group, close it and attach the basket
563 if ( $query->param('createbasketgroup') ) {
565 if ( C4::Context->userenv
566 and C4::Context->userenv->{'branch'} )
568 $branchcode = C4::Context->userenv->{'branch'};
570 my $basketgroupid = NewBasketgroup(
572 name => $basket->{basketname},
573 booksellerid => $booksellerid,
574 deliveryplace => $branchcode,
575 billingplace => $branchcode,
581 basketno => $basketno,
582 basketgroupid => $basketgroupid
585 print $query->redirect(
586 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
590 print $query->redirect(
591 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
599 booksellerid => $booksellerid,
600 basketno => $basket->{basketno},
601 basketname => $basket->{basketname},
602 basketgroupname => $basket->{basketname},
605 $template->param( ean => $ean );