Passed
Push — master ( 37b02e...ebbbe1 )
by James
08:59
created

BudgetController   D

Complexity

Total Complexity 60

Size/Duplication

Total Lines 590
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 60
dl 0
loc 590
rs 4.2857
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B budgetLimit() 0 34 4
B expenseExpense() 0 37 5
C budget() 0 48 7
A getCategoryNames() 0 15 3
B getExpensesForBudget() 0 23 6
A __construct() 0 10 1
B frontpage() 0 44 5
A getAccountNames() 0 15 3
A spentInPeriodWithout() 0 15 2
B expenseAsset() 0 36 5
B expenseCategory() 0 38 5
B spentInPeriodMulti() 0 40 6
A getBudgetedInPeriod() 0 17 2
B periodNoBudget() 0 27 3
B period() 0 33 3

How to fix   Complexity   

Complex Class

Complex classes like BudgetController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BudgetController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * BudgetController.php
4
 * Copyright (c) 2017 [email protected]
5
 *
6
 * This file is part of Firefly III.
7
 *
8
 * Firefly III is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * Firefly III is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Http\Controllers\Chart;
24
25
use Carbon\Carbon;
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
28
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
29
use FireflyIII\Http\Controllers\Controller;
30
use FireflyIII\Models\AccountType;
31
use FireflyIII\Models\Budget;
32
use FireflyIII\Models\BudgetLimit;
33
use FireflyIII\Models\Transaction;
34
use FireflyIII\Models\TransactionType;
35
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
36
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
37
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
38
use FireflyIII\Support\CacheProperties;
39
use Illuminate\Support\Collection;
40
use Steam;
41
42
/**
43
 * Class BudgetController.
44
 *
45
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // can't realy be helped.
46
 */
47
class BudgetController extends Controller
48
{
49
    /** @var GeneratorInterface */
50
    protected $generator;
51
52
    /** @var BudgetRepositoryInterface */
53
    protected $repository;
54
55
    /**
56
     * BudgetController constructor.
57
     */
58
    public function __construct()
59
    {
60
        parent::__construct();
61
62
        $this->middleware(
63
            function ($request, $next) {
64
                $this->generator  = app(GeneratorInterface::class);
65
                $this->repository = app(BudgetRepositoryInterface::class);
66
67
                return $next($request);
68
            }
69
        );
70
    }
71
72
    /**
73
     * @param Budget $budget
74
     *
75
     * @return \Symfony\Component\HttpFoundation\Response
76
     */
77
    public function budget(Budget $budget)
78
    {
79
        $start = $this->repository->firstUseDate($budget);
80
        $end   = session('end', new Carbon);
81
        $cache = new CacheProperties();
82
        $cache->addProperty($start);
83
        $cache->addProperty($end);
84
        $cache->addProperty('chart.budget.budget');
85
        $cache->addProperty($budget->id);
86
87
        if ($cache->has()) {
88
            return response()->json($cache->get()); // @codeCoverageIgnore
89
        }
90
91
        // depending on diff, do something with range of chart.
92
        $step   = '1D';
93
        $months = $start->diffInMonths($end);
94
        if ($months > 3) {
95
            $step = '1W';
96
        }
97
        if ($months > 24) {
98
            $step = '1M';
99
        }
100
        if ($months > 60) {
101
            $step = '1Y'; // @codeCoverageIgnore
102
        }
103
        $budgetCollection = new Collection([$budget]);
104
        $chartData        = [];
105
        $current          = clone $start;
106
        $current          = app('navigation')->startOfPeriod($current, $step);
0 ignored issues
show
Bug introduced by James Cole
The method startOfPeriod() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

106
        $current          = app('navigation')->/** @scrutinizer ignore-call */ startOfPeriod($current, $step);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
107
108
        while ($end >= $current) {
109
            $currentEnd = app('navigation')->endOfPeriod($current, $step);
0 ignored issues
show
Bug introduced by James Cole
The method endOfPeriod() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

109
            $currentEnd = app('navigation')->/** @scrutinizer ignore-call */ endOfPeriod($current, $step);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
110
            if ($step === '1Y') {
111
                $currentEnd->subDay(); // @codeCoverageIgnore
112
            }
113
            $spent             = $this->repository->spentInPeriod($budgetCollection, new Collection, $current, $currentEnd);
114
            $label             = app('navigation')->periodShow($current, $step);
0 ignored issues
show
Bug introduced by James Cole
The method periodShow() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
            $label             = app('navigation')->/** @scrutinizer ignore-call */ periodShow($current, $step);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
115
            $chartData[$label] = (float)bcmul($spent, '-1');
116
            $current           = clone $currentEnd;
117
            $current->addDay();
118
        }
119
120
        $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData);
121
122
        $cache->store($data);
123
124
        return response()->json($data);
125
    }
