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>.
35 use Koha::Acquisition::Baskets;
36 use Koha::Acquisition::Booksellers;
37 use Koha::Acquisition::Orders;
39 use C4::Letters qw/SendAlerts/;
40 use Date::Calc qw/Add_Delta_Days/;
42 use Koha::EDI qw( create_edi_order get_edifact_ean );
43 use Koha::CsvProfiles;
46 use Koha::AdditionalFields;
54 This script display all informations about basket for the supplier given
55 on input arg. Moreover, it allows us to add a new order for this supplier from
56 an existing record, a suggestion or a new record.
68 the supplier this script have to display the basket.
76 our $query = CGI->new;
77 our $basketno = $query->param('basketno');
78 our $ean = $query->param('ean');
79 our $booksellerid = $query->param('booksellerid');
80 my $duplinbatch = $query->param('duplinbatch');
82 our ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
84 template_name => "acqui/basket.tt",
87 flagsrequired => { acquisition => 'order_manage' },
91 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
93 our $basket = GetBasket($basketno);
94 $booksellerid = $basket->{booksellerid} unless $booksellerid;
95 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
96 my $schema = Koha::Database->new()->schema();
97 my $rs = $schema->resultset('VendorEdiAccount')->search(
98 { vendor_id => $booksellerid, } );
99 $template->param( ediaccount => ($rs->count > 0));
101 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
103 cannot_manage_basket => 1,
104 basketno => $basketno,
105 basketname => $basket->{basketname},
106 booksellerid => $booksellerid,
107 booksellername => $bookseller->name,
109 output_html_with_http_headers $query, $cookie, $template->output;
113 # FIXME : what about the "discount" percentage?
114 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
115 # if no booksellerid in parameter, get it from basket
116 # warn "=>".$basket->{booksellerid};
117 my $op = $query->param('op') // 'list';
119 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
120 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
124 if ( $op eq 'delete_confirm' ) {
126 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
127 unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
129 my $basketno = $query->param('basketno');
130 my $delbiblio = $query->param('delbiblio');
131 my $basket_obj = Koha::Acquisition::Baskets->find($basketno);
133 my $orders = $basket_obj->orders;
135 my @cannotdelbiblios;
137 while ( my $order = $orders->next ) {
139 $order->cancel({ delete_biblio => $delbiblio });
140 my @messages = @{ $order->messages };
142 if ( scalar @messages > 0 ) {
144 my $biblio = $order->biblio;
146 push @cannotdelbiblios, {
147 biblionumber => $biblio->id,
148 title => $biblio->title // '',
149 author => $biblio->author // '',
150 countbiblio => $biblio->active_orders->count,
151 itemcount => $biblio->items->count,
152 subscriptions => $biblio->subscriptions->count,
157 $template->param( cannotdelbiblios => \@cannotdelbiblios );
162 delete_confirmed => 1,
163 booksellername => $bookseller->name,
164 booksellerid => $booksellerid,
166 } elsif ( !$bookseller ) {
167 $template->param( NO_BOOKSELLER => 1 );
168 } elsif ($op eq 'export') {
169 print $query->header(
171 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
173 my $csv_profile_id = $query->param('csv_profile');
174 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
176 } elsif ($op eq 'email') {
178 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
181 push @messages, { type => 'error', code => $@ };
182 } elsif ( ref $err and exists $err->{error} ) {
183 push @messages, { type => 'error', code => $err->{error} };
185 push @messages, { type => 'message', code => 'email_sent' };
189 } elsif ($op eq 'close') {
190 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
194 # FIXME: we should fetch the object at the beginning of this script
195 # and get rid of the hash that is passed around
196 Koha::Acquisition::Baskets->find($basketno)->close;
198 # if requested, create basket group, close it and attach the basket
199 if ($query->param('createbasketgroup')) {
201 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
202 $branchcode = C4::Context->userenv->{'branch'};
204 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
205 booksellerid => $booksellerid,
206 deliveryplace => $branchcode,
207 billingplace => $branchcode,
210 ModBasket( { basketno => $basketno,
211 basketgroupid => $basketgroupid } );
212 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
214 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
219 confirm_close => "1",
220 booksellerid => $booksellerid,
221 booksellername => $bookseller->name,
222 basketno => $basket->{'basketno'},
223 basketname => $basket->{'basketname'},
224 basketgroupname => $basket->{'basketname'},
227 } elsif ($op eq 'reopen') {
228 ReopenBasket(scalar $query->param('basketno'));
229 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
231 elsif ( $op eq 'ediorder' ) {
232 edi_close_and_order()
233 } elsif ( $op eq 'mod_users' ) {
234 my $basketusers_ids = $query->param('users_ids');
235 my @basketusers = split( /:/, $basketusers_ids );
236 ModBasketUsers($basketno, @basketusers);
237 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
239 } elsif ( $op eq 'mod_branch' ) {
240 my $branch = $query->param('branch');
241 $branch = undef if(defined $branch and $branch eq '');
243 basketno => $basket->{basketno},
246 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
250 if ( $op eq 'list' ) {
252 # get librarian branch...
253 if ( C4::Context->preference("IndependentBranches") ) {
254 my $userenv = C4::Context->userenv;
255 unless ( C4::Context->IsSuperLibrarian() ) {
256 my $validtest = ( $basket->{creationdate} eq '' )
257 || ( $userenv->{branch} eq $basket->{branch} )
258 || ( $userenv->{branch} eq '' )
259 || ( $basket->{branch} eq '' );
260 unless ($validtest) {
261 print $query->redirect("../mainpage.pl");
266 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
267 push @branches_loop, {
268 branchcode => $userenv->{branch},
269 branchname => $userenv->{branchname},
275 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
276 foreach my $branch (@$branches) {
278 if (defined $basket->{branch}) {
279 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
281 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
283 push @branches_loop, {
284 branchcode => $branch->{branchcode},
285 branchname => $branch->{branchname},
286 selected => $selected
291 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
292 my ($basketgroup, $basketgroups);
293 my $patron = Koha::Patrons->find($loggedinuser);
294 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
295 $basketgroups = GetBasketgroups($basket->{booksellerid});
296 for my $bg ( @{$basketgroups} ) {
297 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
304 # if the basket is closed, calculate estimated delivery date
305 my $estimateddeliverydate;
306 if( $basket->{closedate} ) {
307 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
308 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
309 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
312 # if new basket, pre-fill infos
313 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
314 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
316 my @basketusers_ids = GetBasketUsers($basketno);
318 foreach my $basketuser_id (@basketusers_ids) {
319 # FIXME Could be improved with a search -in
320 my $basket_patron = Koha::Patrons->find( $basketuser_id );
321 push @basketusers, $basket_patron if $basket_patron;
324 my $active_currency = Koha::Acquisition::Currencies->get_active;
326 my @orders = GetOrders( $basketno );
331 my $total_quantity = 0;
332 my $total_tax_excluded = 0;
333 my $total_tax_included = 0;
334 my $total_tax_value = 0;
335 for my $order (@orders) {
336 my $line = get_order_infos( $order, $bookseller);
337 if ( $line->{uncertainprice} ) {
338 $template->param( uncertainprices => 1 );
341 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
342 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
344 push @books_loop, $line;
346 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
347 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
348 $total_tax_value += $$line{tax_value};
349 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
350 $total_quantity += $$line{quantity};
351 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
352 $total_tax_excluded += $$line{total_tax_excluded};
353 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
354 $total_tax_included += $$line{total_tax_included};
357 push @book_foot_loop, map {$_} values %foot;
359 # Get cancelled orders
360 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
361 my @cancelledorders_loop;
362 for my $order (@cancelledorders) {
363 my $line = get_order_infos( $order, $bookseller);
364 push @cancelledorders_loop, $line;
367 my $contract = GetContract({
368 contractnumber => $basket->{contractnumber}
371 if ($basket->{basketgroupid}){
372 $basketgroup = GetBasketgroup($basket->{basketgroupid});
374 my $budgets = GetBudgetHierarchy;
376 foreach my $r (@{$budgets}) {
377 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
384 basketno => $basketno,
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 authorisedby => $basket->{authorisedby},
395 authorisedbyname => $basket->{authorisedbyname},
396 users_ids => join(':', @basketusers_ids),
397 users => \@basketusers,
398 closedate => $basket->{closedate},
399 estimateddeliverydate=> $estimateddeliverydate,
400 is_standing => $basket->{is_standing},
401 deliveryplace => $basket->{deliveryplace},
402 billingplace => $basket->{billingplace},
403 active => $bookseller->active,
404 booksellerid => $bookseller->id,
405 booksellername => $bookseller->name,
406 books_loop => \@books_loop,
407 book_foot_loop => \@book_foot_loop,
408 cancelledorders_loop => \@cancelledorders_loop,
409 total_quantity => $total_quantity,
410 total_tax_excluded => $total_tax_excluded,
411 total_tax_included => $total_tax_included,
412 total_tax_value => $total_tax_value,
413 currency => $active_currency->currency,
414 listincgst => $bookseller->listincgst,
415 basketgroups => $basketgroups,
416 basketgroup => $basketgroup,
417 grouped => $basket->{basketgroupid},
418 # The double negatives and booleans here mean:
419 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
421 # (The template has another implicit restriction that the order cannot be closed if there
422 # are any orders with uncertain prices.)
423 unclosable => @orders || @cancelledorders ? $basket->{is_standing} : 1,
424 has_budgets => $has_budgets,
425 duplinbatch => $duplinbatch,
426 csv_profiles => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
427 available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
428 additional_field_values => { map {
429 $_->field->name => $_->value
430 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
434 $template->param( messages => \@messages );
435 output_html_with_http_headers $query, $cookie, $template->output;
437 sub get_order_infos {
439 my $bookseller = shift;
440 my $qty = $order->{'quantity'} || 0;
441 if ( !defined $order->{quantityreceived} ) {
442 $order->{quantityreceived} = 0;
444 my $budget = GetBudget($order->{budget_id});
445 my $basket = GetBasket($order->{basketno});
447 my %line = %{ $order };
448 # Don't show unreceived standing orders as received
449 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
450 $line{basketno} = $basketno;
451 $line{budget_name} = $budget->{budget_name};
453 # If we have an actual cost that should be the total, otherwise use the ecost
454 $line{unitprice_tax_included} += 0;
455 $line{unitprice_tax_excluded} += 0;
456 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
457 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
458 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
459 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
460 $line{tax_value} = $line{tax_value_on_ordering};
461 $line{tax_rate} = $line{tax_rate_on_ordering};
463 if ( $line{'title'} ) {
464 my $volume = $order->{'volume'};
465 my $seriestitle = $order->{'seriestitle'};
466 $line{'title'} .= " / $seriestitle" if $seriestitle;
467 $line{'title'} .= " / $volume" if $volume;
470 my $biblionumber = $order->{'biblionumber'};
471 if ( $biblionumber ) { # The biblio still exists
472 my $biblio = Koha::Biblios->find( $biblionumber );
473 my $countbiblio = $biblio->active_orders->count;
475 my $ordernumber = $order->{'ordernumber'};
476 my $cnt_subscriptions = $biblio->subscriptions->count;
477 my $itemcount = $biblio->items->count;
478 my $holds_count = $biblio->holds->count;
479 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
480 my $items = $order->items;
481 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
483 # 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
484 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
485 $line{items} = $itemcount - $items->count;
486 $line{left_item} = 1 if $line{items} >= 1;
487 $line{left_biblio} = 1 if $countbiblio > 1;
488 $line{biblios} = $countbiblio - 1;
489 $line{left_subscription} = 1 if $cnt_subscriptions;
490 $line{subscriptions} = $cnt_subscriptions;
491 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
492 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
493 $line{holds} = $holds_count;
494 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
495 $line{order_object} = $order;
499 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
500 $line{suggestionid} = $$suggestion{suggestionid};
501 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
502 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
504 foreach my $key (qw(transferred_from transferred_to)) {
506 my $order = GetOrder($line{$key});
507 my $basket = GetBasket($order->{basketno});
508 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
512 bookseller => $bookseller,
513 timestamp => $line{$key . '_timestamp'},
521 sub edi_close_and_order {
522 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
525 basketno => $basketno,
528 if ( $basket->{branch} ) {
529 $edi_params->{branchcode} = $basket->{branch};
531 if ( create_edi_order($edi_params) ) {
532 #$template->param( edifile => 1 );
534 Koha::Acquisition::Baskets->find($basketno)->close;
536 # if requested, create basket group, close it and attach the basket
537 if ( $query->param('createbasketgroup') ) {
539 if ( C4::Context->userenv
540 and C4::Context->userenv->{'branch'} )
542 $branchcode = C4::Context->userenv->{'branch'};
544 my $basketgroupid = NewBasketgroup(
546 name => $basket->{basketname},
547 booksellerid => $booksellerid,
548 deliveryplace => $branchcode,
549 billingplace => $branchcode,
555 basketno => $basketno,
556 basketgroupid => $basketgroupid
559 print $query->redirect(
560 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
564 print $query->redirect(
565 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
573 booksellerid => $booksellerid,
574 basketno => $basket->{basketno},
575 basketname => $basket->{basketname},
576 basketgroupname => $basket->{basketname},
579 $template->param( ean => $ean );