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>.
36 use Koha::Acquisition::Baskets;
37 use Koha::Acquisition::Booksellers;
38 use Koha::Acquisition::Orders;
40 use C4::Letters qw/SendAlerts/;
41 use Date::Calc qw/Add_Delta_Days/;
43 use Koha::EDI qw( create_edi_order get_edifact_ean );
44 use Koha::CsvProfiles;
47 use Koha::AdditionalFields;
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.
69 the supplier this script have to display the basket.
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",
88 flagsrequired => { acquisition => 'order_manage' },
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)) {
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;
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';
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 ) {
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 );
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(
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
178 } elsif ($op eq 'email') {
180 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
183 push @messages, { type => 'error', code => $@ };
184 } elsif ( ref $err and exists $err->{error} ) {
185 push @messages, { type => 'error', code => $err->{error} };
187 push @messages, { type => 'message', code => 'email_sent' };
191 } elsif ($op eq 'close') {
192 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
196 # FIXME: we should fetch the object at the beginning of this script
197 # and get rid of the hash that is passed around
198 Koha::Acquisition::Baskets->find($basketno)->close;
200 # if requested, create basket group, close it and attach the basket
201 if ($query->param('createbasketgroup')) {
203 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
204 $branchcode = C4::Context->userenv->{'branch'};
206 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
207 booksellerid => $booksellerid,
208 deliveryplace => $branchcode,
209 billingplace => $branchcode,
212 ModBasket( { basketno => $basketno,
213 basketgroupid => $basketgroupid,
214 borrowernumber => $loggedinuser } );
215 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
217 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
222 confirm_close => "1",
223 booksellerid => $booksellerid,
224 booksellername => $bookseller->name,
225 basketno => $basket->{'basketno'},
226 basketname => $basket->{'basketname'},
227 basketgroupname => $basket->{'basketname'},
230 } elsif ($op eq 'reopen') {
231 ReopenBasket(scalar $query->param('basketno'));
232 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
234 elsif ( $op eq 'ediorder' ) {
235 edi_close_and_order()
236 } elsif ( $op eq 'mod_users' ) {
237 my $basketusers_ids = $query->param('users_ids');
238 my @basketusers = split( /:/, $basketusers_ids );
239 ModBasketUsers($basketno, @basketusers);
240 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
242 } elsif ( $op eq 'mod_branch' ) {
243 my $branch = $query->param('branch');
244 $branch = undef if(defined $branch and $branch eq '');
246 basketno => $basket->{basketno},
248 borrowernumber => $loggedinuser
250 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
254 if ( $op eq 'list' ) {
256 # get librarian branch...
257 if ( C4::Context->preference("IndependentBranches") ) {
258 my $userenv = C4::Context->userenv;
259 unless ( C4::Context->IsSuperLibrarian() ) {
260 my $validtest = ( $basket->{creationdate} eq '' )
261 || ( $userenv->{branch} eq $basket->{branch} )
262 || ( $userenv->{branch} eq '' )
263 || ( $basket->{branch} eq '' );
264 unless ($validtest) {
265 print $query->redirect("../mainpage.pl");
270 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
271 push @branches_loop, {
272 branchcode => $userenv->{branch},
273 branchname => $userenv->{branchname},
279 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
280 foreach my $branch (@$branches) {
282 if (defined $basket->{branch}) {
283 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
285 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
287 push @branches_loop, {
288 branchcode => $branch->{branchcode},
289 branchname => $branch->{branchname},
290 selected => $selected
295 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
296 my ($basketgroup, $basketgroups);
297 my $patron = Koha::Patrons->find($loggedinuser);
298 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
299 $basketgroups = GetBasketgroups($basket->{booksellerid});
300 for my $bg ( @{$basketgroups} ) {
301 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
308 # if the basket is closed, calculate estimated delivery date
309 my $estimateddeliverydate;
310 if( $basket->{closedate} ) {
311 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
312 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
313 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
316 # if new basket, pre-fill infos
317 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
318 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
321 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
322 $basket->{creationdate}, $basket->{authorisedby};
324 my @basketusers_ids = GetBasketUsers($basketno);
326 foreach my $basketuser_id (@basketusers_ids) {
327 # FIXME Could be improved with a search -in
328 my $basket_patron = Koha::Patrons->find( $basketuser_id );
329 push @basketusers, $basket_patron if $basket_patron;
332 my $active_currency = Koha::Acquisition::Currencies->get_active;
334 my @orders = GetOrders( $basketno );
339 my $total_quantity = 0;
340 my $total_tax_excluded = 0;
341 my $total_tax_included = 0;
342 my $total_tax_value = 0;
343 for my $order (@orders) {
344 my $line = get_order_infos( $order, $bookseller);
345 if ( $line->{uncertainprice} ) {
346 $template->param( uncertainprices => 1 );
349 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
350 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
352 push @books_loop, $line;
354 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
355 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
356 $total_tax_value += $$line{tax_value};
357 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
358 $total_quantity += $$line{quantity};
359 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
360 $total_tax_excluded += $$line{total_tax_excluded};
361 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
362 $total_tax_included += $$line{total_tax_included};
365 push @book_foot_loop, map {$_} values %foot;
367 # Get cancelled orders
368 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
369 my @cancelledorders_loop;
370 for my $order (@cancelledorders) {
371 my $line = get_order_infos( $order, $bookseller);
372 push @cancelledorders_loop, $line;
375 my $contract = GetContract({
376 contractnumber => $basket->{contractnumber}
379 if ($basket->{basketgroupid}){
380 $basketgroup = GetBasketgroup($basket->{basketgroupid});
382 my $budgets = GetBudgetHierarchy;
384 foreach my $r (@{$budgets}) {
385 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
392 basketno => $basketno,
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."
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 || @cancelledorders ? $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::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
436 additional_field_values => { map {
437 $_->field->name => $_->value
438 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
442 $template->param( messages => \@messages );
443 output_html_with_http_headers $query, $cookie, $template->output;
445 sub get_order_infos {
447 my $bookseller = shift;
448 my $qty = $order->{'quantity'} || 0;
449 if ( !defined $order->{quantityreceived} ) {
450 $order->{quantityreceived} = 0;
452 my $budget = GetBudget($order->{budget_id});
453 my $basket = GetBasket($order->{basketno});
455 my %line = %{ $order };
456 # Don't show unreceived standing orders as received
457 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
458 $line{basketno} = $basketno;
459 $line{budget_name} = $budget->{budget_name};
461 # If we have an actual cost that should be the total, otherwise use the ecost
462 $line{unitprice_tax_included} += 0;
463 $line{unitprice_tax_excluded} += 0;
464 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
465 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
466 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
467 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
468 $line{tax_value} = $line{tax_value_on_ordering};
469 $line{tax_rate} = $line{tax_rate_on_ordering};
471 if ( $line{'title'} ) {
472 my $volume = $order->{'volume'};
473 my $seriestitle = $order->{'seriestitle'};
474 $line{'title'} .= " / $seriestitle" if $seriestitle;
475 $line{'title'} .= " / $volume" if $volume;
478 my $biblionumber = $order->{'biblionumber'};
479 if ( $biblionumber ) { # The biblio still exists
480 my $biblio = Koha::Biblios->find( $biblionumber );
481 my $countbiblio = $biblio->active_orders->count;
483 my $ordernumber = $order->{'ordernumber'};
484 my $cnt_subscriptions = $biblio->subscriptions->count;
485 my $itemcount = $biblio->items->count;
486 my $holds_count = $biblio->holds->count;
487 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
488 my $items = $order->items;
489 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
491 # 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
492 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
493 $line{items} = $itemcount - $items->count;
494 $line{left_item} = 1 if $line{items} >= 1;
495 $line{left_biblio} = 1 if $countbiblio > 1;
496 $line{biblios} = $countbiblio - 1;
497 $line{left_subscription} = 1 if $cnt_subscriptions;
498 $line{subscriptions} = $cnt_subscriptions;
499 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
500 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
501 $line{holds} = $holds_count;
502 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
503 $line{order_object} = $order;
507 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
508 $line{suggestionid} = $$suggestion{suggestionid};
509 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
510 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
512 foreach my $key (qw(transferred_from transferred_to)) {
514 my $order = GetOrder($line{$key});
515 my $basket = GetBasket($order->{basketno});
516 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
520 bookseller => $bookseller,
521 timestamp => $line{$key . '_timestamp'},
529 sub edi_close_and_order {
530 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
533 basketno => $basketno,
536 if ( $basket->{branch} ) {
537 $edi_params->{branchcode} = $basket->{branch};
539 if ( create_edi_order($edi_params) ) {
540 #$template->param( edifile => 1 );
542 Koha::Acquisition::Baskets->find($basketno)->close;
544 # if requested, create basket group, close it and attach the basket
545 if ( $query->param('createbasketgroup') ) {
547 if ( C4::Context->userenv
548 and C4::Context->userenv->{'branch'} )
550 $branchcode = C4::Context->userenv->{'branch'};
552 my $basketgroupid = NewBasketgroup(
554 name => $basket->{basketname},
555 booksellerid => $booksellerid,
556 deliveryplace => $branchcode,
557 billingplace => $branchcode,
563 basketno => $basketno,
564 basketgroupid => $basketgroupid,
565 borrowernumber => $loggedinuser
568 print $query->redirect(
569 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
573 print $query->redirect(
574 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
582 booksellerid => $booksellerid,
583 basketno => $basket->{basketno},
584 basketname => $basket->{basketname},
585 basketgroupname => $basket->{basketname},
588 $template->param( ean => $ean );