Bug 7295: More granular permissions for baskets
[koha.git] / acqui / basket.pl
1 #!/usr/bin/perl
2
3 #script to show display basket of orders
4
5 # Copyright 2000 - 2004 Katipo
6 # Copyright 2008 - 2009 BibLibre SARL
7 #
8 # This file is part of Koha.
9 #
10 # Koha is free software; you can redistribute it and/or modify it under the
11 # terms of the GNU General Public License as published by the Free Software
12 # Foundation; either version 2 of the License, or (at your option) any later
13 # version.
14 #
15 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
16 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License along
20 # with Koha; if not, write to the Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23 use strict;
24 use warnings;
25 use C4::Auth;
26 use C4::Koha;
27 use C4::Output;
28 use CGI;
29 use C4::Acquisition;
30 use C4::Budgets;
31 use C4::Branch;
32 use C4::Bookseller qw( GetBookSellerFromId);
33 use C4::Debug;
34 use C4::Biblio;
35 use C4::Members qw/GetMember/;  #needed for permissions checking for changing basketgroup of a basket
36 use C4::Items;
37 use C4::Suggestions;
38 use Date::Calc qw/Add_Delta_Days/;
39
40 =head1 NAME
41
42 basket.pl
43
44 =head1 DESCRIPTION
45
46  This script display all informations about basket for the supplier given
47  on input arg.  Moreover, it allows us to add a new order for this supplier from
48  an existing record, a suggestion or a new record.
49
50 =head1 CGI PARAMETERS
51
52 =over 4
53
54 =item $basketno
55
56 The basket number.
57
58 =item booksellerid
59
60 the supplier this script have to display the basket.
61
62 =item order
63
64 =back
65
66 =cut
67
68 my $query        = new CGI;
69 our $basketno     = $query->param('basketno');
70 my $booksellerid = $query->param('booksellerid');
71
72 my ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
73     {
74         template_name   => "acqui/basket.tmpl",
75         query           => $query,
76         type            => "intranet",
77         authnotrequired => 0,
78         flagsrequired   => { acquisition => 'order_manage' },
79         debug           => 1,
80     }
81 );
82
83 my $basket = GetBasket($basketno);
84 $booksellerid = $basket->{booksellerid} unless $booksellerid;
85 my ($bookseller) = GetBookSellerFromId($booksellerid);
86
87 unless (CanUserManageBasket($loggedinuser, $basket, $userflags)) {
88     $template->param(
89         cannot_manage_basket => 1,
90         basketno => $basketno,
91         basketname => $basket->{basketname},
92         booksellerid => $booksellerid,
93         name => $bookseller->{name}
94     );
95     output_html_with_http_headers $query, $cookie, $template->output;
96     exit;
97 }
98
99 # FIXME : what about the "discount" percentage?
100 # FIXME : the query->param('booksellerid') below is probably useless. The bookseller is always known from the basket
101 # if no booksellerid in parameter, get it from basket
102 # warn "=>".$basket->{booksellerid};
103 my $op = $query->param('op');
104 if (!defined $op) {
105     $op = q{};
106 }
107
108 my $confirm_pref= C4::Context->preference("BasketConfirmations") || '1';
109 $template->param( skip_confirm_reopen => 1) if $confirm_pref eq '2';
110
111 if ( $op eq 'delete_confirm' ) {
112     my $basketno = $query->param('basketno');
113     DelBasket($basketno);
114     $template->param( delete_confirmed => 1 );
115 } elsif ( !$bookseller ) {
116     $template->param( NO_BOOKSELLER => 1 );
117 } elsif ( $op eq 'del_basket') {
118     $template->param( delete_confirm => 1 );
119     if ( C4::Context->preference("IndependentBranches") ) {
120         my $userenv = C4::Context->userenv;
121         unless ( $userenv->{flags} == 1 ) {
122             my $validtest = ( $basket->{creationdate} eq '' )
123               || ( $userenv->{branch} eq $basket->{branch} )
124               || ( $userenv->{branch} eq '' )
125               || ( $basket->{branch}  eq '' );
126             unless ($validtest) {
127                 print $query->redirect("../mainpage.pl");
128                 exit 1;
129             }
130         }
131     }
132     $basket->{creationdate} = ""            unless ( $basket->{creationdate} );
133     $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
134     my $contract = &GetContract($basket->{contractnumber});
135     $template->param(
136         basketno             => $basketno,
137         basketname           => $basket->{'basketname'},
138         basketnote           => $basket->{note},
139         basketbooksellernote => $basket->{booksellernote},
140         basketcontractno     => $basket->{contractnumber},
141         basketcontractname   => $contract->{contractname},
142         creationdate         => $basket->{creationdate},
143         authorisedby         => $basket->{authorisedby},
144         authorisedbyname     => $basket->{authorisedbyname},
145         closedate            => $basket->{closedate},
146         deliveryplace        => $basket->{deliveryplace},
147         billingplace         => $basket->{billingplace},
148         active               => $bookseller->{'active'},
149         booksellerid         => $bookseller->{'id'},
150         name                 => $bookseller->{'name'},
151         address1             => $bookseller->{'address1'},
152         address2             => $bookseller->{'address2'},
153         address3             => $bookseller->{'address3'},
154         address4             => $bookseller->{'address4'},
155       );
156 } elsif ($op eq 'attachbasket' && $template->{'VARS'}->{'CAN_user_acquisition_group_manage'} == 1) {
157       print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?basketno=' . $basket->{'basketno'} . '&op=attachbasket&booksellerid=' . $booksellerid);
158     # check if we have to "close" a basket before building page
159 } elsif ($op eq 'export') {
160     print $query->header(
161         -type       => 'text/csv',
162         -attachment => 'basket' . $basket->{'basketno'} . '.csv',
163     );
164     print GetBasketAsCSV($query->param('basketno'), $query);
165     exit;
166 } elsif ($op eq 'close') {
167     my $confirm = $query->param('confirm') || $confirm_pref eq '2';
168     if ($confirm) {
169         my $basketno = $query->param('basketno');
170         my $booksellerid = $query->param('booksellerid');
171         $basketno =~ /^\d+$/ and CloseBasket($basketno);
172         # if requested, create basket group, close it and attach the basket
173         if ($query->param('createbasketgroup')) {
174             my $branchcode;
175             if(C4::Context->userenv and C4::Context->userenv->{'branch'}
176               and C4::Context->userenv->{'branch'} ne "NO_LIBRARY_SET") {
177                 $branchcode = C4::Context->userenv->{'branch'};
178             }
179             my $basketgroupid = NewBasketgroup( { name => $basket->{basketname},
180                             booksellerid => $booksellerid,
181                             deliveryplace => $branchcode,
182                             billingplace => $branchcode,
183                             closed => 1,
184                             });
185             ModBasket( { basketno => $basketno,
186                          basketgroupid => $basketgroupid } );
187             print $query->redirect('/cgi-bin/koha/acqui/basketgroup.pl?booksellerid='.$booksellerid.'&closed=1');
188         } else {
189             print $query->redirect('/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' . $booksellerid);
190         }
191         exit;
192     } else {
193     $template->param(
194         confirm_close   => "1",
195         booksellerid    => $booksellerid,
196         basketno        => $basket->{'basketno'},
197         basketname      => $basket->{'basketname'},
198         basketgroupname => $basket->{'basketname'},
199     );
200     }
201 } elsif ($op eq 'reopen') {
202     ReopenBasket($query->param('basketno'));
203     print $query->redirect('/cgi-bin/koha/acqui/basket.pl?basketno='.$basket->{'basketno'})
204 } elsif ( $op eq 'mod_users' ) {
205     my $basketusers_ids = $query->param('basketusers_ids');
206     my @basketusers = split( /:/, $basketusers_ids );
207     ModBasketUsers($basketno, @basketusers);
208     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
209     exit;
210 } elsif ( $op eq 'mod_branch' ) {
211     my $branch = $query->param('branch');
212     $branch = undef if(defined $branch and $branch eq '');
213     ModBasket({
214         basketno => $basket->{basketno},
215         branch   => $branch
216     });
217     print $query->redirect("/cgi-bin/koha/acqui/basket.pl?basketno=$basketno");
218     exit;
219 } else {
220     # get librarian branch...
221     if ( C4::Context->preference("IndependentBranches") ) {
222         my $userenv = C4::Context->userenv;
223         unless ( $userenv->{flags} == 1 ) {
224             my $validtest = ( $basket->{creationdate} eq '' )
225               || ( $userenv->{branch} eq $basket->{branch} )
226               || ( $userenv->{branch} eq '' )
227               || ( $basket->{branch}  eq '' );
228             unless ($validtest) {
229                 print $query->redirect("../mainpage.pl");
230                 exit 1;
231             }
232         }
233     }
234     # get branches
235     my $branches = C4::Branch::GetBranches;
236     my @branches_loop;
237     foreach my $branch (sort keys %$branches) {
238         push @branches_loop, {
239             branchcode => $branch,
240             branchname => $branches->{$branch}->{branchname},
241             selected => (defined $basket->{branch} and $branch eq $basket->{branch}) ? 1 : 0
242         };
243     }
244
245 #if the basket is closed,and the user has the permission to edit basketgroups, display a list of basketgroups
246     my ($basketgroup, $basketgroups);
247     my $staffuser = GetMember(borrowernumber => $loggedinuser);
248     if ($basket->{closedate} && haspermission($staffuser->{userid}, { acquisition => 'group_manage'} )) {
249         $basketgroups = GetBasketgroups($basket->{booksellerid});
250         for my $bg ( @{$basketgroups} ) {
251             if ($basket->{basketgroupid} && $basket->{basketgroupid} == $bg->{id}){
252                 $bg->{default} = 1;
253                 $basketgroup = $bg;
254             }
255         }
256         my %emptygroup = ( id   =>   undef,
257                            name =>   "No group");
258         if ( ! $basket->{basketgroupid} ) {
259             $emptygroup{default} = 1;
260             $emptygroup{nogroup} = 1;
261         }
262         unshift( @$basketgroups, \%emptygroup );
263     }
264
265     # if the basket is closed, calculate estimated delivery date
266     my $estimateddeliverydate;
267     if( $basket->{closedate} ) {
268         my ($year, $month, $day) = ($basket->{closedate} =~ /(\d+)-(\d+)-(\d+)/);
269         ($year, $month, $day) = Add_Delta_Days($year, $month, $day, $bookseller->{deliverytime});
270         $estimateddeliverydate = "$year-$month-$day";
271     }
272
273     # if new basket, pre-fill infos
274     $basket->{creationdate} = ""            unless ( $basket->{creationdate} );
275     $basket->{authorisedby} = $loggedinuser unless ( $basket->{authorisedby} );
276     $debug
277       and warn sprintf
278       "loggedinuser: $loggedinuser; creationdate: %s; authorisedby: %s",
279       $basket->{creationdate}, $basket->{authorisedby};
280
281     my @basketusers_ids = GetBasketUsers($basketno);
282     my @basketusers;
283     foreach my $basketuser_id (@basketusers_ids) {
284         my $basketuser = GetMember(borrowernumber => $basketuser_id);
285         push @basketusers, $basketuser if $basketuser;
286     }
287
288     #to get active currency
289     my $cur = GetCurrency();
290
291
292     my @results = GetOrders( $basketno );
293     my @books_loop;
294
295     my @book_foot_loop;
296     my %foot;
297     my $total_quantity = 0;
298     my $total_gste = 0;
299     my $total_gsti = 0;
300     my $total_gstvalue = 0;
301     for my $order (@results) {
302         my $line = get_order_infos( $order, $bookseller);
303         if ( $line->{uncertainprice} ) {
304             $template->param( uncertainprices => 1 );
305         }
306
307         push @books_loop, $line;
308
309         $foot{$$line{gstgsti}}{gstgsti} = $$line{gstgsti};
310         $foot{$$line{gstgsti}}{gstvalue} += $$line{gstvalue};
311         $total_gstvalue += $$line{gstvalue};
312         $foot{$$line{gstgsti}}{quantity}  += $$line{quantity};
313         $total_quantity += $$line{quantity};
314         $foot{$$line{gstgsti}}{totalgste} += $$line{totalgste};
315         $total_gste += $$line{totalgste};
316         $foot{$$line{gstgsti}}{totalgsti} += $$line{totalgsti};
317         $total_gsti += $$line{totalgsti};
318     }
319
320     push @book_foot_loop, map {$_} values %foot;
321
322     # Get cancelled orders
323     @results = GetCancelledOrders($basketno);
324     my @cancelledorders_loop;
325     for my $order (@results) {
326         my $line = get_order_infos( $order, $bookseller);
327         push @cancelledorders_loop, $line;
328     }
329
330     my $contract = &GetContract($basket->{contractnumber});
331     my @orders = GetOrders($basketno);
332
333     if ($basket->{basketgroupid}){
334         $basketgroup = GetBasketgroup($basket->{basketgroupid});
335         $basketgroup->{deliveryplacename} = C4::Branch::GetBranchName( $basketgroup->{deliveryplace} );
336         $basketgroup->{billingplacename} = C4::Branch::GetBranchName( $basketgroup->{billingplace} );
337     }
338     my $borrower= GetMember('borrowernumber' => $loggedinuser);
339     my $budgets = GetBudgetHierarchy;
340     my $has_budgets = 0;
341     foreach my $r (@{$budgets}) {
342         if (!defined $r->{budget_amount} || $r->{budget_amount} == 0) {
343             next;
344         }
345         next unless (CanUserUseBudget($loggedinuser, $r, $userflags));
346
347         $has_budgets = 1;
348         last;
349     }
350
351     $template->param(
352         basketno             => $basketno,
353         basketname           => $basket->{'basketname'},
354         basketnote           => $basket->{note},
355         basketbooksellernote => $basket->{booksellernote},
356         basketcontractno     => $basket->{contractnumber},
357         basketcontractname   => $contract->{contractname},
358         branches_loop        => \@branches_loop,
359         creationdate         => $basket->{creationdate},
360         authorisedby         => $basket->{authorisedby},
361         authorisedbyname     => $basket->{authorisedbyname},
362         basketusers_ids      => join(':', @basketusers_ids),
363         basketusers          => \@basketusers,
364         closedate            => $basket->{closedate},
365         estimateddeliverydate=> $estimateddeliverydate,
366         deliveryplace        => C4::Branch::GetBranchName( $basket->{deliveryplace} ),
367         billingplace         => C4::Branch::GetBranchName( $basket->{billingplace} ),
368         active               => $bookseller->{'active'},
369         booksellerid         => $bookseller->{'id'},
370         name                 => $bookseller->{'name'},
371         books_loop           => \@books_loop,
372         book_foot_loop       => \@book_foot_loop,
373         cancelledorders_loop => \@cancelledorders_loop,
374         total_quantity       => $total_quantity,
375         total_gste           => sprintf( "%.2f", $total_gste ),
376         total_gsti           => sprintf( "%.2f", $total_gsti ),
377         total_gstvalue       => sprintf( "%.2f", $total_gstvalue ),
378         currency             => $cur->{'currency'},
379         listincgst           => $bookseller->{listincgst},
380         basketgroups         => $basketgroups,
381         basketgroup          => $basketgroup,
382         grouped              => $basket->{basketgroupid},
383         unclosable           => @orders ? 0 : 1, 
384         has_budgets          => $has_budgets,
385     );
386 }
387
388 sub get_order_infos {
389     my $order = shift;
390     my $bookseller = shift;
391     my $qty = $order->{'quantity'} || 0;
392     if ( !defined $order->{quantityreceived} ) {
393         $order->{quantityreceived} = 0;
394     }
395     my $budget = GetBudget( $order->{'budget_id'} );
396
397     my %line = %{ $order };
398     $line{order_received} = ( $qty == $order->{'quantityreceived'} );
399     $line{basketno}       = $basketno;
400     $line{budget_name}    = $budget->{budget_name};
401     $line{rrp} = ConvertCurrency( $order->{'currency'}, $line{rrp} ); # FIXME from comm
402     if ( $bookseller->{'listincgst'} ) {
403         $line{rrpgsti} = sprintf( "%.2f", $line{rrp} );
404         $line{gstgsti} = sprintf( "%.2f", $line{gstrate} * 100 );
405         $line{rrpgste} = sprintf( "%.2f", $line{rrp} / ( 1 + ( $line{gstgsti} / 100 ) ) );
406         $line{gstgste} = sprintf( "%.2f", $line{gstgsti} / ( 1 + ( $line{gstgsti} / 100 ) ) );
407         $line{ecostgsti} = sprintf( "%.2f", $line{ecost} );
408         $line{ecostgste} = sprintf( "%.2f", $line{ecost} / ( 1 + ( $line{gstgsti} / 100 ) ) );
409         $line{gstvalue} = sprintf( "%.2f", ( $line{ecostgsti} - $line{ecostgste} ) * $line{quantity});
410         $line{totalgste} = sprintf( "%.2f", $order->{quantity} * $line{ecostgste} );
411         $line{totalgsti} = sprintf( "%.2f", $order->{quantity} * $line{ecostgsti} );
412     } else {
413         $line{rrpgsti} = sprintf( "%.2f", $line{rrp} * ( 1 + ( $line{gstrate} ) ) );
414         $line{rrpgste} = sprintf( "%.2f", $line{rrp} );
415         $line{gstgsti} = sprintf( "%.2f", $line{gstrate} * 100 );
416         $line{gstgste} = sprintf( "%.2f", $line{gstrate} * 100 );
417         $line{ecostgsti} = sprintf( "%.2f", $line{ecost} * ( 1 + ( $line{gstrate} ) ) );
418         $line{ecostgste} = sprintf( "%.2f", $line{ecost} );
419         $line{gstvalue} = sprintf( "%.2f", ( $line{ecostgsti} - $line{ecostgste} ) * $line{quantity});
420         $line{totalgste} = sprintf( "%.2f", $order->{quantity} * $line{ecostgste} );
421         $line{totalgsti} = sprintf( "%.2f", $order->{quantity} * $line{ecostgsti} );
422     }
423
424     if ( $line{uncertainprice} ) {
425         $line{rrpgste} .= ' (Uncertain)';
426     }
427     if ( $line{'title'} ) {
428         my $volume      = $order->{'volume'};
429         my $seriestitle = $order->{'seriestitle'};
430         $line{'title'} .= " / $seriestitle" if $seriestitle;
431         $line{'title'} .= " / $volume"      if $volume;
432     } else {
433         $line{'title'} = "Deleted bibliographic notice, can't find title.";
434     }
435
436     my $biblionumber = $order->{'biblionumber'};
437     my $countbiblio = CountBiblioInOrders($biblionumber);
438     my $ordernumber = $order->{'ordernumber'};
439     my @subscriptions = GetSubscriptionsId ($biblionumber);
440     my $itemcount = GetItemsCount($biblionumber);
441     my $holds  = GetHolds ($biblionumber);
442     my @items = GetItemnumbersFromOrder( $ordernumber );
443     my $itemholds;
444     foreach my $item (@items){
445         my $nb = GetItemHolds($biblionumber, $item);
446         if ($nb){
447             $itemholds += $nb;
448         }
449     }
450     # 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
451     $line{can_del_bib}          = 1 if $countbiblio <= 1 && $itemcount == scalar @items && !(@subscriptions) && !($holds);
452     $line{items}                = ($itemcount) - (scalar @items);
453     $line{left_item}            = 1 if $line{items} >= 1;
454     $line{left_biblio}          = 1 if $countbiblio > 1;
455     $line{biblios}              = $countbiblio - 1;
456     $line{left_subscription}    = 1 if scalar @subscriptions >= 1;
457     $line{subscriptions}        = scalar @subscriptions;
458     ($holds >= 1) ? $line{left_holds} = 1 : $line{left_holds} = 0;
459     $line{left_holds_on_order}  = 1 if $line{left_holds}==1 && ($line{items} == 0 || $itemholds );
460     $line{holds}                = $holds;
461     $line{holds_on_order}       = $itemholds?$itemholds:$holds if $line{left_holds_on_order};
462
463
464     my $suggestion   = GetSuggestionInfoFromBiblionumber($line{biblionumber});
465     $line{suggestionid}         = $$suggestion{suggestionid};
466     $line{surnamesuggestedby}   = $$suggestion{surnamesuggestedby};
467     $line{firstnamesuggestedby} = $$suggestion{firstnamesuggestedby};
468
469     foreach my $key (qw(transferred_from transferred_to)) {
470         if ($line{$key}) {
471             my $order = GetOrder($line{$key});
472             my $basket = GetBasket($order->{basketno});
473             my $bookseller = GetBookSellerFromId($basket->{booksellerid});
474             $line{$key} = {
475                 order => $order,
476                 basket => $basket,
477                 bookseller => $bookseller,
478                 timestamp => $line{$key . '_timestamp'},
479             };
480         }
481     }
482
483     return \%line;
484 }
485
486 output_html_with_http_headers $query, $cookie, $template->output;