Bug 18736: Use rounding syspref to determine correct prices in calculations
[koha.git] / C4 / Budgets.pm
1 package C4::Budgets;
2
3 # Copyright 2000-2002 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 #use warnings; FIXME - Bug 2505
22 use C4::Context;
23 use Koha::Database;
24 use Koha::Patrons;
25 use Koha::Acquisition::Invoice::Adjustments;
26 use C4::Debug;
27 use vars qw(@ISA @EXPORT);
28
29 BEGIN {
30         require Exporter;
31         @ISA    = qw(Exporter);
32         @EXPORT = qw(
33
34         &GetBudget
35         &GetBudgetByOrderNumber
36         &GetBudgetByCode
37         &GetBudgets
38         &BudgetsByActivity
39         &GetBudgetsReport
40         &GetBudgetReport
41         &GetBudgetHierarchy
42             &AddBudget
43         &ModBudget
44         &DelBudget
45         &GetBudgetSpent
46         &GetBudgetOrdered
47         &GetBudgetName
48         &GetPeriodsCount
49         GetBudgetHierarchySpent
50         GetBudgetHierarchyOrdered
51
52         &GetBudgetUsers
53         &ModBudgetUsers
54         &CanUserUseBudget
55         &CanUserModifyBudget
56
57             &GetBudgetPeriod
58         &GetBudgetPeriods
59         &ModBudgetPeriod
60         &AddBudgetPeriod
61             &DelBudgetPeriod
62
63         &ModBudgetPlan
64
65                 &GetBudgetsPlanCell
66         &AddBudgetPlanValue
67         &GetBudgetAuthCats
68         &BudgetHasChildren
69         &CheckBudgetParent
70         &CheckBudgetParentPerm
71
72         &HideCols
73         &GetCols
74         );
75 }
76
77 # ----------------------------BUDGETS.PM-----------------------------";
78
79 =head1 FUNCTIONS ABOUT BUDGETS
80
81 =cut
82
83 sub HideCols {
84     my ( $authcat, @hide_cols ) = @_;
85     my $dbh = C4::Context->dbh;
86
87     my $sth1 = $dbh->prepare(
88         qq|
89         UPDATE aqbudgets_planning SET display = 0 
90         WHERE authcat = ? 
91         AND  authvalue = ? |
92     );
93     foreach my $authvalue (@hide_cols) {
94 #        $sth1->{TraceLevel} = 3;
95         $sth1->execute(  $authcat, $authvalue );
96     }
97 }
98
99 sub GetCols {
100     my ( $authcat, $authvalue ) = @_;
101
102     my $dbh = C4::Context->dbh;
103     my $sth = $dbh->prepare(
104         qq|
105         SELECT count(display) as cnt from aqbudgets_planning
106         WHERE  authcat = ? 
107         AND authvalue = ? and display  = 0   |
108     );
109
110 #    $sth->{TraceLevel} = 3;
111     $sth->execute( $authcat, $authvalue );
112     my $res  = $sth->fetchrow_hashref;
113
114     return  $res->{cnt} > 0 ? 0: 1
115
116 }
117
118 sub CheckBudgetParentPerm {
119     my ( $budget, $borrower_id ) = @_;
120     my $depth = $budget->{depth};
121     my $parent_id = $budget->{budget_parent_id};
122     while ($depth) {
123         my $parent = GetBudget($parent_id);
124         $parent_id = $parent->{budget_parent_id};
125         if ( $parent->{budget_owner_id} == $borrower_id ) {
126             return 1;
127         }
128         $depth--
129     }
130     return 0;
131 }
132
133 sub AddBudgetPeriod {
134     my ($budgetperiod) = @_;
135     return unless($budgetperiod->{budget_period_startdate} && $budgetperiod->{budget_period_enddate});
136
137     undef $budgetperiod->{budget_period_id};
138     my $resultset = Koha::Database->new()->schema->resultset('Aqbudgetperiod');
139     return $resultset->create($budgetperiod)->id;
140 }
141 # -------------------------------------------------------------------
142 sub GetPeriodsCount {
143     my $dbh = C4::Context->dbh;
144     my $sth = $dbh->prepare("
145         SELECT COUNT(*) AS sum FROM aqbudgetperiods ");
146     $sth->execute();
147     my $res = $sth->fetchrow_hashref;
148     return $res->{'sum'};
149 }
150
151 # -------------------------------------------------------------------
152 sub CheckBudgetParent {
153     my ( $new_parent, $budget ) = @_;
154     my $new_parent_id = $new_parent->{'budget_id'};
155     my $budget_id     = $budget->{'budget_id'};
156     my $dbh           = C4::Context->dbh;
157     my $parent_id_tmp = $new_parent_id;
158
159     # check new-parent is not a child (or a child's child ;)
160     my $sth = $dbh->prepare(qq|
161         SELECT budget_parent_id FROM
162             aqbudgets where budget_id = ? | );
163     while (1) {
164         $sth->execute($parent_id_tmp);
165         my $res = $sth->fetchrow_hashref;
166         if ( $res->{'budget_parent_id'} == $budget_id ) {
167             return 1;
168         }
169         if ( not defined $res->{'budget_parent_id'} ) {
170             return 0;
171         }
172         $parent_id_tmp = $res->{'budget_parent_id'};
173     }
174 }
175
176 # -------------------------------------------------------------------
177 sub BudgetHasChildren {
178     my ( $budget_id  ) = @_;
179     my $dbh = C4::Context->dbh;
180     my $sth = $dbh->prepare(qq|
181        SELECT count(*) as sum FROM  aqbudgets
182         WHERE budget_parent_id = ?   | );
183     $sth->execute( $budget_id );
184     my $sum = $sth->fetchrow_hashref;
185     return $sum->{'sum'};
186 }
187
188 sub GetBudgetChildren {
189     my ( $budget_id ) = @_;
190     my $dbh = C4::Context->dbh;
191     return $dbh->selectall_arrayref(q|
192        SELECT  * FROM  aqbudgets
193         WHERE budget_parent_id = ?
194     |, { Slice => {} }, $budget_id );
195 }
196
197 sub SetOwnerToFundHierarchy {
198     my ( $budget_id, $borrowernumber ) = @_;
199
200     my $budget = GetBudget( $budget_id );
201     $budget->{budget_owner_id} = $borrowernumber;
202     ModBudget( $budget );
203     my $children = GetBudgetChildren( $budget_id );
204     for my $child ( @$children ) {
205         SetOwnerToFundHierarchy( $child->{budget_id}, $borrowernumber );
206     }
207 }
208
209 # -------------------------------------------------------------------
210 sub GetBudgetsPlanCell {
211     my ( $cell, $period, $budget ) = @_;
212     my ($actual, $sth);
213     my $dbh = C4::Context->dbh;
214     my $roundsql = _get_rounding_sql(qq|ecost_tax_included|);
215     if ( $cell->{'authcat'} eq 'MONTHS' ) {
216         # get the actual amount
217         $sth = $dbh->prepare( qq|
218
219             SELECT SUM(| .  $roundsql . qq|) AS actual FROM aqorders
220                 WHERE    budget_id = ? AND
221                 entrydate like "$cell->{'authvalue'}%"  |
222         );
223         $sth->execute( $cell->{'budget_id'} );
224     } elsif ( $cell->{'authcat'} eq 'BRANCHES' ) {
225         # get the actual amount
226         $sth = $dbh->prepare( qq|
227
228             SELECT SUM(| . $roundsql . qq|) FROM aqorders
229                 LEFT JOIN aqorders_items
230                 ON (aqorders.ordernumber = aqorders_items.ordernumber)
231                 LEFT JOIN items
232                 ON (aqorders_items.itemnumber = items.itemnumber)
233                 WHERE budget_id = ? AND homebranch = ? |          );
234
235         $sth->execute( $cell->{'budget_id'}, $cell->{'authvalue'} );
236     } elsif ( $cell->{'authcat'} eq 'ITEMTYPES' ) {
237         # get the actual amount
238         $sth = $dbh->prepare(  qq|
239
240             SELECT SUM( | . $roundsql . qq| *  quantity) AS actual
241                 FROM aqorders JOIN biblioitems
242                 ON (biblioitems.biblionumber = aqorders.biblionumber )
243                 WHERE aqorders.budget_id = ? and itemtype  = ? |
244         );
245         $sth->execute(  $cell->{'budget_id'},
246                         $cell->{'authvalue'} );
247     }
248     # ELSE GENERIC ORDERS SORT1/SORT2 STAT COUNT.
249     else {
250         # get the actual amount
251         $sth = $dbh->prepare( qq|
252
253         SELECT  SUM(| . $roundsql . qq| * quantity) AS actual
254             FROM aqorders
255             JOIN aqbudgets ON (aqbudgets.budget_id = aqorders.budget_id )
256             WHERE  aqorders.budget_id = ? AND
257                 ((aqbudgets.sort1_authcat = ? AND sort1 =?) OR
258                 (aqbudgets.sort2_authcat = ? AND sort2 =?))    |
259         );
260         $sth->execute(  $cell->{'budget_id'},
261                         $budget->{'sort1_authcat'},
262                         $cell->{'authvalue'},
263                         $budget->{'sort2_authcat'},
264                         $cell->{'authvalue'}
265         );
266     }
267     $actual = $sth->fetchrow_array;
268
269     # get the estimated amount
270     $sth = $dbh->prepare( qq|
271
272         SELECT estimated_amount AS estimated, display FROM aqbudgets_planning
273             WHERE budget_period_id = ? AND
274                 budget_id = ? AND
275                 authvalue = ? AND
276                 authcat = ?         |
277     );
278     $sth->execute(  $cell->{'budget_period_id'},
279                     $cell->{'budget_id'},
280                     $cell->{'authvalue'},
281                     $cell->{'authcat'},
282     );
283
284
285     my $res  = $sth->fetchrow_hashref;
286   #  my $display = $res->{'display'};
287     my $estimated = $res->{'estimated'};
288
289
290     return $actual, $estimated;
291 }
292
293 # -------------------------------------------------------------------
294 sub ModBudgetPlan {
295     my ( $budget_plan, $budget_period_id, $authcat ) = @_;
296     my $dbh = C4::Context->dbh;
297     foreach my $buds (@$budget_plan) {
298         my $lines = $buds->{lines};
299         my $sth = $dbh->prepare( qq|
300                 DELETE FROM aqbudgets_planning
301                     WHERE   budget_period_id   = ? AND
302                             budget_id   = ? AND
303                             authcat            = ? |
304         );
305     #delete a aqplan line of cells, then insert new cells, 
306     # these could be UPDATES rather than DEL/INSERTS...
307         $sth->execute( $budget_period_id,  $lines->[0]{budget_id}   , $authcat );
308
309         foreach my $cell (@$lines) {
310             my $sth = $dbh->prepare( qq|
311
312                 INSERT INTO aqbudgets_planning
313                      SET   budget_id     = ?,
314                      budget_period_id  = ?,
315                      authcat          = ?,
316                      estimated_amount  = ?,
317                      authvalue       = ?  |
318             );
319             $sth->execute(
320                             $cell->{'budget_id'},
321                             $cell->{'budget_period_id'},
322                             $cell->{'authcat'},
323                             $cell->{'estimated_amount'},
324                             $cell->{'authvalue'},
325             );
326         }
327     }
328 }
329
330 # -------------------------------------------------------------------
331 sub GetBudgetSpent {
332     my ($budget_id) = @_;
333     my $dbh = C4::Context->dbh;
334     # unitprice_tax_included should always been set here
335     # we should not need to retrieve ecost_tax_included
336     my $sth = $dbh->prepare(qq|
337         SELECT SUM( | . _get_rounding_sql("COALESCE(unitprice_tax_included, ecost_tax_included)") . qq| * quantity ) AS sum FROM aqorders
338             WHERE budget_id = ? AND
339             quantityreceived > 0 AND
340             datecancellationprinted IS NULL
341     |);
342         $sth->execute($budget_id);
343     my $sum = 0 + $sth->fetchrow_array;
344
345     $sth = $dbh->prepare(qq|
346         SELECT SUM(shipmentcost) AS sum
347         FROM aqinvoices
348         WHERE shipmentcost_budgetid = ?
349     |);
350
351     $sth->execute($budget_id);
352     my ($shipmentcost_sum) = $sth->fetchrow_array;
353     $sum += $shipmentcost_sum;
354
355     my $adjustments = Koha::Acquisition::Invoice::Adjustments->search({budget_id => $budget_id, closedate => { '!=' => undef } },{ join => 'invoiceid' });
356     while ( my $adj = $adjustments->next ){
357         $sum += $adj->adjustment;
358     }
359
360         return $sum;
361 }
362
363 # -------------------------------------------------------------------
364 sub GetBudgetOrdered {
365         my ($budget_id) = @_;
366         my $dbh = C4::Context->dbh;
367         my $sth = $dbh->prepare(qq|
368         SELECT SUM(| . _get_rounding_sql(qq|ecost_tax_included|) . qq| *  quantity) AS sum FROM aqorders
369             WHERE budget_id = ? AND
370             quantityreceived = 0 AND
371             datecancellationprinted IS NULL
372     |);
373         $sth->execute($budget_id);
374     my $sum =  0 + $sth->fetchrow_array;
375
376     my $adjustments = Koha::Acquisition::Invoice::Adjustments->search({budget_id => $budget_id, encumber_open => 1, closedate => undef},{ join => 'invoiceid' });
377     while ( my $adj = $adjustments->next ){
378         $sum += $adj->adjustment;
379     }
380
381         return $sum;
382 }
383
384 =head2 GetBudgetName
385
386   my $budget_name = &GetBudgetName($budget_id);
387
388 get the budget_name for a given budget_id
389
390 =cut
391
392 sub GetBudgetName {
393     my ( $budget_id ) = @_;
394     my $dbh         = C4::Context->dbh;
395     my $sth         = $dbh->prepare(
396         qq|
397         SELECT budget_name
398         FROM aqbudgets
399         WHERE budget_id = ?
400     |);
401
402     $sth->execute($budget_id);
403     return $sth->fetchrow_array;
404 }
405
406 =head2 GetBudgetAuthCats
407
408   my $auth_cats = &GetBudgetAuthCats($budget_period_id);
409
410 Return the list of authcat for a given budget_period_id
411
412 =cut
413
414 sub GetBudgetAuthCats  {
415     my ($budget_period_id) = shift;
416     # now, populate the auth_cats_loop used in the budget planning button
417     # we must retrieve all auth values used by at least one budget
418     my $dbh = C4::Context->dbh;
419     my $sth=$dbh->prepare("SELECT sort1_authcat,sort2_authcat FROM aqbudgets WHERE budget_period_id=?");
420     $sth->execute($budget_period_id);
421     my %authcats;
422     while (my ($sort1_authcat,$sort2_authcat) = $sth->fetchrow) {
423         $authcats{$sort1_authcat}=1 if $sort1_authcat;
424         $authcats{$sort2_authcat}=1 if $sort2_authcat;
425     }
426     return [ sort keys %authcats ];
427 }
428
429 # -------------------------------------------------------------------
430 sub GetBudgetPeriods {
431         my ($filters,$orderby) = @_;
432
433     my $rs = Koha::Database->new()->schema->resultset('Aqbudgetperiod');
434     $rs = $rs->search( $filters, { order_by => $orderby } );
435     $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
436     return [ $rs->all ];
437 }
438 # -------------------------------------------------------------------
439 sub GetBudgetPeriod {
440         my ($budget_period_id) = @_;
441         my $dbh = C4::Context->dbh;
442         ## $total = number of records linked to the record that must be deleted
443         my $total = 0;
444         ## get information about the record that will be deleted
445         my $sth;
446         if ($budget_period_id) {
447                 $sth = $dbh->prepare( qq|
448               SELECT      *
449                 FROM aqbudgetperiods
450                 WHERE budget_period_id=? |
451                 );
452                 $sth->execute($budget_period_id);
453         } else {         # ACTIVE BUDGET
454                 $sth = $dbh->prepare(qq|
455                           SELECT      *
456                 FROM aqbudgetperiods
457                 WHERE budget_period_active=1 |
458                 );
459                 $sth->execute();
460         }
461         my $data = $sth->fetchrow_hashref;
462         return $data;
463 }
464
465 sub DelBudgetPeriod{
466         my ($budget_period_id) = @_;
467         my $dbh = C4::Context->dbh;
468           ; ## $total = number of records linked to the record that must be deleted
469     my $total = 0;
470
471         ## get information about the record that will be deleted
472         my $sth = $dbh->prepare(qq|
473                 DELETE 
474          FROM aqbudgetperiods
475          WHERE budget_period_id=? |
476         );
477         return $sth->execute($budget_period_id);
478 }
479
480 # -------------------------------------------------------------------
481 sub ModBudgetPeriod {
482     my ($budget_period) = @_;
483     my $result = Koha::Database->new()->schema->resultset('Aqbudgetperiod')->find($budget_period);
484     return unless($result);
485
486     $result = $result->update($budget_period);
487     return $result->in_storage;
488 }
489
490 # -------------------------------------------------------------------
491 sub GetBudgetHierarchy {
492     my ( $budget_period_id, $branchcode, $owner ) = @_;
493     my @bind_params;
494     my $dbh   = C4::Context->dbh;
495     my $query = qq|
496                     SELECT aqbudgets.*, aqbudgetperiods.budget_period_active, aqbudgetperiods.budget_period_description,
497                            b.firstname as budget_owner_firstname, b.surname as budget_owner_surname, b.borrowernumber as budget_owner_borrowernumber
498                     FROM aqbudgets 
499                     LEFT JOIN borrowers b on b.borrowernumber = aqbudgets.budget_owner_id
500                     JOIN aqbudgetperiods USING (budget_period_id)|;
501
502         my @where_strings;
503     # show only period X if requested
504     if ($budget_period_id) {
505         push @where_strings," aqbudgets.budget_period_id = ?";
506         push @bind_params, $budget_period_id;
507     }
508         # show only budgets owned by me, my branch or everyone
509     if ($owner) {
510         if ($branchcode) {
511             push @where_strings,
512             qq{ (budget_owner_id = ? OR budget_branchcode = ? OR ((budget_branchcode IS NULL or budget_branchcode="") AND (budget_owner_id IS NULL OR budget_owner_id="")))};
513             push @bind_params, ( $owner, $branchcode );
514         } else {
515             push @where_strings, ' (budget_owner_id = ? OR budget_owner_id IS NULL or budget_owner_id ="") ';
516             push @bind_params, $owner;
517         }
518     } else {
519         if ($branchcode) {
520             push @where_strings," (budget_branchcode =? or budget_branchcode is NULL OR budget_branchcode='')";
521             push @bind_params, $branchcode;
522         }
523     }
524         $query.=" WHERE ".join(' AND ', @where_strings) if @where_strings;
525         $debug && warn $query,join(",",@bind_params);
526         my $sth = $dbh->prepare($query);
527         $sth->execute(@bind_params);
528
529     my %links;
530     # create hash with budget_id has key
531     while ( my $data = $sth->fetchrow_hashref ) {
532         $links{ $data->{'budget_id'} } = $data;
533     }
534
535     # link child to parent
536     my @first_parents;
537     foreach my $budget ( sort { $a->{budget_code} cmp $b->{budget_code} } values %links ) {
538         my $child = $links{$budget->{budget_id}};
539         if ( $child->{'budget_parent_id'} ) {
540             my $parent = $links{ $child->{'budget_parent_id'} };
541             if ($parent) {
542                 unless ( $parent->{'children'} ) {
543                     # init child arrayref
544                     $parent->{'children'} = [];
545                 }
546                 # add as child
547                 push @{ $parent->{'children'} }, $child;
548             }
549         } else {
550             push @first_parents, $child;
551         }
552     }
553
554     my @sort = ();
555     foreach my $first_parent (@first_parents) {
556         _add_budget_children(\@sort, $first_parent, 0);
557     }
558
559     # Get all the budgets totals in as few queries as possible
560     my $hr_budget_spent = $dbh->selectall_hashref(q|
561         SELECT aqorders.budget_id, aqbudgets.budget_parent_id,
562                SUM( COALESCE(unitprice_tax_included, ecost_tax_included) * quantity ) AS budget_spent
563         FROM aqorders JOIN aqbudgets USING (budget_id)
564         WHERE quantityreceived > 0 AND datecancellationprinted IS NULL
565         GROUP BY budget_id, budget_parent_id
566         |, 'budget_id');
567     my $hr_budget_ordered = $dbh->selectall_hashref(q|
568         SELECT aqorders.budget_id, aqbudgets.budget_parent_id,
569                SUM(ecost_tax_included *  quantity) AS budget_ordered
570         FROM aqorders JOIN aqbudgets USING (budget_id)
571         WHERE quantityreceived = 0 AND datecancellationprinted IS NULL
572         GROUP BY budget_id, budget_parent_id
573         |, 'budget_id');
574     my $hr_budget_spent_shipment = $dbh->selectall_hashref(q|
575         SELECT shipmentcost_budgetid as budget_id,
576                SUM(shipmentcost) as shipmentcost
577         FROM aqinvoices
578         WHERE closedate IS NOT NULL
579         GROUP BY shipmentcost_budgetid
580         |, 'budget_id');
581     my $hr_budget_ordered_shipment = $dbh->selectall_hashref(q|
582         SELECT shipmentcost_budgetid as budget_id,
583                SUM(shipmentcost) as shipmentcost
584         FROM aqinvoices
585         WHERE closedate IS NULL
586         GROUP BY shipmentcost_budgetid
587         |, 'budget_id');
588     my $hr_budget_spent_adjustment = $dbh->selectall_hashref(q|
589         SELECT budget_id,
590                SUM(adjustment) as adjustments
591         FROM aqinvoice_adjustments
592         JOIN aqinvoices USING (invoiceid)
593         WHERE closedate IS NOT NULL
594         GROUP BY budget_id
595         |, 'budget_id');
596     my $hr_budget_ordered_adjustment = $dbh->selectall_hashref(q|
597         SELECT budget_id,
598                SUM(adjustment) as adjustments
599         FROM aqinvoice_adjustments
600         JOIN aqinvoices USING (invoiceid)
601         WHERE closedate IS NULL AND encumber_open = 1
602         GROUP BY budget_id
603         |, 'budget_id');
604
605
606     foreach my $budget (@sort) {
607         if ( not defined $budget->{budget_parent_id} ) {
608             _recursiveAdd( $budget, undef, $hr_budget_spent, $hr_budget_spent_shipment, $hr_budget_ordered, $hr_budget_ordered_shipment, $hr_budget_spent_adjustment, $hr_budget_ordered_adjustment );
609         }
610     }
611     return \@sort;
612 }
613
614 sub _recursiveAdd {
615     my ($budget, $parent, $hr_budget_spent, $hr_budget_spent_shipment, $hr_budget_ordered, $hr_budget_ordered_shipment, $hr_budget_spent_adjustment, $hr_budget_ordered_adjustment ) = @_;
616
617     foreach my $child (@{$budget->{children}}){
618         _recursiveAdd($child, $budget, $hr_budget_spent, $hr_budget_spent_shipment, $hr_budget_ordered, $hr_budget_ordered_shipment, $hr_budget_spent_adjustment, $hr_budget_ordered_adjustment );
619     }
620
621     $budget->{budget_spent} += $hr_budget_spent->{$budget->{budget_id}}->{budget_spent};
622     $budget->{budget_spent} += $hr_budget_spent_shipment->{$budget->{budget_id}}->{shipmentcost};
623     $budget->{budget_spent} += $hr_budget_spent_adjustment->{$budget->{budget_id}}->{adjustments};
624     $budget->{budget_ordered} += $hr_budget_ordered->{$budget->{budget_id}}->{budget_ordered};
625     $budget->{budget_ordered} += $hr_budget_ordered_shipment->{$budget->{budget_id}}->{shipmentcost};
626     $budget->{budget_ordered} += $hr_budget_ordered_adjustment->{$budget->{budget_id}}->{adjustments};
627
628     $budget->{total_spent} += $budget->{budget_spent};
629     $budget->{total_ordered} += $budget->{budget_ordered};
630
631     if ($parent) {
632         $parent->{total_spent} += $budget->{total_spent};
633         $parent->{total_ordered} += $budget->{total_ordered};
634     }
635 }
636
637 # Recursive method to add a budget and its chidren to an array
638 sub _add_budget_children {
639     my $res = shift;
640     my $budget = shift;
641     $budget->{budget_level} = shift;
642     push @$res, $budget;
643     my $children = $budget->{'children'} || [];
644     return unless @$children; # break recursivity
645     foreach my $child (@$children) {
646         _add_budget_children($res, $child, $budget->{budget_level} + 1);
647     }
648 }
649
650 # -------------------------------------------------------------------
651
652 sub AddBudget {
653     my ($budget) = @_;
654     return unless ($budget);
655
656     undef $budget->{budget_encumb} if $budget->{budget_encumb} eq '';
657     undef $budget->{budget_owner_id} if $budget->{budget_owner_id} eq '';
658     my $resultset = Koha::Database->new()->schema->resultset('Aqbudget');
659     return $resultset->create($budget)->id;
660 }
661
662 # -------------------------------------------------------------------
663 sub ModBudget {
664     my ($budget) = @_;
665     my $result = Koha::Database->new()->schema->resultset('Aqbudget')->find($budget);
666     return unless($result);
667
668     undef $budget->{budget_encumb} if $budget->{budget_encumb} eq '';
669     undef $budget->{budget_owner_id} if $budget->{budget_owner_id} eq '';
670     $result = $result->update($budget);
671     return $result->in_storage;
672 }
673
674 # -------------------------------------------------------------------
675 sub DelBudget {
676         my ($budget_id) = @_;
677         my $dbh         = C4::Context->dbh;
678         my $sth         = $dbh->prepare("delete from aqbudgets where budget_id=?");
679         my $rc          = $sth->execute($budget_id);
680         return $rc;
681 }
682
683
684 # -------------------------------------------------------------------
685
686 =head2 GetBudget
687
688   &GetBudget($budget_id);
689
690 get a specific budget
691
692 =cut
693
694 sub GetBudget {
695     my ( $budget_id ) = @_;
696     my $dbh = C4::Context->dbh;
697     my $query = "
698         SELECT *
699         FROM   aqbudgets
700         WHERE  budget_id=?
701         ";
702     my $sth = $dbh->prepare($query);
703     $sth->execute( $budget_id );
704     my $result = $sth->fetchrow_hashref;
705     return $result;
706 }
707
708 # -------------------------------------------------------------------
709
710 =head2 GetBudgetByOrderNumber
711
712   &GetBudgetByOrderNumber($ordernumber);
713
714 get a specific budget by order number
715
716 =cut
717
718 sub GetBudgetByOrderNumber {
719     my ( $ordernumber ) = @_;
720     my $dbh = C4::Context->dbh;
721     my $query = "
722         SELECT aqbudgets.*
723         FROM   aqbudgets, aqorders
724         WHERE  ordernumber=?
725         AND    aqorders.budget_id = aqbudgets.budget_id
726         ";
727     my $sth = $dbh->prepare($query);
728     $sth->execute( $ordernumber );
729     my $result = $sth->fetchrow_hashref;
730     return $result;
731 }
732
733 =head2 GetBudgetReport
734
735   &GetBudgetReport( [$budget_id] );
736
737 Get all orders for a specific budget, without cancelled orders.
738
739 Returns an array of hashrefs.
740
741 =cut
742
743 # --------------------------------------------------------------------
744 sub GetBudgetReport {
745     my ( $budget_id ) = @_;
746     my $dbh = C4::Context->dbh;
747     my $query = '
748         SELECT o.*, b.budget_name
749         FROM   aqbudgets b
750         INNER JOIN aqorders o
751         ON b.budget_id = o.budget_id
752         WHERE  b.budget_id=?
753         AND (o.orderstatus != "cancelled")
754         ORDER BY b.budget_name';
755
756     my $sth = $dbh->prepare($query);
757     $sth->execute( $budget_id );
758
759     my @results = ();
760     while ( my $data = $sth->fetchrow_hashref ) {
761         push( @results, $data );
762     }
763     return @results;
764 }
765
766 =head2 GetBudgetsByActivity
767
768   &GetBudgetsByActivity( $budget_period_active );
769
770 Get all active or inactive budgets, depending of the value
771 of the parameter.
772
773 1 = active
774 0 = inactive
775
776 =cut
777
778 # --------------------------------------------------------------------
779 sub GetBudgetsByActivity {
780     my ( $budget_period_active ) = @_;
781     my $dbh = C4::Context->dbh;
782     my $query = "
783         SELECT DISTINCT b.*
784         FROM   aqbudgetperiods bp
785         INNER JOIN aqbudgets b
786         ON bp.budget_period_id = b.budget_period_id
787         WHERE  bp.budget_period_active=?
788         ";
789     my $sth = $dbh->prepare($query);
790     $sth->execute( $budget_period_active );
791     my @results = ();
792     while ( my $data = $sth->fetchrow_hashref ) {
793         push( @results, $data );
794     }
795     return @results;
796 }
797 # --------------------------------------------------------------------
798
799 =head2 GetBudgetsReport
800
801   &GetBudgetsReport( [$activity] );
802
803 Get all but cancelled orders for all funds.
804
805 If the optionnal activity parameter is passed, returns orders for active/inactive budgets only.
806
807 active = 1
808 inactive = 0
809
810 Returns an array of hashrefs.
811
812 =cut
813
814 sub GetBudgetsReport {
815     my ($activity) = @_;
816     my $dbh = C4::Context->dbh;
817     my $query = '
818         SELECT o.*, b.budget_name
819         FROM   aqbudgetperiods bp
820         INNER JOIN aqbudgets b
821         ON bp.budget_period_id = b.budget_period_id
822         INNER JOIN aqorders o
823         ON b.budget_id = o.budget_id ';
824     if($activity ne ''){
825         $query .= 'WHERE  bp.budget_period_active=? ';
826     }
827     $query .= 'AND (o.orderstatus != "cancelled")
828                ORDER BY b.budget_name';
829
830     my $sth = $dbh->prepare($query);
831     if($activity ne ''){
832         $sth->execute($activity);
833     }
834     else{
835         $sth->execute;
836     }
837     my @results = ();
838     while ( my $data = $sth->fetchrow_hashref ) {
839         push( @results, $data );
840     }
841     return @results;
842 }
843
844 =head2 GetBudgetByCode
845
846     my $budget = &GetBudgetByCode($budget_code);
847
848 Retrieve all aqbudgets fields as a hashref for the budget that has
849 given budget_code
850
851 =cut
852
853 sub GetBudgetByCode {
854     my ( $budget_code ) = @_;
855
856     my $dbh = C4::Context->dbh;
857     my $query = qq{
858         SELECT aqbudgets.*
859         FROM aqbudgets
860         JOIN aqbudgetperiods USING (budget_period_id)
861         WHERE budget_code = ?
862         ORDER BY budget_period_active DESC, budget_id DESC
863         LIMIT 1
864     };
865     my $sth = $dbh->prepare( $query );
866     $sth->execute( $budget_code );
867     return $sth->fetchrow_hashref;
868 }
869
870 =head2 GetBudgetHierarchySpent
871
872   my $spent = GetBudgetHierarchySpent( $budget_id );
873
874 Gets the total spent of the level and sublevels of $budget_id
875
876 =cut
877
878 sub GetBudgetHierarchySpent {
879     my ( $budget_id ) = @_;
880     my $dbh = C4::Context->dbh;
881     my $children_ids = $dbh->selectcol_arrayref(q|
882         SELECT budget_id
883         FROM   aqbudgets
884         WHERE  budget_parent_id = ?
885     |, {}, $budget_id );
886
887     my $total_spent = GetBudgetSpent( $budget_id );
888     for my $child_id ( @$children_ids ) {
889         $total_spent += GetBudgetHierarchySpent( $child_id );
890     }
891     return $total_spent;
892 }
893
894 =head2 GetBudgetHierarchyOrdered
895
896   my $ordered = GetBudgetHierarchyOrdered( $budget_id );
897
898 Gets the total ordered of the level and sublevels of $budget_id
899
900 =cut
901
902 sub GetBudgetHierarchyOrdered {
903     my ( $budget_id ) = @_;
904     my $dbh = C4::Context->dbh;
905     my $children_ids = $dbh->selectcol_arrayref(q|
906         SELECT budget_id
907         FROM   aqbudgets
908         WHERE  budget_parent_id = ?
909     |, {}, $budget_id );
910
911     my $total_ordered = GetBudgetOrdered( $budget_id );
912     for my $child_id ( @$children_ids ) {
913         $total_ordered += GetBudgetHierarchyOrdered( $child_id );
914     }
915     return $total_ordered;
916 }
917
918 =head2 GetBudgets
919
920   &GetBudgets($filter, $order_by);
921
922 gets all budgets
923
924 =cut
925
926 # -------------------------------------------------------------------
927 sub GetBudgets {
928     my ($filters, $orderby) = @_;
929     $orderby = 'budget_name' unless($orderby);
930
931     my $rs = Koha::Database->new()->schema->resultset('Aqbudget');
932     $rs = $rs->search( $filters, { order_by => $orderby } );
933     $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
934     return [ $rs->all  ];
935 }
936
937 =head2 GetBudgetUsers
938
939     my @borrowernumbers = &GetBudgetUsers($budget_id);
940
941 Return the list of borrowernumbers linked to a budget
942
943 =cut
944
945 sub GetBudgetUsers {
946     my ($budget_id) = @_;
947
948     my $dbh = C4::Context->dbh;
949     my $query = qq{
950         SELECT borrowernumber
951         FROM aqbudgetborrowers
952         WHERE budget_id = ?
953     };
954     my $sth = $dbh->prepare($query);
955     $sth->execute($budget_id);
956
957     my @borrowernumbers;
958     while (my ($borrowernumber) = $sth->fetchrow_array) {
959         push @borrowernumbers, $borrowernumber
960     }
961
962     return @borrowernumbers;
963 }
964
965 =head2 ModBudgetUsers
966
967     &ModBudgetUsers($budget_id, @borrowernumbers);
968
969 Modify the list of borrowernumbers linked to a budget
970
971 =cut
972
973 sub ModBudgetUsers {
974     my ($budget_id, @budget_users_id) = @_;
975
976     return unless $budget_id;
977
978     my $dbh = C4::Context->dbh;
979     my $query = "DELETE FROM aqbudgetborrowers WHERE budget_id = ?";
980     my $sth = $dbh->prepare($query);
981     $sth->execute($budget_id);
982
983     $query = qq{
984         INSERT INTO aqbudgetborrowers (budget_id, borrowernumber)
985         VALUES (?,?)
986     };
987     $sth = $dbh->prepare($query);
988     foreach my $borrowernumber (@budget_users_id) {
989         next unless $borrowernumber;
990         $sth->execute($budget_id, $borrowernumber);
991     }
992 }
993
994 sub CanUserUseBudget {
995     my ($borrower, $budget, $userflags) = @_;
996
997     if (not ref $borrower) {
998         $borrower = Koha::Patrons->find( $borrower );
999         return 0 unless $borrower;
1000         $borrower = $borrower->unblessed;
1001     }
1002     if (not ref $budget) {
1003         $budget = GetBudget($budget);
1004     }
1005
1006     return 0 unless ($borrower and $budget);
1007
1008     if (not defined $userflags) {
1009         $userflags = C4::Auth::getuserflags($borrower->{flags},
1010             $borrower->{userid});
1011     }
1012
1013     unless ($userflags->{superlibrarian}
1014     || (ref $userflags->{acquisition}
1015         && $userflags->{acquisition}->{budget_manage_all})
1016     || (!ref $userflags->{acquisition} && $userflags->{acquisition}))
1017     {
1018         if (not exists $userflags->{acquisition}) {
1019             return 0;
1020         }
1021
1022         if (!ref $userflags->{acquisition} && !$userflags->{acquisition}) {
1023             return 0;
1024         }
1025
1026         # Budget restricted to owner
1027         if ( $budget->{budget_permission} == 1 ) {
1028             if (    $budget->{budget_owner_id}
1029                 and $budget->{budget_owner_id} != $borrower->{borrowernumber} )
1030             {
1031                 return 0;
1032             }
1033         }
1034
1035         # Budget restricted to owner, users and library
1036         elsif ( $budget->{budget_permission} == 2 ) {
1037             my @budget_users = GetBudgetUsers( $budget->{budget_id} );
1038
1039             if (
1040                 (
1041                         $budget->{budget_owner_id}
1042                     and $budget->{budget_owner_id} !=
1043                     $borrower->{borrowernumber}
1044                     or not $budget->{budget_owner_id}
1045                 )
1046                 and ( 0 == grep { $borrower->{borrowernumber} == $_ }
1047                     @budget_users )
1048                 and defined $budget->{budget_branchcode}
1049                 and $budget->{budget_branchcode} ne
1050                 C4::Context->userenv->{branch}
1051               )
1052             {
1053                 return 0;
1054             }
1055         }
1056
1057         # Budget restricted to owner and users
1058         elsif ( $budget->{budget_permission} == 3 ) {
1059             my @budget_users = GetBudgetUsers( $budget->{budget_id} );
1060             if (
1061                 (
1062                         $budget->{budget_owner_id}
1063                     and $budget->{budget_owner_id} !=
1064                     $borrower->{borrowernumber}
1065                     or not $budget->{budget_owner_id}
1066                 )
1067                 and ( 0 == grep { $borrower->{borrowernumber} == $_ }
1068                     @budget_users )
1069               )
1070             {
1071                 return 0;
1072             }
1073         }
1074     }
1075
1076     return 1;
1077 }
1078
1079 sub CanUserModifyBudget {
1080     my ($borrower, $budget, $userflags) = @_;
1081
1082     if (not ref $borrower) {
1083         $borrower = Koha::Patrons->find( $borrower );
1084         return 0 unless $borrower;
1085         $borrower = $borrower->unblessed;
1086     }
1087     if (not ref $budget) {
1088         $budget = GetBudget($budget);
1089     }
1090
1091     return 0 unless ($borrower and $budget);
1092
1093     if (not defined $userflags) {
1094         $userflags = C4::Auth::getuserflags($borrower->{flags},
1095             $borrower->{userid});
1096     }
1097
1098     unless ($userflags->{superlibrarian}
1099     || (ref $userflags->{acquisition}
1100         && $userflags->{acquisition}->{budget_manage_all})
1101     || (!ref $userflags->{acquisition} && $userflags->{acquisition}))
1102     {
1103         if (!CanUserUseBudget($borrower, $budget, $userflags)) {
1104             return 0;
1105         }
1106
1107         if (ref $userflags->{acquisition}
1108         && !$userflags->{acquisition}->{budget_modify}) {
1109             return 0;
1110         }
1111     }
1112
1113     return 1;
1114 }
1115
1116 sub _round {
1117     my ($value, $increment) = @_;
1118
1119     if ($increment && $increment != 0) {
1120         $value = int($value / $increment) * $increment;
1121     }
1122
1123     return $value;
1124 }
1125
1126 =head2 CloneBudgetPeriod
1127
1128   my $new_budget_period_id = CloneBudgetPeriod({
1129     budget_period_id => $budget_period_id,
1130     budget_period_startdate => $budget_period_startdate,
1131     budget_period_enddate   => $budget_period_enddate,
1132     mark_original_budget_as_inactive => 1n
1133     reset_all_budgets => 1,
1134   });
1135
1136 Clone a budget period with all budgets.
1137 If the mark_origin_budget_as_inactive is set (0 by default),
1138 the original budget will be marked as inactive.
1139
1140 If the reset_all_budgets is set (0 by default), all budget (fund)
1141 amounts will be reset.
1142
1143 =cut
1144
1145 sub CloneBudgetPeriod {
1146     my ($params)                  = @_;
1147     my $budget_period_id          = $params->{budget_period_id};
1148     my $budget_period_startdate   = $params->{budget_period_startdate};
1149     my $budget_period_enddate     = $params->{budget_period_enddate};
1150     my $budget_period_description = $params->{budget_period_description};
1151     my $amount_change_percentage  = $params->{amount_change_percentage};
1152     my $amount_change_round_increment = $params->{amount_change_round_increment};
1153     my $mark_original_budget_as_inactive =
1154       $params->{mark_original_budget_as_inactive} || 0;
1155     my $reset_all_budgets = $params->{reset_all_budgets} || 0;
1156
1157     my $budget_period = GetBudgetPeriod($budget_period_id);
1158
1159     $budget_period->{budget_period_startdate}   = $budget_period_startdate;
1160     $budget_period->{budget_period_enddate}     = $budget_period_enddate;
1161     $budget_period->{budget_period_description} = $budget_period_description;
1162     # The new budget (budget_period) should be active by default
1163     $budget_period->{budget_period_active}    = 1;
1164
1165     if ($amount_change_percentage) {
1166         my $total = $budget_period->{budget_period_total};
1167         $total += $total * $amount_change_percentage / 100;
1168         $total = _round($total, $amount_change_round_increment);
1169         $budget_period->{budget_period_total} = $total;
1170     }
1171
1172     my $original_budget_period_id = $budget_period->{budget_period_id};
1173     delete $budget_period->{budget_period_id};
1174     my $new_budget_period_id = AddBudgetPeriod( $budget_period );
1175
1176     my $budgets = GetBudgetHierarchy($budget_period_id);
1177     CloneBudgetHierarchy(
1178         {
1179             budgets              => $budgets,
1180             new_budget_period_id => $new_budget_period_id
1181         }
1182     );
1183
1184     if ($mark_original_budget_as_inactive) {
1185         ModBudgetPeriod(
1186             {
1187                 budget_period_id     => $budget_period_id,
1188                 budget_period_active => 0,
1189             }
1190         );
1191     }
1192
1193     if ( $reset_all_budgets ) {
1194         my $budgets = GetBudgets({ budget_period_id => $new_budget_period_id });
1195         for my $budget ( @$budgets ) {
1196             $budget->{budget_amount} = 0;
1197             ModBudget( $budget );
1198         }
1199     } elsif ($amount_change_percentage) {
1200         my $budgets = GetBudgets({ budget_period_id => $new_budget_period_id });
1201         for my $budget ( @$budgets ) {
1202             my $amount = $budget->{budget_amount};
1203             $amount += $amount * $amount_change_percentage / 100;
1204             $amount = _round($amount, $amount_change_round_increment);
1205             $budget->{budget_amount} = $amount;
1206             ModBudget( $budget );
1207         }
1208     }
1209
1210     return $new_budget_period_id;
1211 }
1212
1213 =head2 CloneBudgetHierarchy
1214
1215   CloneBudgetHierarchy({
1216     budgets => $budgets,
1217     new_budget_period_id => $new_budget_period_id;
1218   });
1219
1220 Clone a budget hierarchy.
1221
1222 =cut
1223
1224 sub CloneBudgetHierarchy {
1225     my ($params)             = @_;
1226     my $budgets              = $params->{budgets};
1227     my $new_budget_period_id = $params->{new_budget_period_id};
1228     next unless @$budgets or $new_budget_period_id;
1229
1230     my $children_of   = $params->{children_of};
1231     my $new_parent_id = $params->{new_parent_id};
1232
1233     my @first_level_budgets =
1234       ( not defined $children_of )
1235       ? map { ( not $_->{budget_parent_id} )             ? $_ : () } @$budgets
1236       : map { ( $_->{budget_parent_id} == $children_of ) ? $_ : () } @$budgets;
1237
1238     # get only the columns of aqbudgets
1239     my @columns = Koha::Database->new()->schema->source('Aqbudget')->columns;
1240
1241     for my $budget ( sort { $a->{budget_id} <=> $b->{budget_id} }
1242         @first_level_budgets )
1243     {
1244
1245         my $tidy_budget =
1246           { map { join( ' ', @columns ) =~ /$_/ ? ( $_ => $budget->{$_} ) : () }
1247               keys %$budget };
1248         delete $tidy_budget->{timestamp};
1249         my $new_budget_id = AddBudget(
1250             {
1251                 %$tidy_budget,
1252                 budget_id        => undef,
1253                 budget_parent_id => $new_parent_id,
1254                 budget_period_id => $new_budget_period_id
1255             }
1256         );
1257         CloneBudgetHierarchy(
1258             {
1259                 budgets              => $budgets,
1260                 new_budget_period_id => $new_budget_period_id,
1261                 children_of          => $budget->{budget_id},
1262                 new_parent_id        => $new_budget_id
1263             }
1264         );
1265     }
1266 }
1267
1268 =head2 MoveOrders
1269
1270   my $report = MoveOrders({
1271     from_budget_period_id => $from_budget_period_id,
1272     to_budget_period_id   => $to_budget_period_id,
1273   });
1274
1275 Move orders from one budget period to another.
1276
1277 =cut
1278
1279 sub MoveOrders {
1280     my ($params)              = @_;
1281     my $from_budget_period_id = $params->{from_budget_period_id};
1282     my $to_budget_period_id   = $params->{to_budget_period_id};
1283     my $move_remaining_unspent = $params->{move_remaining_unspent};
1284     return
1285       if not $from_budget_period_id
1286           or not $to_budget_period_id
1287           or $from_budget_period_id == $to_budget_period_id;
1288
1289     # Can't move orders to an inactive budget (budgetperiod)
1290     my $budget_period = GetBudgetPeriod($to_budget_period_id);
1291     return unless $budget_period->{budget_period_active};
1292
1293     my @report;
1294     my $dbh     = C4::Context->dbh;
1295     my $sth_update_aqorders = $dbh->prepare(
1296         q|
1297             UPDATE aqorders
1298             SET budget_id = ?
1299             WHERE ordernumber = ?
1300         |
1301     );
1302     my $sth_update_budget_amount = $dbh->prepare(
1303         q|
1304             UPDATE aqbudgets
1305             SET budget_amount = ?
1306             WHERE budget_id = ?
1307         |
1308     );
1309     my $from_budgets = GetBudgetHierarchy($from_budget_period_id);
1310     for my $from_budget (@$from_budgets) {
1311         my $new_budget_id = $dbh->selectcol_arrayref(
1312             q|
1313                 SELECT budget_id
1314                 FROM aqbudgets
1315                 WHERE budget_period_id = ?
1316                     AND budget_code = ?
1317             |, {}, $to_budget_period_id, $from_budget->{budget_code}
1318         );
1319         $new_budget_id = $new_budget_id->[0];
1320         my $new_budget = GetBudget( $new_budget_id );
1321         unless ( $new_budget ) {
1322             push @report,
1323               {
1324                 moved       => 0,
1325                 budget      => $from_budget,
1326                 error       => 'budget_code_not_exists',
1327               };
1328             next;
1329         }
1330         my $orders_to_move = C4::Acquisition::SearchOrders(
1331             {
1332                 budget_id => $from_budget->{budget_id},
1333                 pending   => 1,
1334             }
1335         );
1336
1337         my @orders_moved;
1338         for my $order (@$orders_to_move) {
1339             $sth_update_aqorders->execute( $new_budget->{budget_id}, $order->{ordernumber} );
1340             push @orders_moved, $order;
1341         }
1342
1343         my $unspent_moved = 0;
1344         if ($move_remaining_unspent) {
1345             my $spent   = GetBudgetHierarchySpent( $from_budget->{budget_id} );
1346             my $unspent = $from_budget->{budget_amount} - $spent;
1347             my $new_budget_amount = $new_budget->{budget_amount};
1348             if ( $unspent > 0 ) {
1349                 $new_budget_amount += $unspent;
1350                 $unspent_moved = $unspent;
1351             }
1352             $new_budget->{budget_amount} = $new_budget_amount;
1353             $sth_update_budget_amount->execute( $new_budget_amount,
1354                 $new_budget->{budget_id} );
1355         }
1356
1357         push @report,
1358           {
1359             budget        => $new_budget,
1360             orders_moved  => \@orders_moved,
1361             moved         => 1,
1362             unspent_moved => $unspent_moved,
1363           };
1364     }
1365     return \@report;
1366 }
1367
1368 =head1 INTERNAL FUNCTIONS
1369
1370 =cut
1371
1372 =head3 _get_rounding_sql
1373
1374     $rounding_sql = _get_rounding_sql("mysql_variable_to_round_string");
1375
1376 returns the correct SQL routine based on OrderPriceRounding system preference.
1377
1378 =cut
1379
1380 sub _get_rounding_sql {
1381     my $to_round = shift;
1382     my $rounding_pref = C4::Context->preference('OrderPriceRounding');
1383     if   ($rounding_pref eq 'nearest_cent') { return "CAST($to_round*100 AS INTEGER)/100"; }
1384     else { return "$to_round"; }
1385 }
1386
1387 END { }    # module clean-up code here (global destructor)
1388
1389 1;
1390 __END__
1391
1392 =head1 AUTHOR
1393
1394 Koha Development Team <http://koha-community.org/>
1395
1396 =cut