126
127
    /**
128
     * Shows the amount left in a specific budget limit.
129
     *
130
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
131
     *
132
     * @param Budget      $budget
133
     * @param BudgetLimit $budgetLimit
134
     *
135
     * @return \Symfony\Component\HttpFoundation\Response
136
     *
137
     * @throws FireflyException
138
     */
139
    public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit)
140
    {
141
        if ($budgetLimit->budget->id !== $budget->id) {
142
            throw new FireflyException('This budget limit is not part of this budget.');
143
        }
144
145
        $start = clone $budgetLimit->start_date;
0 ignored issues
show
Bug introduced by James Cole
The property start_date does not seem to exist on FireflyIII\Models\BudgetLimit. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
146
        $end   = clone $budgetLimit->end_date;
147
        $cache = new CacheProperties();
148
        $cache->addProperty($start);
149
        $cache->addProperty($end);
150
        $cache->addProperty('chart.budget.budget.limit');
151
        $cache->addProperty($budgetLimit->id);
152
        $cache->addProperty($budget->id);
153
154
        if ($cache->has()) {
155
            return response()->json($cache->get()); // @codeCoverageIgnore
156
        }
157
158
        $entries          = [];
159
        $amount           = $budgetLimit->amount;
0 ignored issues
show
Bug introduced by James Cole
The property amount does not seem to exist on FireflyIII\Models\BudgetLimit. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
160
        $budgetCollection = new Collection([$budget]);
161
        while ($start <= $end) {
162
            $spent            = $this->repository->spentInPeriod($budgetCollection, new Collection, $start, $start);
163
            $amount           = bcadd($amount, $spent);
164
            $format           = $start->formatLocalized((string)trans('config.month_and_day'));
165
            $entries[$format] = $amount;
166
167
            $start->addDay();
168
        }
169
        $data = $this->generator->singleSet((string)trans('firefly.left'), $entries);
170
        $cache->store($data);
171
172
        return response()->json($data);
173
    }
174
175
    /**
176
     * @param Budget           $budget
177
     * @param BudgetLimit|null $budgetLimit
178
     *
179
     * @return \Illuminate\Http\JsonResponse
180
     */
181
    public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit)
182
    {
183
        $cache = new CacheProperties;
184
        $cache->addProperty($budget->id);
185
        $cache->addProperty($budgetLimit->id ?? 0);
186
        $cache->addProperty('chart.budget.expense-asset');
187
        if ($cache->has()) {
188
            return response()->json($cache->get()); // @codeCoverageIgnore
189
        }
190
191
        /** @var JournalCollectorInterface $collector */
192
        $collector = app(JournalCollectorInterface::class);
193
        $collector->setAllAssetAccounts()->setBudget($budget);
194
        if (null !== $budgetLimit->id) {
195
            $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
0 ignored issues
show
Bug introduced by James Cole
The property start_date does not seem to exist on FireflyIII\Models\BudgetLimit. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by James Cole
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

195
            $collector->setRange($budgetLimit->start_date, /** @scrutinizer ignore-type */ $budgetLimit->end_date);
Loading history...
196
        }
197
198
        $transactions = $collector->getJournals();
199
        $result       = [];
200
        $chartData    = [];
201
        /** @var Transaction $transaction */
202
        foreach ($transactions as $transaction) {
203
            $assetId          = (int)$transaction->account_id;
204
            $result[$assetId] = $result[$assetId] ?? '0';
205
            $result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]);
206
        }
207
208
        $names = $this->getAccountNames(array_keys($result));
209
        foreach ($result as $assetId => $amount) {
210
            $chartData[$names[$assetId]] = $amount;
211
        }
212
213
        $data = $this->generator->pieChart($chartData);
214
        $cache->store($data);
215
216
        return response()->json($data);
217
    }
218
219
    /**
220
     * @param Budget           $budget
221
     * @param BudgetLimit|null $budgetLimit
222
     *
223
     * @return \Illuminate\Http\JsonResponse
224
     */
225
    public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit)
