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::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::AdditionalField;
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.
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",
88 flagsrequired => { acquisition => 'order_manage' },
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' ) {
125 my $basketno = $query->param('basketno');
126 my $delbiblio = $query->param('delbiblio');
127 my @orders = GetOrders($basketno);
128 #Delete all orders included in that basket, and all items received.
129 foreach my $myorder (@orders){
130 DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
132 # if $delbiblio = 1, delete the records if possible
133 if ((defined $delbiblio)and ($delbiblio ==1)){
134 my @cannotdelbiblios ;
135 foreach my $myorder (@orders){
136 my $biblionumber = $myorder->{'biblionumber'};
137 my $biblio = Koha::Biblios->find( $biblionumber );
138 my $countbiblio = CountBiblioInOrders($biblionumber);
139 my $ordernumber = $myorder->{'ordernumber'};
140 my $cnt_subscriptions = $biblio->subscriptions->count;
141 my $itemcount = $biblio->items->count;
143 if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
144 $error = DelBiblio($myorder->{biblionumber}) }
146 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
147 title=> $myorder->{'title'},
148 author=> $myorder->{'author'},
149 countbiblio=> $countbiblio,
150 itemcount=>$itemcount,
151 subscriptions => $cnt_subscriptions};
154 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
155 title=> $myorder->{'title'},
156 author=> $myorder->{'author'},
157 othererror=> $error};
160 $template->param( cannotdelbiblios => \@cannotdelbiblios );
163 DelBasket($basketno,);
165 delete_confirmed => 1,
166 booksellername => $bookseller->name,
167 booksellerid => $booksellerid,
169 } elsif ( !$bookseller ) {
170 $template->param( NO_BOOKSELLER => 1 );
171 } elsif ($op eq 'export') {
172 print $query->header(
174 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
176 my $csv_profile_id = $query->param('csv_profile');
177 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
179 } elsif ($op eq 'email') {
181 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
184 push @messages, { type => 'error', code => $@ };
185 } elsif ( ref $err and exists $err->{error} ) {
186 push @messages, { type => 'error', code => $err->{error} };
188 push @messages, { type => 'message', code => 'email_sent' };
192 } elsif ($op eq 'close') {
193 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
195 my $basketno = $query->param('basketno');
196 my $booksellerid = $query->param('booksellerid');
197 $basketno =~ /^\d+$/ and CloseBasket($basketno);
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 and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET") {
203 $branchcode = C4::Context->userenv->{'branch'};
205 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
206 booksellerid => $booksellerid,
207 deliveryplace => $branchcode,
208 billingplace => $branchcode,
211 ModBasket( { basketno => $basketno,
212 basketgroupid => $basketgroupid } );
213 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
215 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
220 confirm_close => "1",
221 booksellerid => $booksellerid,
222 booksellername => $bookseller->name,
223 basketno => $basket->{'basketno'},
224 basketname => $basket->{'basketname'},
225 basketgroupname => $basket->{'basketname'},
228 } elsif ($op eq 'reopen') {
229 ReopenBasket(scalar $query->param('basketno'));
230 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
232 elsif ( $op eq 'ediorder' ) {
233 edi_close_and_order()
234 } elsif ( $op eq 'mod_users' ) {
235 my $basketusers_ids = $query->param('users_ids');
236 my @basketusers = split( /:/, $basketusers_ids );
237 ModBasketUsers($basketno, @basketusers);
238 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
240 } elsif ( $op eq 'mod_branch' ) {
241 my $branch = $query->param('branch');
242 $branch = undef if(defined $branch and $branch eq '');
244 basketno => $basket->{basketno},
247 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
251 if ( $op eq 'list' ) {
253 # get librarian branch...
254 if ( C4::Context->preference("IndependentBranches") ) {
255 my $userenv = C4::Context->userenv;
256 unless ( C4::Context->IsSuperLibrarian() ) {
257 my $validtest = ( $basket->{creationdate} eq '' )
258 || ( $userenv->{branch} eq $basket->{branch} )
259 || ( $userenv->{branch} eq '' )
260 || ( $basket->{branch} eq '' );
261 unless ($validtest) {
262 print $query->redirect("../mainpage.pl");
267 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
268 push @branches_loop, {
269 branchcode => $userenv->{branch},
270 branchname => $userenv->{branchname},
276 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
277 foreach my $branch (@$branches) {
279 if (defined $basket->{branch}) {
280 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
282 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
284 push @branches_loop, {
285 branchcode => $branch->{branchcode},
286 branchname => $branch->{branchname},
287 selected => $selected
292 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
293 my ($basketgroup, $basketgroups);
294 my $patron = Koha::Patrons->find($loggedinuser);
295 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
296 $basketgroups = GetBasketgroups($basket->{booksellerid});
297 for my $bg ( @{$basketgroups} ) {
298 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
305 # if the basket is closed, calculate estimated delivery date
306 my $estimateddeliverydate;
307 if( $basket->{closedate} ) {
308 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
309 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
310 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
313 # if new basket, pre-fill infos
314 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
315 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
318 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
319 $basket->{creationdate}, $basket->{authorisedby};
321 my @basketusers_ids = GetBasketUsers($basketno);
323 foreach my $basketuser_id (@basketusers_ids) {
324 # FIXME Could be improved with a search -in
325 my $basket_patron = Koha::Patrons->find( $basketuser_id );
326 push @basketusers, $basket_patron if $basket_patron;
329 my $active_currency = Koha::Acquisition::Currencies->get_active;
331 my @orders = GetOrders( $basketno );
336 my $total_quantity = 0;
337 my $total_tax_excluded = 0;
338 my $total_tax_included = 0;
339 my $total_tax_value = 0;
340 for my $order (@orders) {
341 my $line = get_order_infos( $order, $bookseller);
342 if ( $line->{uncertainprice} ) {
343 $template->param( uncertainprices => 1 );
346 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
347 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
349 push @books_loop, $line;
351 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
352 $foot{$$line{tax_rate}}{tax_value} += $$line{tax_value};
353 $total_tax_value += $$line{tax_value};
354 $foot{$$line{tax_rate}}{quantity} += $$line{quantity};
355 $total_quantity += $$line{quantity};
356 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
357 $total_tax_excluded += $$line{total_tax_excluded};
358 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
359 $total_tax_included += $$line{total_tax_included};
362 push @book_foot_loop, map {$_} values %foot;
364 # Get cancelled orders
365 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
366 my @cancelledorders_loop;
367 for my $order (@cancelledorders) {
368 my $line = get_order_infos( $order, $bookseller);
369 push @cancelledorders_loop, $line;
372 my $contract = GetContract({
373 contractnumber => $basket->{contractnumber}
376 if ($basket->{basketgroupid}){
377 $basketgroup = GetBasketgroup($basket->{basketgroupid});
379 my $budgets = GetBudgetHierarchy;
381 foreach my $r (@{$budgets}) {
382 if (!defined $r->{budget_amount} || $r->{budget_amount} == 0) {
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 ? $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::AdditionalField->all( { tablename => 'aqbasket' } ),
436 additional_field_values => Koha::AdditionalField->fetch_all_values( {
437 tablename => 'aqbasket',
438 record_id => $basketno,
443 $template->param( messages => \@messages );
444 output_html_with_http_headers $query, $cookie, $template->output;
446 sub get_order_infos {
448 my $bookseller = shift;
449 my $qty = $order->{'quantity'} || 0;
450 if ( !defined $order->{quantityreceived} ) {
451 $order->{quantityreceived} = 0;
453 my $budget = GetBudget($order->{budget_id});
454 my $basket = GetBasket($order->{basketno});
456 my %line = %{ $order };
457 # Don't show unreceived standing orders as received
458 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
459 $line{basketno} = $basketno;
460 $line{budget_name} = $budget->{budget_name};
462 $line{total_tax_included} = $line{ecost_tax_included} * $line{quantity};
463 $line{total_tax_excluded} = $line{ecost_tax_excluded} * $line{quantity};
464 $line{tax_value} = $line{tax_value_on_ordering};
465 $line{tax_rate} = $line{tax_rate_on_ordering};
467 if ( $line{uncertainprice} ) {
468 $line{rrp_tax_excluded} .= ' (Uncertain)';
470 if ( $line{'title'} ) {
471 my $volume = $order->{'volume'};
472 my $seriestitle = $order->{'seriestitle'};
473 $line{'title'} .= " / $seriestitle" if $seriestitle;
474 $line{'title'} .= " / $volume" if $volume;
477 my $biblionumber = $order->{'biblionumber'};
478 if ( $biblionumber ) { # The biblio still exists
479 my $biblio = Koha::Biblios->find( $biblionumber );
480 my $countbiblio = CountBiblioInOrders($biblionumber);
481 my $ordernumber = $order->{'ordernumber'};
482 my $cnt_subscriptions = $biblio->subscriptions->count;
483 my $itemcount = $biblio->items->count;
484 my $holds_count = $biblio->holds->count;
485 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
486 my $items = $order->items;
487 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
489 # 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
490 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
491 $line{items} = $itemcount - $items->count;
492 $line{left_item} = 1 if $line{items} >= 1;
493 $line{left_biblio} = 1 if $countbiblio > 1;
494 $line{biblios} = $countbiblio - 1;
495 $line{left_subscription} = 1 if $cnt_subscriptions;
496 $line{subscriptions} = $cnt_subscriptions;
497 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
498 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
499 $line{holds} = $holds_count;
500 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
504 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
505 $line{suggestionid} = $$suggestion{suggestionid};
506 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
507 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
509 foreach my $key (qw(transferred_from transferred_to)) {
511 my $order = GetOrder($line{$key});
512 my $basket = GetBasket($order->{basketno});
513 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
517 bookseller => $bookseller,
518 timestamp => $line{$key . '_timestamp'},
526 sub edi_close_and_order {
527 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
530 basketno => $basketno,
533 if ( $basket->{branch} ) {
534 $edi_params->{branchcode} = $basket->{branch};
536 if ( create_edi_order($edi_params) ) {
537 #$template->param( edifile => 1 );
539 CloseBasket($basketno);
541 # if requested, create basket group, close it and attach the basket
542 if ( $query->param('createbasketgroup') ) {
544 if ( C4::Context->userenv
545 and C4::Context->userenv->{'branch'}
546 and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET" )
548 $branchcode = C4::Context->userenv->{'branch'};
550 my $basketgroupid = NewBasketgroup(
552 name => $basket->{basketname},
553 booksellerid => $booksellerid,
554 deliveryplace => $branchcode,
555 billingplace => $branchcode,
561 basketno => $basketno,
562 basketgroupid => $basketgroupid
565 print $query->redirect(
566 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
570 print $query->redirect(
571 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
579 booksellerid => $booksellerid,
580 basketno => $basket->{basketno},
581 basketname => $basket->{basketname},
582 basketgroupname => $basket->{basketname},
585 $template->param( ean => $ean );