Bug 26529: Define some rules as not able to be blank
[koha.git] / acqui / basket.pl
blob7d1687996f00aa337923f03a824faf1fd5cb6553
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 flagsrequired => { acquisition => 'order_manage' },
89 debug => 1,
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)) {
104 $template->param(
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;
112 exit;
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';
124 my @messages;
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 @orders = GetOrders($basketno);
134 #Delete all orders included in that basket, and all items received.
135 foreach my $myorder (@orders){
136 DelOrder($myorder->{biblionumber},$myorder->{ordernumber});
138 # if $delbiblio = 1, delete the records if possible
139 if ((defined $delbiblio)and ($delbiblio ==1)){
140 my @cannotdelbiblios ;
141 foreach my $myorder (@orders){
142 my $biblionumber = $myorder->{'biblionumber'};
143 my $biblio = Koha::Biblios->find( $biblionumber );
144 my $countbiblio = $biblio->active_orders->count;
145 my $ordernumber = $myorder->{'ordernumber'};
146 my $cnt_subscriptions = $biblio->subscriptions->count;
147 my $itemcount = $biblio->items->count;
148 my $error;
149 if ($countbiblio == 0 && $itemcount == 0 && not $cnt_subscriptions ) {
150 $error = DelBiblio($myorder->{biblionumber}) }
151 else {
152 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
153 title=> $myorder->{'title'},
154 author=> $myorder->{'author'},
155 countbiblio=> $countbiblio,
156 itemcount=>$itemcount,
157 subscriptions => $cnt_subscriptions};
159 if ($error) {
160 push @cannotdelbiblios, {biblionumber=> ($myorder->{biblionumber}),
161 title=> $myorder->{'title'},
162 author=> $myorder->{'author'},
163 othererror=> $error};
166 $template->param( cannotdelbiblios => \@cannotdelbiblios );
168 # delete the basket
169 DelBasket($basketno,);
170 $template->param(
171 delete_confirmed => 1,
172 booksellername => $bookseller->name,
173 booksellerid => $booksellerid,
175 } elsif ( !$bookseller ) {
176 $template->param( NO_BOOKSELLER => 1 );
177 } elsif ($op eq 'export') {
178 print $query->header(
179 -type => 'text/csv',
180 -attachment => 'basket' . $basket->{'basketno'} . '.csv',
182 my $csv_profile_id = $query->param('csv_profile');
183 print GetBasketAsCSV( scalar $query->param('basketno'), $query, $csv_profile_id ); # if no csv_profile_id passed, using default rows
184 exit;
185 } elsif ($op eq 'email') {
186 my $err = eval {
187 SendAlerts( 'orderacquisition', $query->param('basketno'), 'ACQORDER' );
189 if ( $@ ) {
190 push @messages, { type => 'error', code => $@ };
191 } elsif ( ref $err and exists $err->{error} ) {
192 push @messages, { type => 'error', code => $err->{error} };
193 } else {
194 push @messages, { type => 'message', code => 'email_sent' };
197 $op = 'list';
198 } elsif ($op eq 'close') {
199 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
200 if ($confirm) {
201 my $basketno = $query->param('basketno');
202 my $booksellerid = $query->param('booksellerid');
203 $basketno =~ /^\d+$/ and CloseBasket($basketno);
204 # if requested, create basket group, close it and attach the basket
205 if ($query->param('createbasketgroup')) {
206 my $branchcode;
207 if(C4::Context->userenv and C4::Context->userenv->{'branch'}) {
208 $branchcode = C4::Context->userenv->{'branch'};
210 my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
211 booksellerid => $booksellerid,
212 deliveryplace => $branchcode,
213 billingplace => $branchcode,
214 closed => 1,
216 ModBasket( { basketno => $basketno,
217 basketgroupid => $basketgroupid } );
218 print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
219 } else {
220 print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
222 exit;
223 } else {
224 $template->param(
225 confirm_close => "1",
226 booksellerid => $booksellerid,
227 booksellername => $bookseller->name,
228 basketno => $basket->{'basketno'},
229 basketname => $basket->{'basketname'},
230 basketgroupname => $basket->{'basketname'},
233 } elsif ($op eq 'reopen') {
234 ReopenBasket(scalar $query->param('basketno'));
235 print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
237 elsif ( $op eq 'ediorder' ) {
238 edi_close_and_order()
239 } elsif ( $op eq 'mod_users' ) {
240 my $basketusers_ids = $query->param('users_ids');
241 my @basketusers = split( /:/, $basketusers_ids );
242 ModBasketUsers($basketno, @basketusers);
243 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
244 exit;
245 } elsif ( $op eq 'mod_branch' ) {
246 my $branch = $query->param('branch');
247 $branch = undef if(defined $branch and $branch eq '');
248 ModBasket({
249 basketno => $basket->{basketno},
250 branch => $branch
252 print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
253 exit;
256 if ( $op eq 'list' ) {
257 my @branches_loop;
258 # get librarian branch...
259 if ( C4::Context->preference("IndependentBranches") ) {
260 my $userenv = C4::Context->userenv;
261 unless ( C4::Context->IsSuperLibrarian() ) {
262 my $validtest = ( $basket->{creationdate} eq '' )
263 || ( $userenv->{branch} eq $basket->{branch} )
264 || ( $userenv->{branch} eq '' )
265 || ( $basket->{branch} eq '' );
266 unless ($validtest) {
267 print $query->redirect("../mainpage.pl");
268 exit 0;
272 if (!defined $basket->{branch} or $basket->{branch} eq $userenv->{branch}) {
273 push @branches_loop, {
274 branchcode => $userenv->{branch},
275 branchname => $userenv->{branchname},
276 selected => 1,
279 } else {
280 # get branches
281 my $branches = Koha::Libraries->search( {}, { order_by => ['branchname'] } )->unblessed;
282 foreach my $branch (@$branches) {
283 my $selected = 0;
284 if (defined $basket->{branch}) {
285 $selected = 1 if $branch->{branchcode} eq $basket->{branch};
286 } else {
287 $selected = 1 if $branch->{branchcode} eq C4::Context->userenv->{branch};
289 push @branches_loop, {
290 branchcode => $branch->{branchcode},
291 branchname => $branch->{branchname},
292 selected => $selected
297 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
298 my ($basketgroup, $basketgroups);
299 my $patron = Koha::Patrons->find($loggedinuser);
300 if ($basket->{closedate} && haspermission($patron->userid, { acquisition => 'group_manage'} )) {
301 $basketgroups = GetBasketgroups($basket->{booksellerid});
302 for my $bg ( @{$basketgroups} ) {
303 if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
304 $bg->{default} = 1;
305 $basketgroup = $bg;
310 # if the basket is closed, calculate estimated delivery date
311 my $estimateddeliverydate;
312 if( $basket->{closedate} ) {
313 my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
314 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->deliverytime);
315 $estimateddeliverydate = sprintf( "%04d-%02d-%02d", $year, $month, $day );
318 # if new basket, pre-fill infos
319 $basket->{creationdate} = "" unless ( $basket->{creationdate} );
320 $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
321 $debug
322 and warn sprintf
323 "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
324 $basket->{creationdate}, $basket->{authorisedby};
326 my @basketusers_ids = GetBasketUsers($basketno);
327 my @basketusers;
328 foreach my $basketuser_id (@basketusers_ids) {
329 # FIXME Could be improved with a search -in
330 my $basket_patron = Koha::Patrons->find( $basketuser_id );
331 push @basketusers, $basket_patron if $basket_patron;
334 my $active_currency = Koha::Acquisition::Currencies->get_active;
336 my @orders = GetOrders( $basketno );
337 my @books_loop;
339 my @book_foot_loop;
340 my %foot;
341 my $total_quantity = 0;
342 my $total_tax_excluded = 0;
343 my $total_tax_included = 0;
344 my $total_tax_value = 0;
345 for my $order (@orders) {
346 my $line = get_order_infos( $order, $bookseller);
347 if ( $line->{uncertainprice} ) {
348 $template->param( uncertainprices => 1 );
351 $line->{tax_rate} = $line->{tax_rate_on_ordering} // 0;
352 $line->{tax_value} = $line->{tax_value_on_ordering} // 0;
354 push @books_loop, $line;
356 $foot{$$line{tax_rate}}{tax_rate} = $$line{tax_rate};
357 $foot{$$line{tax_rate}}{tax_value} += get_rounded_price($$line{tax_value});
358 $total_tax_value += $$line{tax_value};
359 $foot{$$line{tax_rate}}{quantity} += get_rounded_price($$line{quantity});
360 $total_quantity += $$line{quantity};
361 $foot{$$line{tax_rate}}{total_tax_excluded} += $$line{total_tax_excluded};
362 $total_tax_excluded += $$line{total_tax_excluded};
363 $foot{$$line{tax_rate}}{total_tax_included} += $$line{total_tax_included};
364 $total_tax_included += $$line{total_tax_included};
367 push @book_foot_loop, map {$_} values %foot;
369 # Get cancelled orders
370 my @cancelledorders = GetOrders($basketno, { cancelled => 1 });
371 my @cancelledorders_loop;
372 for my $order (@cancelledorders) {
373 my $line = get_order_infos( $order, $bookseller);
374 push @cancelledorders_loop, $line;
377 my $contract = GetContract({
378 contractnumber => $basket->{contractnumber}
381 if ($basket->{basketgroupid}){
382 $basketgroup = GetBasketgroup($basket->{basketgroupid});
384 my $budgets = GetBudgetHierarchy;
385 my $has_budgets = 0;
386 foreach my $r (@{$budgets}) {
387 next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
389 $has_budgets = 1;
390 last;
393 $template->param(
394 basketno => $basketno,
395 basket => $basket,
396 basketname => $basket->{'basketname'},
397 basketbranchcode => $basket->{branch},
398 basketnote => $basket->{note},
399 basketbooksellernote => $basket->{booksellernote},
400 basketcontractno => $basket->{contractnumber},
401 basketcontractname => $contract->{contractname},
402 branches_loop => \@branches_loop,
403 creationdate => $basket->{creationdate},
404 authorisedby => $basket->{authorisedby},
405 authorisedbyname => $basket->{authorisedbyname},
406 users_ids => join(':', @basketusers_ids),
407 users => \@basketusers,
408 closedate => $basket->{closedate},
409 estimateddeliverydate=> $estimateddeliverydate,
410 is_standing => $basket->{is_standing},
411 deliveryplace => $basket->{deliveryplace},
412 billingplace => $basket->{billingplace},
413 active => $bookseller->active,
414 booksellerid => $bookseller->id,
415 booksellername => $bookseller->name,
416 books_loop => \@books_loop,
417 book_foot_loop => \@book_foot_loop,
418 cancelledorders_loop => \@cancelledorders_loop,
419 total_quantity => $total_quantity,
420 total_tax_excluded => $total_tax_excluded,
421 total_tax_included => $total_tax_included,
422 total_tax_value => $total_tax_value,
423 currency => $active_currency->currency,
424 listincgst => $bookseller->listincgst,
425 basketgroups => $basketgroups,
426 basketgroup => $basketgroup,
427 grouped => $basket->{basketgroupid},
428 # The double negatives and booleans here mean:
429 # "A basket cannot be closed if there are no orders in it or it's a standing order basket."
431 # (The template has another implicit restriction that the order cannot be closed if there
432 # are any orders with uncertain prices.)
433 unclosable => @orders ? $basket->{is_standing} : 1,
434 has_budgets => $has_budgets,
435 duplinbatch => $duplinbatch,
436 csv_profiles => [ Koha::CsvProfiles->search({ type => 'sql', used_for => 'export_basket' }) ],
437 available_additional_fields => [ Koha::AdditionalFields->search( { tablename => 'aqbasket' } ) ],
438 additional_field_values => { map {
439 $_->field->name => $_->value
440 } Koha::Acquisition::Baskets->find($basketno)->additional_field_values->as_list },
444 $template->param( messages => \@messages );
445 output_html_with_http_headers $query, $cookie, $template->output;
447 sub get_order_infos {
448 my $order = shift;
449 my $bookseller = shift;
450 my $qty = $order->{'quantity'} || 0;
451 if ( !defined $order->{quantityreceived} ) {
452 $order->{quantityreceived} = 0;
454 my $budget = GetBudget($order->{budget_id});
455 my $basket = GetBasket($order->{basketno});
457 my %line = %{ $order };
458 # Don't show unreceived standing orders as received
459 $line{order_received} = ( $qty == $order->{'quantityreceived'} && ( $basket->{is_standing} ? $qty : 1 ) );
460 $line{basketno} = $basketno;
461 $line{budget_name} = $budget->{budget_name};
463 # If we have an actual cost that should be the total, otherwise use the ecost
464 $line{unitprice_tax_included} += 0;
465 $line{unitprice_tax_excluded} += 0;
466 my $cost_tax_included = $line{unitprice_tax_included} || $line{ecost_tax_included};
467 my $cost_tax_excluded = $line{unitprice_tax_excluded} || $line{ecost_tax_excluded};
468 $line{total_tax_included} = get_rounded_price($cost_tax_included) * $line{quantity};
469 $line{total_tax_excluded} = get_rounded_price($cost_tax_excluded) * $line{quantity};
470 $line{tax_value} = $line{tax_value_on_ordering};
471 $line{tax_rate} = $line{tax_rate_on_ordering};
473 if ( $line{'title'} ) {
474 my $volume = $order->{'volume'};
475 my $seriestitle = $order->{'seriestitle'};
476 $line{'title'} .= " / $seriestitle" if $seriestitle;
477 $line{'title'} .= " / $volume" if $volume;
480 my $biblionumber = $order->{'biblionumber'};
481 if ( $biblionumber ) { # The biblio still exists
482 my $biblio = Koha::Biblios->find( $biblionumber );
483 my $countbiblio = $biblio->active_orders->count;
485 my $ordernumber = $order->{'ordernumber'};
486 my $cnt_subscriptions = $biblio->subscriptions->count;
487 my $itemcount = $biblio->items->count;
488 my $holds_count = $biblio->holds->count;
489 my $order = Koha::Acquisition::Orders->find($ordernumber); # FIXME We should certainly do that at the beginning of this sub
490 my $items = $order->items;
491 my $itemholds = $biblio->holds->search({ itemnumber => { -in => [ $items->get_column('itemnumber') ] } })->count;
493 # 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
494 $line{can_del_bib} = 1 if $countbiblio <= 1 && $itemcount == $items->count && !($cnt_subscriptions) && !($holds_count);
495 $line{items} = $itemcount - $items->count;
496 $line{left_item} = 1 if $line{items} >= 1;
497 $line{left_biblio} = 1 if $countbiblio > 1;
498 $line{biblios} = $countbiblio - 1;
499 $line{left_subscription} = 1 if $cnt_subscriptions;
500 $line{subscriptions} = $cnt_subscriptions;
501 ($holds_count >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
502 $line{left_holds_on_order} = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
503 $line{holds} = $holds_count;
504 $line{holds_on_order} = $itemholds?$itemholds:$holds_count if $line{left_holds_on_order};
505 $line{order_object} = $order;
509 my $suggestion = GetSuggestionInfoFromBiblionumber($line{biblionumber});
510 $line{suggestionid} = $$suggestion{suggestionid};
511 $line{surnamesuggestedby} = $$suggestion{surnamesuggestedby};
512 $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
514 foreach my $key (qw(transferred_from transferred_to)) {
515 if ($line{$key}) {
516 my $order = GetOrder($line{$key});
517 my $basket = GetBasket($order->{basketno});
518 my $bookseller = Koha::Acquisition::Booksellers->find( $basket->{booksellerid} );
519 $line{$key} = {
520 order => $order,
521 basket => $basket,
522 bookseller => $bookseller,
523 timestamp => $line{$key . '_timestamp'},
528 return \%line;
531 sub edi_close_and_order {
532 my $confirm = $query->param('confirm') || $confirm_pref eq '2';
533 if ($confirm) {
534 my $edi_params = {
535 basketno => $basketno,
536 ean => $ean,
538 if ( $basket->{branch} ) {
539 $edi_params->{branchcode} = $basket->{branch};
541 if ( create_edi_order($edi_params) ) {
542 #$template->param( edifile => 1 );
544 CloseBasket($basketno);
546 # if requested, create basket group, close it and attach the basket
547 if ( $query->param('createbasketgroup') ) {
548 my $branchcode;
549 if ( C4::Context->userenv
550 and C4::Context->userenv->{'branch'} )
552 $branchcode = C4::Context->userenv->{'branch'};
554 my $basketgroupid = NewBasketgroup(
556 name => $basket->{basketname},
557 booksellerid => $booksellerid,
558 deliveryplace => $branchcode,
559 billingplace => $branchcode,
560 closed => 1,
563 ModBasket(
565 basketno => $basketno,
566 basketgroupid => $basketgroupid
569 print $query->redirect(
570 "/cgi-bin/koha/acqui/basketgroup.pl?booksellerid=$booksellerid&closed=1"
573 else {
574 print $query->redirect(
575 "/cgi-bin/koha/acqui/booksellers.pl?booksellerid=$booksellerid"
578 exit;
580 else {
581 $template->param(
582 edi_confirm => 1,
583 booksellerid => $booksellerid,
584 basketno => $basket->{basketno},
585 basketname => $basket->{basketname},
586 basketgroupname => $basket->{basketname},
588 if ($ean) {
589 $template->param( ean => $ean );
593 return;