226
    {
227
        $cache = new CacheProperties;
228
        $cache->addProperty($budget->id);
229
        $cache->addProperty($budgetLimit->id ?? 0);
230
        $cache->addProperty('chart.budget.expense-category');
231
        if ($cache->has()) {
232
            return response()->json($cache->get()); // @codeCoverageIgnore
233
        }
234
235
        /** @var JournalCollectorInterface $collector */
236
        $collector = app(JournalCollectorInterface::class);
237
        $collector->setAllAssetAccounts()->setBudget($budget)->withCategoryInformation();
238
        if (null !== $budgetLimit->id) {
239
            $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
0 ignored issues
show
Bug introduced by James Cole
The property start_date does not seem to exist on FireflyIII\Models\BudgetLimit. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by James Cole
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

239
            $collector->setRange($budgetLimit->start_date, /** @scrutinizer ignore-type */ $budgetLimit->end_date);
Loading history...
240
        }
241
242
        $transactions = $collector->getJournals();
243
        $result       = [];
244
        $chartData    = [];
245
        /** @var Transaction $transaction */
246
        foreach ($transactions as $transaction) {
247
            $jrnlCatId           = (int)$transaction->transaction_journal_category_id;
248
            $transCatId          = (int)$transaction->transaction_category_id;
249
            $categoryId          = max($jrnlCatId, $transCatId);
250
            $result[$categoryId] = $result[$categoryId] ?? '0';
251
            $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]);
252
        }
253
254
        $names = $this->getCategoryNames(array_keys($result));
255
        foreach ($result as $categoryId => $amount) {
256
            $chartData[$names[$categoryId]] = $amount;
257
        }
258
259
        $data = $this->generator->pieChart($chartData);
260
        $cache->store($data);
261
262
        return response()->json($data);
263
    }
264
265
    /**
266
     * @param Budget           $budget
267
     * @param BudgetLimit|null $budgetLimit
268
     *
269
     * @return \Illuminate\Http\JsonResponse
270
     */
271
    public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit)
272
    {
273
        $cache = new CacheProperties;
274
        $cache->addProperty($budget->id);
275
        $cache->addProperty($budgetLimit->id ?? 0);
276
        $cache->addProperty('chart.budget.expense-expense');
277
        if ($cache->has()) {
278
            return response()->json($cache->get()); // @codeCoverageIgnore
279
        }
280
281
        /** @var JournalCollectorInterface $collector */
282
        $collector = app(JournalCollectorInterface::class);
283
        $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount();
284
        if (null !== $budgetLimit->id) {
285
            $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date);
0 ignored issues
show
Bug introduced by James Cole
The property start_date does not seem to exist on FireflyIII\Models\BudgetLimit. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by James Cole
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

285
            $collector->setRange($budgetLimit->start_date, /** @scrutinizer ignore-type */ $budgetLimit->end_date);
Loading history...
286
        }
287
288
        $transactions = $collector->getJournals();
289
        $result       = [];
290
        $chartData    = [];
291
        /** @var Transaction $transaction */
292
        foreach ($transactions as $transaction) {
293
            $opposingId          = (int)$transaction->opposing_account_id;
294
            $result[$opposingId] = $result[$opposingId] ?? '0';
295
            $result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]);
296
        }
297
298
        $names = $this->getAccountNames(array_keys($result));
299
        foreach ($result as $opposingId => $amount) {
300
            $name             = $names[$opposingId] ?? 'no name';
301
            $chartData[$name] = $amount;
302
        }
303
304
        $data = $this->generator->pieChart($chartData);
305
        $cache->store($data);
306
307
        return response()->json($data);
308
    }
309
310
    /**
311
     * Shows a budget list with spent/left/overspent.
312
     *
313
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
314
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // 46 lines, I'm fine with this.
315
     *
316
     * @return \Symfony\Component\HttpFoundation\Response
317
     */
318
    public function frontpage()
