Bug 24591: Add developer script to preview a letter
[koha.git] / acqui / basket.pl
blob88abf2485edfe3949120e246b1136919117d6284
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 = new CGI;
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 authnotrequired => 0,
89 flagsrequired => { acquisition => 'order_manage' },
90 debug => 1,
94 my $logged_in_patron = Koha::Patrons->find( $loggedinuser );
96 our $basket = GetBasket($basketno);
97 $booksellerid = $basket->{booksellerid} unless $booksellerid;
98 my $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
99 my $schema = Koha::Database->new()->schema();
100 my $rs = $schema->resultset('VendorEdiAccount')->search(
101 { vendor_id => $booksellerid, } );
102 $template->param( ediaccount => ($rs->count > 0));
104 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
105 $template->param(
106 cannot_manage_basket => 1,
107 basketno => $basketno,
108 basketname => $basket->{basketname},
109 booksellerid => $booksellerid,
110 booksellername => $bookseller->name,
112 output_html_with_http_headers $query, $cookie, $template->output;
113 exit;
116 # FIXME : what about the "discount" percentage?
117 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
118 # if no booksellerid in parameter, get it from basket
119 # warn "=>".$basket->{booksellerid};
120 my $op = $query->param('op') // 'list';
122 our $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
123 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
125 my @messages;
127 if ( $op eq 'delete_confirm' ) {
129 output_and_exit( $query, $cookie, $template, 'insufficient_permission' )
130 unless $logged_in_patron->has_permission( { acquisition => 'delete_baskets' } );
132 my $basketno = $query->param('basketno');
133 my $delbiblio = $query->param('delbiblio');
134 my @orders = GetOrders($basketno);
135 #Delete all orders included in that basket, and all items received.
136 foreach my $myorder (@orders){
137 DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
139 # if $delbiblio = 1, delete the records if possible
140 if ((defined $delbiblio)and ($delbiblio ==1)){
141 my @cannotdelbiblios ;
142 foreach my $myorder (@orders){
143 my $biblionumber = $myorder->{'biblionumber'};
144 my $biblio = Koha::Biblios->find( $biblionumber );
145 my $countbiblio = $biblio->active_orders->count;
146 my $ordernumber = $myorder->{'ordernumber'};
147 my $cnt_subscriptions = $biblio->subscriptions->count;
148 my $itemcount = $biblio->items->count;
149 my $error;
150 if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
151 $error = DelBiblio($myorder->{biblionumber}) }
152 else {
153 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
154 title=> $myorder->{'title'},
155 author=> $myorder->{'author'},
156 countbiblio=> $countbiblio,
157 itemcount=>$itemcount,
158 subscriptions => $cnt_subscriptions};
160 if ($error) {
161 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
162 title=> $myorder->{'title'},
163 author=> $myorder->{'author'},
164 othererror=> $error};
167 $template->param( cannotdelbiblios => \@cannotdelbiblios );
169 # delete the basket
170 DelBasket($basketno,);
171 $template->param(
172 delete_confirmed => 1,
173 booksellername => $bookseller->name,
174 booksellerid => $booksellerid,
176 } elsif ( !$bookseller ) {
177 $template->param( NO_BOOKSELLER => 1 );
178 } elsif ($op eq 'export') {
179 print $query->header(
180 -type => 'text/csv',
181 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
183 my $csv_profile_id = $query->param('csv_profile');
184 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
185 exit;
186 } elsif ($op eq 'email') {
187 my $err = eval {
188 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
190 if ( $@ ) {
191 push @messages, { type => 'error', code => $@ };
192 } elsif ( ref $err and exists $err->{error} ) {
193 push @messages, { type => 'error', code => $err->{error} };
194 } else {
195 push @messages, { type => 'message', code => 'email_sent' };
198 $op = 'list';
199 } elsif ($op eq 'close') {
200 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
201 if ($confirm) {
202 my $basketno = $query->param('basketno');
203 my $booksellerid = $query->param('booksellerid');
204 $basketno =~ /^\d+$/ and CloseBasket($basketno);
205 # if requested, create basket group, close it and attach the basket
206 if ($query->param('createbasketgroup')) {
207 my $branchcode;
208 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
209 $branchcode = C4::Context->userenv->{'branch'};
211 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
212 booksellerid => $booksellerid,
213 deliveryplace => $branchcode,
214 billingplace => $branchcode,
215 closed => 1,
217 ModBasket( { basketno => $basketno,
218 basketgroupid => $basketgroupid } );
219 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
220 } else {
221 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
223 exit;
224 } else {
225 $template->param(
226 confirm_close => "1",
227 booksellerid => $booksellerid,
228 booksellername => $bookseller->name,
229 basketno => $basket->{'basketno'},
230 basketname => $basket->{'basketname'},
231 basketgroupname => $basket->{'basketname'},
234 } elsif ($op eq 'reopen') {
235 ReopenBasket(scalar $query->param('basketno'));
236 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
238 elsif ( $op eq 'ediorder' ) {
239 edi_close_and_order()
240 } elsif ( $op eq 'mod_users' ) {
241 my $basketusers_ids = $query->param('users_ids');
242 my @basketusers = split( /:/, $basketusers_ids );
243 ModBasketUsers($basketno, @basketusers);
244 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
245 exit;
246 } elsif ( $op eq 'mod_branch' ) {
247 my $branch = $query->param('branch');
248 $branch = undef if(defined $branch and $branch eq '');
249 ModBasket({
250 basketno => $basket->{basketno},
251 branch => $branch
253 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
254 exit;
257 if ( $op eq 'list' ) {
258 my @branches_loop;
259 # get librarian branch...
260 if ( C4::Context->preference("IndependentBranches") ) {
261 my $userenv = C4::Context->userenv;
262 unless ( C4::Context->IsSuperLibrarian() ) {
263 my $validtest = ( $basket->{creationdate} eq '' )
264 || ( $userenv->{branch} eq $basket->{branch} )
265 || ( $userenv->{branch} eq '' )
266 || ( $basket->{branch} eq '' );
267 unless ($validtest) {
268 print $query->redirect("../mainpage.pl");
269 exit 0;
273 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
274 push @branches_loop, {
275 branchcode => $userenv->{branch},
276 branchname => $userenv->{branchname},
277 selected => 1,
280 } else {
281 # get branches
282 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
283 foreach my $branch (@$branches) {
284 my $selected = 0;
285 if (defined $basket->{branch}) {
286 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
287 } else {
288 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
290 push @branches_loop, {
291 branchcode => $branch->{branchcode},
292 branchname => $branch->{branchname},
293 selected => $selected
298 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
299 my ($basketgroup, $basketgroups);
300 my $patron = Koha::Patrons->find($loggedinuser);
301 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
302 $basketgroups = GetBasketgroups($basket->{booksellerid});
303 for my $bg ( @{$basketgroups} ) {
304 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
305 $bg->{default} = 1;
306 $basketgroup = $bg;
311 # if the basket is closed, calculate estimated delivery date
312 my $estimateddeliverydate;
313 if( $basket->{closedate} ) {
314 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
315 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
316 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
319 # if new basket, pre-fill infos
320 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
321 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
322 $debug
323 and warn sprintf
324 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
325 $basket->{creationdate}, $basket->{authorisedby};
327 my @basketusers_ids = GetBasketUsers($basketno);
328 my @basketusers;
329 foreach my $basketuser_id (@basketusers_ids) {
330 # FIXME Could be improved with a search -in
331 my $basket_patron = Koha::Patrons->find( $basketuser_id );
332 push @basketusers, $basket_patron if $basket_patron;
335 my $active_currency = Koha::Acquisition::Currencies->get_active;
337 my @orders = GetOrders( $basketno );
338 my @books_loop;
340 my @book_foot_loop;
341 my %foot;
342 my $total_quantity = 0;
343 my $total_tax_excluded = 0;
344 my $total_tax_included = 0;
345 my $total_tax_value = 0;
346 for my $order (@orders) {
347 my $line = get_order_infos( $order, $bookseller);
348 if ( $line->{uncertainprice} ) {
349 $template->param( uncertainprices => 1 );
352 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
353 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
355 push @books_loop, $line;
357 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
358 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
359 $total_tax_value += $$line{tax_value};
360 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
361 $total_quantity += $$line{quantity};
362 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
363 $total_tax_excluded += $$line{total_tax_excluded};
364 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
365 $total_tax_included += $$line{total_tax_included};
368 push @book_foot_loop, map {$_} values %foot;
370 # Get cancelled orders
371 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
372 my @cancelledorders_loop;
373 for my $order (@cancelledorders) {
374 my $line = get_order_infos( $order, $bookseller);
375 push @cancelledorders_loop, $line;
378 my $contract = GetContract({
379 contractnumber => $basket->{contractnumber}
382 if ($basket->{basketgroupid}){
383 $basketgroup = GetBasketgroup($basket->{basketgroupid});
385 my $budgets = GetBudgetHierarchy;
386 my $has_budgets = 0;
387 foreach my $r (@{$budgets}) {
388 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
390 $has_budgets = 1;
391 last;
394 $template->param(
395 basketno => $basketno,
396 basket => $basket,
397 basketname => $basket->{'basketname'},
398 basketbranchcode => $basket->{branch},
399 basketnote => $basket->{note},
400 basketbooksellernote => $basket->{booksellernote},
401 basketcontractno => $basket->{contractnumber},
402 basketcontractname => $contract->{contractname},
403 branches_loop => \@branches_loop,
404 creationdate => $basket->{creationdate},
405 authorisedby => $basket->{authorisedby},
406 authorisedbyname => $basket->{authorisedbyname},
407 users_ids => join(':', @basketusers_ids),
408 users => \@basketusers,
409 closedate => $basket->{closedate},
410 estimateddeliverydate=> $estimateddeliverydate,
411 is_standing => $basket->{is_standing},
412 deliveryplace => $basket->{deliveryplace},
413 billingplace => $basket->{billingplace},
414 active => $bookseller->active,
415 booksellerid => $bookseller->id,
416 booksellername => $bookseller->name,
417 books_loop => \@books_loop,
418 book_foot_loop => \@book_foot_loop,
419 cancelledorders_loop => \@cancelledorders_loop,
420 total_quantity => $total_quantity,
421 total_tax_excluded => $total_tax_excluded,
422 total_tax_included => $total_tax_included,
423 total_tax_value => $total_tax_value,
424 currency => $active_currency->currency,
425 listincgst => $bookseller->listincgst,
426 basketgroups => $basketgroups,
427 basketgroup => $basketgroup,
428 grouped => $basket->{basketgroupid},
429 # The double negatives and booleans here mean:
430 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
432 # (The template has another implicit restriction that the order cannot be closed if there
433 # are any orders with uncertain prices.)
434 unclosable => @orders ? $basket->{is_standing} : 1,
435 has_budgets => $has_budgets,
436 duplinbatch => $duplinbatch,
437 csv_profiles => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
438 available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
439 additional_field_values => { map {
440 $_->field->name => $_->value
441 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
445 $template->param( messages => \@messages );
446 output_html_with_http_headers $query, $cookie, $template->output;
448 sub get_order_infos {
449 my $order = shift;
450 my $bookseller = shift;
451 my $qty = $order->{'quantity'} || 0;
452 if ( !defined $order->{quantityreceived} ) {
453 $order->{quantityreceived} = 0;
455 my $budget = GetBudget($order->{budget_id});
456 my $basket = GetBasket($order->{basketno});
458 my %line = %{ $order };
459 # Don't show unreceived standing orders as received
460 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
461 $line{basketno} = $basketno;
462 $line{budget_name} = $budget->{budget_name};
464 # If we have an actual cost that should be the total, otherwise use the ecost
465 $line{unitprice_tax_included} += 0;
466 $line{unitprice_tax_excluded} += 0;
467 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
468 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
469 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
470 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
471 $line{tax_value} = $line{tax_value_on_ordering};
472 $line{tax_rate} = $line{tax_rate_on_ordering};
474 if ( $line{'title'} ) {
475 my $volume = $order->{'volume'};
476 my $seriestitle = $order->{'seriestitle'};
477 $line{'title'} .= " / $seriestitle" if $seriestitle;
478 $line{'title'} .= " / $volume" if $volume;
481 my $biblionumber = $order->{'biblionumber'};
482 if ( $biblionumber ) { # The biblio still exists
483 my $biblio = Koha::Biblios->find( $biblionumber );
484 my $countbiblio = $biblio->active_orders->count;
486 my $ordernumber = $order->{'ordernumber'};
487 my $cnt_subscriptions = $biblio->subscriptions->count;
488 my $itemcount = $biblio->items->count;
489 my $holds_count = $biblio->holds->count;
490 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
491 my $items = $order->items;
492 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
494 # 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
495 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
496 $line{items} = $itemcount - $items->count;
497 $line{left_item} = 1 if $line{items} >= 1;
498 $line{left_biblio} = 1 if $countbiblio > 1;
499 $line{biblios} = $countbiblio - 1;
500 $line{left_subscription} = 1 if $cnt_subscriptions;
501 $line{subscriptions} = $cnt_subscriptions;
502 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
503 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
504 $line{holds} = $holds_count;
505 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
506 $line{order_object} = $order;
510 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
511 $line{suggestionid} = $$suggestion{suggestionid};
512 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
513 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
515 foreach my $key (qw(transferred_from transferred_to)) {
516 if ($line{$key}) {
517 my $order = GetOrder($line{$key});
518 my $basket = GetBasket($order->{basketno});
519 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
520 $line{$key} = {
521 order => $order,
522 basket => $basket,
523 bookseller => $bookseller,
524 timestamp => $line{$key . '_timestamp'},
529 return \%line;
532 sub edi_close_and_order {
533 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
534 if ($confirm) {
535 my $edi_params = {
536 basketno => $basketno,
537 ean => $ean,
539 if ( $basket->{branch} ) {
540 $edi_params->{branchcode} = $basket->{branch};
542 if ( create_edi_order($edi_params) ) {
543 #$template->param( edifile => 1 );
545 CloseBasket($basketno);
547 # if requested, create basket group, close it and attach the basket
548 if ( $query->param('createbasketgroup') ) {
549 my $branchcode;
550 if ( C4::Context->userenv
551 and C4::Context->userenv->{'branch'} )
553 $branchcode = C4::Context->userenv->{'branch'};
555 my $basketgroupid = NewBasketgroup(
557 name => $basket->{basketname},
558 booksellerid => $booksellerid,
559 deliveryplace => $branchcode,
560 billingplace => $branchcode,
561 closed => 1,
564 ModBasket(
566 basketno => $basketno,
567 basketgroupid => $basketgroupid
570 print $query->redirect(
571 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
574 else {
575 print $query->redirect(
576 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
579 exit;
581 else {
582 $template->param(
583 edi_confirm => 1,
584 booksellerid => $booksellerid,
585 basketno => $basket->{basketno},
586 basketname => $basket->{basketname},
587 basketgroupname => $basket->{basketname},
589 if ($ean) {
590 $template->param( ean => $ean );
594 return;