319
    {
320
        $start = session('start', Carbon::now()->startOfMonth());
321
        $end   = session('end', Carbon::now()->endOfMonth());
322
        // chart properties for cache:
323
        $cache = new CacheProperties();
324
        $cache->addProperty($start);
325
        $cache->addProperty($end);
326
        $cache->addProperty('chart.budget.frontpage');
327
        if ($cache->has()) {
328
            return response()->json($cache->get()); // @codeCoverageIgnore
329
        }
330
        $budgets   = $this->repository->getActiveBudgets();
331
        $chartData = [
332
            ['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
333
            ['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
334
            ['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
335
        ];
336
337
        /** @var Budget $budget */
338
        foreach ($budgets as $budget) {
339
            // get relevant repetitions:
340
            $limits   = $this->repository->getBudgetLimits($budget, $start, $end);
0 ignored issues
show
Bug introduced by James Cole
It seems like $start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $start of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

340
            $limits   = $this->repository->getBudgetLimits($budget, /** @scrutinizer ignore-type */ $start, $end);
Loading history...
Bug introduced by James Cole
It seems like $end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $end of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

340
            $limits   = $this->repository->getBudgetLimits($budget, $start, /** @scrutinizer ignore-type */ $end);
Loading history...
341
            $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end);
0 ignored issues
show
Bug introduced by James Cole
It seems like $start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $start of FireflyIII\Http\Controll...:getExpensesForBudget() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
            $expenses = $this->getExpensesForBudget($limits, $budget, /** @scrutinizer ignore-type */ $start, $end);
Loading history...
Bug introduced by James Cole
It seems like $end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $end of FireflyIII\Http\Controll...:getExpensesForBudget() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
            $expenses = $this->getExpensesForBudget($limits, $budget, $start, /** @scrutinizer ignore-type */ $end);
Loading history...
342
343
            foreach ($expenses as $name => $row) {
344
                $chartData[0]['entries'][$name] = $row['spent'];
345
                $chartData[1]['entries'][$name] = $row['left'];
346
                $chartData[2]['entries'][$name] = $row['overspent'];
347
            }
348
        }
349
        // for no budget:
350
        $spent = $this->spentInPeriodWithout($start, $end);
0 ignored issues
show
Bug introduced by James Cole
It seems like $end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $end of FireflyIII\Http\Controll...:spentInPeriodWithout() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
        $spent = $this->spentInPeriodWithout($start, /** @scrutinizer ignore-type */ $end);
Loading history...
Bug introduced by James Cole
It seems like $start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager; however, parameter $start of FireflyIII\Http\Controll...:spentInPeriodWithout() does only seem to accept Carbon\Carbon, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
        $spent = $this->spentInPeriodWithout(/** @scrutinizer ignore-type */ $start, $end);
Loading history...
351
        $name  = (string)trans('firefly.no_budget');
352
        if (0 !== bccomp($spent, '0')) {
353
            $chartData[0]['entries'][$name] = bcmul($spent, '-1');
354
            $chartData[1]['entries'][$name] = '0';
355
            $chartData[2]['entries'][$name] = '0';
356
        }
357
358
        $data = $this->generator->multiSet($chartData);
359
        $cache->store($data);
360
361
        return response()->json($data);
362
    }
363
364
    /**
365
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
366
     *
367
     * @param Budget     $budget
368
     * @param Carbon     $start
369
     * @param Carbon     $end
370
     * @param Collection $accounts
371
     *
372
     * @return \Illuminate\Http\JsonResponse
373
     */
374
    public function period(Budget $budget, Collection $accounts, Carbon $start, Carbon $end)
375
    {
376
        // chart properties for cache:
377
        $cache = new CacheProperties();
378
        $cache->addProperty($start);
379
        $cache->addProperty($end);
380
        $cache->addProperty($accounts);
381
        $cache->addProperty($budget->id);
382
        $cache->addProperty('chart.budget.period');
383
        if ($cache->has()) {
384
            return response()->json($cache->get()); // @codeCoverageIgnore
385
        }
386
        $periods  = app('navigation')->listOfPeriods($start, $end);
0 ignored issues
show
Bug introduced by James Cole
The method listOfPeriods() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

386
        $periods  = app('navigation')->/** @scrutinizer ignore-call */ listOfPeriods($start, $end);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
387
        $entries  = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses
388
        $budgeted = $this->getBudgetedInPeriod($budget, $start, $end);
389
390
        // join them into one set of data:
391
        $chartData = [
392
            ['label' => (string)trans('firefly.spent'), 'type' => 'bar', 'entries' => []],
393
            ['label' => (string)trans('firefly.budgeted'), 'type' => 'bar', 'entries' => []],
394
        ];
395
396
        foreach (array_keys($periods) as $period) {
397
            $label                           = $periods[$period];
398
            $spent                           = $entries[$budget->id]['entries'][$period] ?? '0';
399
            $limit                           = (int)($budgeted[$period] ?? 0);
400
            $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12);
0 ignored issues
show
Bug introduced by James Cole
bcmul($spent, '-1') of type string is incompatible with the type double expected by parameter $val of round(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

400
            $chartData[0]['entries'][$label] = round(/** @scrutinizer ignore-type */ bcmul($spent, '-1'), 12);
Loading history...
401
            $chartData[1]['entries'][$label] = $limit;
402
        }
403
        $data = $this->generator->multiSet($chartData);
404
        $cache->store($data);
405
406
        return response()->json($data);
407
    }
408
409
    /**
410
     * @param Collection $accounts
411
     * @param Carbon     $start
412
     * @param Carbon     $end
413
     *
414
     * @return \Illuminate\Http\JsonResponse
415
     */
416
    public function periodNoBudget(Collection $accounts, Carbon $start, Carbon $end)
417
    {
418
        // chart properties for cache:
419
        $cache = new CacheProperties();
420
        $cache->addProperty($start);
421
        $cache->addProperty($end);
422
        $cache->addProperty($accounts);
423
        $cache->addProperty('chart.budget.no-budget');
424
        if ($cache->has()) {
425
            return response()->json($cache->get()); // @codeCoverageIgnore
426
        }
427
428
        // the expenses:
429
        $periods   = app('navigation')->listOfPeriods($start, $end);
430
        $entries   = $this->repository->getNoBudgetPeriodReport($accounts, $start, $end);
431
        $chartData = [];
432
433
        // join them:
434
        foreach (array_keys($periods) as $period) {
435
            $label             = $periods[$period];
436
            $spent             = $entries['entries'][$period] ?? '0';
437
            $chartData[$label] = bcmul($spent, '-1');
438
        }
439
        $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData);
440
        $cache->store($data);
441
442
        return response()->json($data);
443
    }
444
445
    /**
446
     * @param array $accountIds
447
     *
448
     * @return array
449
     */
450
    private function getAccountNames(array $accountIds): array
451
    {
452
        /** @var AccountRepositoryInterface $repository */
453
        $repository = app(AccountRepositoryInterface::class);
454
        $accounts   = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::CASH]);
455
        $grouped    = $accounts->groupBy('id')->toArray();
456
        $return     = [];
457
        foreach ($accountIds as $accountId) {
458
            if (isset($grouped[$accountId])) {
459
                $return[$accountId] = $grouped[$accountId][0]['name'];
460
            }
461
        }
462
        $return[0] = '(no name)';
463
464
        return $return;
465
    }
466
467
    /**
468
     * @param Budget $budget
469
     * @param Carbon $start
470
     * @param Carbon $end
471
     *
472
     * @return array
473
     */
474
    private function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array
475
    {
476
        $key      = app('navigation')->preferredCarbonFormat($start, $end);
0 ignored issues
show
Bug introduced by James Cole
The method preferredCarbonFormat() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

476
        $key      = app('navigation')->/** @scrutinizer ignore-call */ preferredCarbonFormat($start, $end);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
477
        $range    = app('navigation')->preferredRangeFormat($start, $end);
0 ignored issues
show
Bug introduced by James Cole
The method preferredRangeFormat() does not exist on FireflyIII\Support\Facades\Navigation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

477
        $range    = app('navigation')->/** @scrutinizer ignore-call */ preferredRangeFormat($start, $end);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
478
        $current  = clone $start;
479
        $budgeted = [];
480
        while ($current < $end) {
481
            $currentStart     = app('navigation')->startOfPeriod($current, $range);
482
            $currentEnd       = app('navigation')->endOfPeriod($current, $range);
483
            $budgetLimits     = $this->repository->getBudgetLimits($budget, $currentStart, $currentEnd);
484
            $index            = $currentStart->format($key);
485
            $budgeted[$index] = $budgetLimits->sum('amount');
486
            $currentEnd->addDay();
487
            $current = clone $currentEnd;
488
        }
489
490
        return $budgeted;
491
    }
492
493
    /**
494
     * Small helper function for some of the charts.
495
     *
496
     * @param array $categoryIds
497
     *
498
     * @return array
499
     */
500
    private function getCategoryNames(array $categoryIds): array
501
    {
502
        /** @var CategoryRepositoryInterface $repository */
503
        $repository = app(CategoryRepositoryInterface::class);
504
        $categories = $repository->getCategories();
505
        $grouped    = $categories->groupBy('id')->toArray();
506
        $return     = [];
507
        foreach ($categoryIds as $categoryId) {
508
            if (isset($grouped[$categoryId])) {
509
                $return[$categoryId] = $grouped[$categoryId][0]['name'];
510
            }
511
        }
512
        $return[0] = trans('firefly.noCategory');
513
514
        return $return;
515
    }
516
517
    /**
518
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok.
519
     *
520
     * @param Collection $limits
521
     * @param Budget     $budget
522
     * @param Carbon     $start
523
     * @param Carbon     $end
524
     *
525
     * @return array
526
     */
527
    private function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array
528
    {
529
        $return = [];
530
        if (0 === $limits->count()) {
531
            $spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end);
532
            if (0 !== bccomp($spent, '0')) {
533
                $return[$budget->name]['spent']     = bcmul($spent, '-1');
534
                $return[$budget->name]['left']      = 0;
535
                $return[$budget->name]['overspent'] = 0;
536
            }
537
538
            return $return;
539
        }
540
541
        $rows = $this->spentInPeriodMulti($budget, $limits);
542
        foreach ($rows as $name => $row) {
543
            if (0 !== bccomp($row['spent'], '0') || 0 !== bccomp($row['left'], '0')) {
544
                $return[$name] = $row;
545
            }
546
        }
547
        unset($rows, $row);
548
549
        return $return;
550
    }
551
552
    /**
553
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five.
554
     *
555
     * Returns an array with the following values:
556
     * 0 =>
557
     *   'name' => name of budget + repetition
558
     *   'left' => left in budget repetition (always zero)
559
     *   'overspent' => spent more than budget repetition? (always zero)
560
     *   'spent' => actually spent in period for budget
561
     * 1 => (etc)
562
     *
563
     * @param Budget     $budget
564
     * @param Collection $limits
565
     *
566
     * @return array
567
     */
568
    private function spentInPeriodMulti(Budget $budget, Collection $limits): array
569
    {
570
        $return = [];
571
        $format = (string)trans('config.month_and_day');
572
        $name   = $budget->name;
573
        /** @var BudgetLimit $budgetLimit */
574
        foreach ($limits as $budgetLimit) {
575
            $expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date);
576
            $expenses = Steam::positive($expenses);
0 ignored issues
show
Bug introduced by James Cole
The method positive() does not exist on FireflyIII\Support\Facades\Steam. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

576
            /** @scrutinizer ignore-call */ 
577
            $expenses = Steam::positive($expenses);
Loading history...
577
578
            if ($limits->count() > 1) {
579
                $name = $budget->name . ' ' . trans(
0 ignored issues
show
Bug introduced by James Cole
Are you sure trans('firefly.between_d...matLocalized($format))) of type null|string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

579
                $name = $budget->name . ' ' . /** @scrutinizer ignore-type */ trans(
Loading history...
580
                        'firefly.between_dates',
581
                        [
582
                            'start' => $budgetLimit->start_date->formatLocalized($format),
583
                            'end'   => $budgetLimit->end_date->formatLocalized($format),
584
                        ]
585
                    );
586
            }
587
            /*
588
             * amount: amount of budget limit
589
             * left: amount of budget limit min spent, or 0 when < 0.
590
             * spent: spent, or amount of budget limit when > amount
591
             */
592
            $amount       = $budgetLimit->amount;
593
            $leftInLimit  = bcsub($amount, $expenses);
594
            $hasOverspent = bccomp($leftInLimit, '0') === -1;
595
596
            $left      = $hasOverspent ? '0' : bcsub($amount, $expenses);
597
            $spent     = $hasOverspent ? $amount : $expenses;
598
            $overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0';
599
600
            $return[$name] = [
601
                'left'      => $left,
602
                'overspent' => $overspent,
603
                'spent'     => $spent,
604
            ];
605
        }
606
607
        return $return;
608
    }
609
610
    /**
611
     * Returns an array with the following values:
612
     * 'name' => "no budget" in local language
613
     * 'repetition_left' => left in budget repetition (always zero)
614
     * 'repetition_overspent' => spent more than budget repetition? (always zero)
615
     * 'spent' => actually spent in period for budget.
616
     *
617
     * @param Carbon $start
618
     * @param Carbon $end
619
     *
620
     * @return string
621
     */
622
    private function spentInPeriodWithout(Carbon $start, Carbon $end): string
623
    {
624
        // collector
625
        /** @var JournalCollectorInterface $collector */
626
        $collector = app(JournalCollectorInterface::class);
627
        $types     = [TransactionType::WITHDRAWAL];
628
        $collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget();
629
        $journals = $collector->getJournals();
630
        $sum      = '0';
631
        /** @var Transaction $entry */
632
        foreach ($journals as $entry) {
633
            $sum = bcadd($entry->transaction_amount, $sum);
634
        }
635
636
        return $sum;
637
    }
638
}
639