CategoryController   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 920
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 85
eloc 541
c 2
b 0
f 0
dl 0
loc 920
rs 2

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
B accountPerCategory() 0 89 8
B categories() 0 94 8
B accounts() 0 90 8
A avgExpenses() 0 43 5
A avgIncome() 0 43 5
A topIncome() 0 40 5
A topExpenses() 0 40 5
A noAmountInArray() 0 7 2
F operations() 0 153 18
C expenses() 0 94 10
C income() 0 92 10

How to fix   Complexity   

Complex Class

Complex classes like CategoryController 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 CategoryController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CategoryController.php
4
 * Copyright (c) 2019 [email protected]
5
 *
6
 * This file is part of Firefly III (https://github.com/firefly-iii).
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License as
10
 * published by the Free Software Foundation, either version 3 of the
11
 * License, or (at your option) any later version.
12
 *
13
 * This program 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 Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Http\Controllers\Report;
24
25
use Carbon\Carbon;
26
use FireflyIII\Http\Controllers\Controller;
27
use FireflyIII\Models\Account;
28
use FireflyIII\Models\Category;
29
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
30
use FireflyIII\Repositories\Category\NoCategoryRepositoryInterface;
31
use FireflyIII\Repositories\Category\OperationsRepositoryInterface;
32
use FireflyIII\Support\CacheProperties;
33
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
34
use Illuminate\Contracts\View\Factory;
35
use Illuminate\Support\Collection;
36
use Illuminate\View\View;
37
use Log;
38
use Throwable;
39
40
/**
41
 * Class CategoryController.
42
 */
43
class CategoryController extends Controller
44
{
45
    use BasicDataSupport;
46
47
48
    /** @var OperationsRepositoryInterface */
49
    private $opsRepository;
50
51
    /**
52
     * ExpenseReportController constructor.
53
     *
54
     * @codeCoverageIgnore
55
     */
56
    public function __construct()
57
    {
58
        parent::__construct();
59
        $this->middleware(
60
            function ($request, $next) {
61
                $this->opsRepository = app(OperationsRepositoryInterface::class);
62
63
                return $next($request);
64
            }
65
        );
66
    }
67
68
    /**
69
     * @param Collection $accounts
70
     * @param Collection $categories
71
     * @param Carbon     $start
72
     * @param Carbon     $end
73
     *
74
     * @return Factory|View
75
     */
76
    public function accountPerCategory(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
77
    {
78
        $spent  = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
79
        $earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
80
        $report = [];
81
        /** @var Account $account */
82
        foreach ($accounts as $account) {
83
            $accountId          = $account->id;
84
            $report[$accountId] = $report[$accountId] ?? [
85
                    'name'       => $account->name,
86
                    'id'         => $account->id,
87
                    'iban'       => $account->iban,
88
                    'currencies' => [],
89
                ];
90
        }
91
92
        // loop expenses.
93
        foreach ($spent as $currency) {
94
            $currencyId = $currency['currency_id'];
95
96
            /** @var array $category */
97
            foreach ($currency['categories'] as $category) {
98
                foreach ($category['transaction_journals'] as $journal) {
99
                    $sourceAccountId                                     = $journal['source_account_id'];
100
                    $report[$sourceAccountId]['currencies'][$currencyId] = $report[$sourceAccountId]['currencies'][$currencyId] ?? [
101
                            'currency_id'             => $currency['currency_id'],
102
                            'currency_symbol'         => $currency['currency_symbol'],
103
                            'currency_name'           => $currency['currency_name'],
104
                            'currency_decimal_places' => $currency['currency_decimal_places'],
105
                            'categories'              => [],
106
                        ];
107
108
                    $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]
109
                                                                                                                 = $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]
110
                                                                                                                   ??
111
                                                                                                                   [
112
                                                                                                                       'spent'  => '0',
113
                                                                                                                       'earned' => '0',
114
                                                                                                                       'sum'    => '0',
115
                                                                                                                   ];
116
                    $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'] = bcadd(
117
                        $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'], $journal['amount']
118
                    );
119
                    $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['sum']   = bcadd(
120
                        $report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['sum'], $journal['amount']
121
                    );
122
                }
123
            }
124
        }
125
126
127
        // loop income.
128
        foreach ($earned as $currency) {
129
            $currencyId = $currency['currency_id'];
130
131
            /** @var array $category */
132
            foreach ($currency['categories'] as $category) {
133
                foreach ($category['transaction_journals'] as $journal) {
134
                    $destinationId = $journal['destination_account_id'];
135
                    $report[$destinationId]['currencies'][$currencyId]
136
                                   = $report[$destinationId]['currencies'][$currencyId]
137
                                     ?? [
138
                                         'currency_id'             => $currency['currency_id'],
139
                                         'currency_symbol'         => $currency['currency_symbol'],
140
                                         'currency_name'           => $currency['currency_name'],
141
                                         'currency_decimal_places' => $currency['currency_decimal_places'],
142
                                         'categories'              => [],
143
                                     ];
144
145
146
                    $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]
147
                                                                                                                = $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]
148
                                                                                                                  ??
149
                                                                                                                  [
150
                                                                                                                      'spent'  => '0',
151
                                                                                                                      'earned' => '0',
152
                                                                                                                      'sum'    => '0',
153
                                                                                                                  ];
154
                    $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'] = bcadd(
155
                        $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'], $journal['amount']
156
                    );
157
                    $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['sum']    = bcadd(
158
                        $report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['sum'], $journal['amount']
159
                    );
160
                }
161
            }
162
        }
163
164
        return view('reports.category.partials.account-per-category', compact('report', 'categories'));
165
    }
166
167
    /**
168
     * @param Collection $accounts
169
     * @param Collection $categories
170
     * @param Carbon     $start
171
     * @param Carbon     $end
172
     *
173
     * @return Factory|View
174
     */
175
    public function accounts(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
176
    {
177
        $spent  = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
178
        $earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
179
        $report = [];
180
        $sums   = [];
181
        /** @var Account $account */
182
        foreach ($accounts as $account) {
183
            $accountId          = $account->id;
184
            $report[$accountId] = $report[$accountId] ?? [
185
                    'name'       => $account->name,
186
                    'id'         => $account->id,
187
                    'iban'       => $account->iban,
188
                    'currencies' => [],
189
                ];
190
        }
191
192
        // loop expenses.
193
        foreach ($spent as $currency) {
194
            $currencyId        = $currency['currency_id'];
195
            $sums[$currencyId] = $sums[$currencyId] ?? [
196
                    'currency_id'             => $currency['currency_id'],
197
                    'currency_symbol'         => $currency['currency_symbol'],
198
                    'currency_name'           => $currency['currency_name'],
199
                    'currency_decimal_places' => $currency['currency_decimal_places'],
200
                    'spent_sum'               => '0',
201
                    'earned_sum'              => '0',
202
                    'total_sum'               => '0',
203
                ];
204
            foreach ($currency['categories'] as $category) {
205
                foreach ($category['transaction_journals'] as $journal) {
206
                    $sourceAccountId                                              = $journal['source_account_id'];
207
                    $report[$sourceAccountId]['currencies'][$currencyId]          = $report[$sourceAccountId]['currencies'][$currencyId] ?? [
208
                            'currency_id'             => $currency['currency_id'],
209
                            'currency_symbol'         => $currency['currency_symbol'],
210
                            'currency_name'           => $currency['currency_name'],
211
                            'currency_decimal_places' => $currency['currency_decimal_places'],
212
                            'spent'                   => '0',
213
                            'earned'                  => '0',
214
                            'sum'                     => '0',
215
                        ];
216
                    $report[$sourceAccountId]['currencies'][$currencyId]['spent'] = bcadd(
217
                        $report[$sourceAccountId]['currencies'][$currencyId]['spent'], $journal['amount']
218
                    );
219
                    $report[$sourceAccountId]['currencies'][$currencyId]['sum']   = bcadd(
220
                        $report[$sourceAccountId]['currencies'][$currencyId]['sum'], $journal['amount']
221
                    );
222
                    $sums[$currencyId]['spent_sum']                               = bcadd($sums[$currencyId]['spent_sum'], $journal['amount']);
223
                    $sums[$currencyId]['total_sum']                               = bcadd($sums[$currencyId]['total_sum'], $journal['amount']);
224
                }
225
            }
226
        }
227
228
        // loop income.
229
        foreach ($earned as $currency) {
230
            $currencyId        = $currency['currency_id'];
231
            $sums[$currencyId] = $sums[$currencyId] ?? [
232
                    'currency_id'             => $currency['currency_id'],
233
                    'currency_symbol'         => $currency['currency_symbol'],
234
                    'currency_name'           => $currency['currency_name'],
235
                    'currency_decimal_places' => $currency['currency_decimal_places'],
236
                    'spent_sum'               => '0',
237
                    'earned_sum'              => '0',
238
                    'total_sum'               => '0',
239
                ];
240
            foreach ($currency['categories'] as $category) {
241
                foreach ($category['transaction_journals'] as $journal) {
242
                    $destinationAccountId                                               = $journal['destination_account_id'];
243
                    $report[$destinationAccountId]['currencies'][$currencyId]           = $report[$destinationAccountId]['currencies'][$currencyId] ?? [
244
                            'currency_id'             => $currency['currency_id'],
245
                            'currency_symbol'         => $currency['currency_symbol'],
246
                            'currency_name'           => $currency['currency_name'],
247
                            'currency_decimal_places' => $currency['currency_decimal_places'],
248
                            'spent'                   => '0',
249
                            'earned'                  => '0',
250
                            'sum'                     => '0',
251
                        ];
252
                    $report[$destinationAccountId]['currencies'][$currencyId]['earned'] = bcadd(
253
                        $report[$destinationAccountId]['currencies'][$currencyId]['earned'], $journal['amount']
254
                    );
255
                    $report[$destinationAccountId]['currencies'][$currencyId]['sum']    = bcadd(
256
                        $report[$destinationAccountId]['currencies'][$currencyId]['sum'], $journal['amount']
257
                    );
258
                    $sums[$currencyId]['earned_sum']                                    = bcadd($sums[$currencyId]['earned_sum'], $journal['amount']);
259
                    $sums[$currencyId]['total_sum']                                     = bcadd($sums[$currencyId]['total_sum'], $journal['amount']);
260
                }
261
            }
262
        }
263
264
        return view('reports.category.partials.accounts', compact('sums', 'report'));
265
    }
266
267
    /**
268
     * @param Collection $accounts
269
     * @param Collection $categories
270
     * @param Carbon     $start
271
     * @param Carbon     $end
272
     *
273
     * @return array|string
274
     */
275
    public function avgExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
276
    {
277
        $spent  = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
278
        $result = [];
279
        foreach ($spent as $currency) {
280
            $currencyId = $currency['currency_id'];
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $currencyId is dead and can be removed.
Loading history...
281
            foreach ($currency['categories'] as $category) {
282
                foreach ($category['transaction_journals'] as $journal) {
283
                    $destinationId = $journal['destination_account_id'];
284
                    $key           = sprintf('%d-%d', $destinationId, $currency['currency_id']);
285
                    $result[$key]  = $result[$key] ?? [
286
                            'transactions'             => 0,
287
                            'sum'                      => '0',
288
                            'avg'                      => '0',
289
                            'avg_float'                => 0,
290
                            'destination_account_name' => $journal['destination_account_name'],
291
                            'destination_account_id'   => $journal['destination_account_id'],
292
                            'currency_id'              => $currency['currency_id'],
293
                            'currency_name'            => $currency['currency_name'],
294
                            'currency_symbol'          => $currency['currency_symbol'],
295
                            'currency_decimal_places'  => $currency['currency_decimal_places'],
296
                        ];
297
                    $result[$key]['transactions']++;
298
                    $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']);
299
                    $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']);
300
                    $result[$key]['avg_float'] = (float)$result[$key]['avg'];
301
                }
302
            }
303
        }
304
        // sort by amount_float
305
        // sort temp array by amount.
306
        $amounts = array_column($result, 'avg_float');
307
        array_multisort($amounts, SORT_ASC, $result);
308
309
        try {
310
            $result = view('reports.category.partials.avg-expenses', compact('result'))->render();
311
            // @codeCoverageIgnoreStart
312
        } catch (Throwable $e) {
313
            Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
314
            $result = sprintf('Could not render view: %s', $e->getMessage());
315
        }
316
317
        return $result;
318
    }
319
320
    /**
321
     * @param Collection $accounts
322
     * @param Collection $categories
323
     * @param Carbon     $start
324
     * @param Carbon     $end
325
     *
326
     * @return array|string
327
     */
328
    public function avgIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
329
    {
330
        $spent  = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
331
        $result = [];
332
        foreach ($spent as $currency) {
333
            $currencyId = $currency['currency_id'];
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $currencyId is dead and can be removed.
Loading history...
334
            foreach ($currency['categories'] as $category) {
335
                foreach ($category['transaction_journals'] as $journal) {
336
                    $sourceId     = $journal['source_account_id'];
337
                    $key          = sprintf('%d-%d', $sourceId, $currency['currency_id']);
338
                    $result[$key] = $result[$key] ?? [
339
                            'transactions'            => 0,
340
                            'sum'                     => '0',
341
                            'avg'                     => '0',
342
                            'avg_float'               => 0,
343
                            'source_account_name'     => $journal['source_account_name'],
344
                            'source_account_id'       => $journal['source_account_id'],
345
                            'currency_id'             => $currency['currency_id'],
346
                            'currency_name'           => $currency['currency_name'],
347
                            'currency_symbol'         => $currency['currency_symbol'],
348
                            'currency_decimal_places' => $currency['currency_decimal_places'],
349
                        ];
350
                    $result[$key]['transactions']++;
351
                    $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']);
352
                    $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']);
353
                    $result[$key]['avg_float'] = (float)$result[$key]['avg'];
354
                }
355
            }
356
        }
357
        // sort by amount_float
358
        // sort temp array by amount.
359
        $amounts = array_column($result, 'avg_float');
360
        array_multisort($amounts, SORT_DESC, $result);
361
362
        try {
363
            $result = view('reports.category.partials.avg-income', compact('result'))->render();
364
            // @codeCoverageIgnoreStart
365
        } catch (Throwable $e) {
366
            Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
367
            $result = sprintf('Could not render view: %s', $e->getMessage());
368
        }
369
370
        return $result;
371
    }
372
373
    /**
374
     * @param Collection $accounts
375
     * @param Collection $categories
376
     * @param Carbon     $start
377
     * @param Carbon     $end
378
     *
379
     * @return Factory|View
380
     */
381
    public function categories(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
382
    {
383
        $spent  = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
384
        $earned = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
385
        $sums   = [];
386
        $report = [];
387
        /** @var Category $category */
388
        foreach ($categories as $category) {
389
            $categoryId          = $category->id;
390
            $report[$categoryId] = $report[$categoryId] ?? [
391
                    'name'       => $category->name,
392
                    'id'         => $category->id,
393
                    'currencies' => [],
394
                ];
395
        }
396
        foreach ($spent as $currency) {
397
            $currencyId        = $currency['currency_id'];
398
            $sums[$currencyId] = $sums[$currencyId] ?? [
399
                    'currency_id'             => $currency['currency_id'],
400
                    'currency_symbol'         => $currency['currency_symbol'],
401
                    'currency_name'           => $currency['currency_name'],
402
                    'currency_decimal_places' => $currency['currency_decimal_places'],
403
                    'earned_sum'              => '0',
404
                    'spent_sum'               => '0',
405
                    'total_sum'               => '0',
406
                ];
407
            /** @var array $category */
408
            foreach ($currency['categories'] as $category) {
409
                $categoryId = $category['id'];
410
411
                foreach ($category['transaction_journals'] as $journal) {
412
                    // add currency info to report array:
413
                    $report[$categoryId]['currencies'][$currencyId]          = $report[$categoryId]['currencies'][$currencyId] ?? [
414
                            'spent'                   => '0',
415
                            'earned'                  => '0',
416
                            'sum'                     => '0',
417
                            'currency_id'             => $currency['currency_id'],
418
                            'currency_symbol'         => $currency['currency_symbol'],
419
                            'currency_name'           => $currency['currency_name'],
420
                            'currency_decimal_places' => $currency['currency_decimal_places'],
421
                        ];
422
                    $report[$categoryId]['currencies'][$currencyId]['spent'] = bcadd(
423
                        $report[$categoryId]['currencies'][$currencyId]['spent'], $journal['amount']
424
                    );
425
                    $report[$categoryId]['currencies'][$currencyId]['sum']   = bcadd(
426
                        $report[$categoryId]['currencies'][$currencyId]['sum'], $journal['amount']
427
                    );
428
429
                    $sums[$currencyId]['spent_sum'] = bcadd($sums[$currencyId]['spent_sum'], $journal['amount']);
430
                    $sums[$currencyId]['total_sum'] = bcadd($sums[$currencyId]['total_sum'], $journal['amount']);
431
                }
432
            }
433
        }
434
435
        foreach ($earned as $currency) {
436
            $currencyId        = $currency['currency_id'];
437
            $sums[$currencyId] = $sums[$currencyId] ?? [
438
                    'currency_id'             => $currency['currency_id'],
439
                    'currency_symbol'         => $currency['currency_symbol'],
440
                    'currency_name'           => $currency['currency_name'],
441
                    'currency_decimal_places' => $currency['currency_decimal_places'],
442
                    'earned_sum'              => '0',
443
                    'spent_sum'               => '0',
444
                    'total_sum'               => '0',
445
                ];
446
            /** @var array $category */
447
            foreach ($currency['categories'] as $category) {
448
                $categoryId = $category['id'];
449
450
                foreach ($category['transaction_journals'] as $journal) {
451
                    // add currency info to report array:
452
                    $report[$categoryId]['currencies'][$currencyId]           = $report[$categoryId]['currencies'][$currencyId] ?? [
453
                            'spent'                   => '0',
454
                            'earned'                  => '0',
455
                            'sum'                     => '0',
456
                            'currency_id'             => $currency['currency_id'],
457
                            'currency_symbol'         => $currency['currency_symbol'],
458
                            'currency_name'           => $currency['currency_name'],
459
                            'currency_decimal_places' => $currency['currency_decimal_places'],
460
                        ];
461
                    $report[$categoryId]['currencies'][$currencyId]['earned'] = bcadd(
462
                        $report[$categoryId]['currencies'][$currencyId]['earned'], $journal['amount']
463
                    );
464
                    $report[$categoryId]['currencies'][$currencyId]['sum']    = bcadd(
465
                        $report[$categoryId]['currencies'][$currencyId]['sum'], $journal['amount']
466
                    );
467
468
                    $sums[$currencyId]['earned_sum'] = bcadd($sums[$currencyId]['earned_sum'], $journal['amount']);
469
                    $sums[$currencyId]['total_sum']  = bcadd($sums[$currencyId]['total_sum'], $journal['amount']);
470
                }
471
            }
472
        }
473
474
        return view('reports.category.partials.categories', compact('sums', 'report'));
475
    }
476
477
    /**
478
     * Show overview of expenses in category.
479
     *
480
     * @param Collection $accounts
481
     * @param Carbon     $start
482
     * @param Carbon     $end
483
     *
484
     * @return mixed|string
485
     */
486
    public function expenses(Collection $accounts, Carbon $start, Carbon $end)
487
    {
488
        $cache = new CacheProperties;
489
        $cache->addProperty($start);
490
        $cache->addProperty($end);
491
        $cache->addProperty('category-period-expenses-report');
492
        $cache->addProperty($accounts->pluck('id')->toArray());
493
        if ($cache->has()) {
494
            return $cache->get(); // @codeCoverageIgnore
495
        }
496
        /** @var CategoryRepositoryInterface $repository */
497
        $repository = app(CategoryRepositoryInterface::class);
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $repository is dead and can be removed.
Loading history...
498
499
        /** @var OperationsRepositoryInterface $opsRepository */
500
        $opsRepository = app(OperationsRepositoryInterface::class);
501
502
        /** @var NoCategoryRepositoryInterface $noCatRepos */
503
        $noCatRepos = app(NoCategoryRepositoryInterface::class);
504
505
        // depending on the carbon format (a reliable way to determine the general date difference)
506
        // change the "listOfPeriods" call so the entire period gets included correctly.
507
        $format = app('navigation')->preferredCarbonFormat($start, $end);
508
509
        if ('Y' === $format) {
510
            $start->startOfYear();
511
        }
512
        if ('Y-m' === $format) {
513
            $start->startOfMonth();
514
        }
515
516
        $periods = app('navigation')->listOfPeriods($start, $end);
517
        $data    = [];
518
        $with    = $opsRepository->listExpenses($start, $end, $accounts);
519
        $without = $noCatRepos->listExpenses($start, $end, $accounts);
520
521
        foreach ($with as $currencyId => $currencyRow) {
522
            foreach ($currencyRow['categories'] as $categoryId => $categoryRow) {
523
                $key        = sprintf('%d-%d', $currencyId, $categoryId);
524
                $data[$key] = $data[$key] ?? [
525
                        'id'                      => $categoryRow['id'],
526
                        'title'                   => sprintf('%s (%s)', $categoryRow['name'], $currencyRow['currency_name']),
527
                        'currency_id'             => $currencyRow['currency_id'],
528
                        'currency_symbol'         => $currencyRow['currency_symbol'],
529
                        'currency_name'           => $currencyRow['currency_name'],
530
                        'currency_code'           => $currencyRow['currency_code'],
531
                        'currency_decimal_places' => $currencyRow['currency_decimal_places'],
532
                        'sum'                     => '0',
533
                        'entries'                 => [],
534
535
                    ];
536
                foreach ($categoryRow['transaction_journals'] as $journalId => $journal) {
537
                    $date                         = $journal['date']->format($format);
538
                    $data[$key]['entries'][$date] = $data[$key]['entries'][$date] ?? '0';
539
                    $data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date], $journal['amount']);
540
                    $data[$key]['sum']            = bcadd($data[$key]['sum'], $journal['amount']);
541
                }
542
            }
543
        }
544
        foreach ($without as $currencyId => $currencyRow) {
545
            $key        = sprintf('0-%d', $currencyId);
546
            $data[$key] = $data[$key] ?? [
547
                    'id'                      => 0,
548
                    'title'                   => sprintf('%s (%s)', trans('firefly.noCategory'), $currencyRow['currency_name']),
0 ignored issues
show
Bug introduced by James Cole
It seems like trans('firefly.noCategory') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, 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

548
                    'title'                   => sprintf('%s (%s)', /** @scrutinizer ignore-type */ trans('firefly.noCategory'), $currencyRow['currency_name']),
Loading history...
549
                    'currency_id'             => $currencyRow['currency_id'],
550
                    'currency_symbol'         => $currencyRow['currency_symbol'],
551
                    'currency_name'           => $currencyRow['currency_name'],
552
                    'currency_code'           => $currencyRow['currency_code'],
553
                    'currency_decimal_places' => $currencyRow['currency_decimal_places'],
554
                    'sum'                     => '0',
555
                    'entries'                 => [],
556
                ];
557
            foreach ($currencyRow['transaction_journals'] as $journalId => $journal) {
558
                $date                         = $journal['date']->format($format);
559
                $data[$key]['entries'][$date] = $data[$key]['entries'][$date] ?? '0';
560
                $data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date], $journal['amount']);
561
                $data[$key]['sum']            = bcadd($data[$key]['sum'], $journal['amount']);
562
            }
563
        }
564
        $cache->store($data);
565
566
        $report = $data;
567
568
        try {
569
            $result = view('reports.partials.category-period', compact('report', 'periods'))->render();
570
            // @codeCoverageIgnoreStart
571
        } catch (Throwable $e) {
572
            Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
573
            $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
574
        }
575
        // @codeCoverageIgnoreEnd
576
577
        $cache->store($result);
578
579
        return $result;
580
    }
581
582
    /**
583
     * Show overview of income in category.
584
     *
585
     * @param Collection $accounts
586
     *
587
     * @param Carbon     $start
588
     * @param Carbon     $end
589
     *
590
     * @return string
591
     */
592
    public function income(Collection $accounts, Carbon $start, Carbon $end): string
593
    {
594
        $cache = new CacheProperties;
595
        $cache->addProperty($start);
596
        $cache->addProperty($end);
597
        $cache->addProperty('category-period-income-report');
598
        $cache->addProperty($accounts->pluck('id')->toArray());
599
        if ($cache->has()) {
600
            return $cache->get(); // @codeCoverageIgnore
601
        }
602
603
        /** @var OperationsRepositoryInterface $opsRepository */
604
        $opsRepository = app(OperationsRepositoryInterface::class);
605
606
        /** @var NoCategoryRepositoryInterface $noCatRepos */
607
        $noCatRepos = app(NoCategoryRepositoryInterface::class);
608
609
        // depending on the carbon format (a reliable way to determine the general date difference)
610
        // change the "listOfPeriods" call so the entire period gets included correctly.
611
        $format = app('navigation')->preferredCarbonFormat($start, $end);
612
613
        if ('Y' === $format) {
614
            $start->startOfYear();
615
        }
616
        if ('Y-m' === $format) {
617
            $start->startOfMonth();
618
        }
619
620
        $periods = app('navigation')->listOfPeriods($start, $end);
621
        $data    = [];
622
        $with    = $opsRepository->listIncome($start, $end, $accounts);
623
        $without = $noCatRepos->listIncome($start, $end, $accounts);
624
625
        foreach ($with as $currencyId => $currencyRow) {
626
            foreach ($currencyRow['categories'] as $categoryId => $categoryRow) {
627
                $key        = sprintf('%d-%d', $currencyId, $categoryId);
628
                $data[$key] = $data[$key] ?? [
629
                        'id'                      => $categoryRow['id'],
630
                        'title'                   => sprintf('%s (%s)', $categoryRow['name'], $currencyRow['currency_name']),
631
                        'currency_id'             => $currencyRow['currency_id'],
632
                        'currency_symbol'         => $currencyRow['currency_symbol'],
633
                        'currency_name'           => $currencyRow['currency_name'],
634
                        'currency_code'           => $currencyRow['currency_code'],
635
                        'currency_decimal_places' => $currencyRow['currency_decimal_places'],
636
                        'sum'                     => '0',
637
                        'entries'                 => [],
638
639
                    ];
640
                foreach ($categoryRow['transaction_journals'] as $journalId => $journal) {
641
                    $date                         = $journal['date']->format($format);
642
                    $data[$key]['entries'][$date] = $data[$key]['entries'][$date] ?? '0';
643
                    $data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date], $journal['amount']);
644
                    $data[$key]['sum']            = bcadd($data[$key]['sum'], $journal['amount']);
645
                }
646
            }
647
        }
648
        foreach ($without as $currencyId => $currencyRow) {
649
            $key        = sprintf('0-%d', $currencyId);
650
            $data[$key] = $data[$key] ?? [
651
                    'id'                      => 0,
652
                    'title'                   => sprintf('%s (%s)', trans('firefly.noCategory'), $currencyRow['currency_name']),
0 ignored issues
show
Bug introduced by James Cole
It seems like trans('firefly.noCategory') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, 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

652
                    'title'                   => sprintf('%s (%s)', /** @scrutinizer ignore-type */ trans('firefly.noCategory'), $currencyRow['currency_name']),
Loading history...
653
                    'currency_id'             => $currencyRow['currency_id'],
654
                    'currency_symbol'         => $currencyRow['currency_symbol'],
655
                    'currency_name'           => $currencyRow['currency_name'],
656
                    'currency_code'           => $currencyRow['currency_code'],
657
                    'currency_decimal_places' => $currencyRow['currency_decimal_places'],
658
                    'sum'                     => '0',
659
                    'entries'                 => [],
660
                ];
661
            foreach ($currencyRow['transaction_journals'] as $journalId => $journal) {
662
                $date                         = $journal['date']->format($format);
663
                $data[$key]['entries'][$date] = $data[$key]['entries'][$date] ?? '0';
664
                $data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date], $journal['amount']);
665
                $data[$key]['sum']            = bcadd($data[$key]['sum'], $journal['amount']);
666
            }
667
        }
668
        $cache->store($data);
669
670
        $report = $data;
671
672
        try {
673
            $result = view('reports.partials.category-period', compact('report', 'periods'))->render();
674
            // @codeCoverageIgnoreStart
675
        } catch (Throwable $e) {
676
            Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
677
            $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
678
        }
679
        // @codeCoverageIgnoreEnd
680
681
        $cache->store($result);
682
683
        return $result;
684
    }
685
686
    /**
687
     * Show overview of operations.
688
     *
689
     * @param Collection $accounts
690
     * @param Carbon     $start
691
     * @param Carbon     $end
692
     *
693
     * @return mixed|string
694
     *
695
     */
696
    public function operations(Collection $accounts, Carbon $start, Carbon $end)
697
    {
698
        // chart properties for cache:
699
        $cache = new CacheProperties;
700
        $cache->addProperty($start);
701
        $cache->addProperty($end);
702
        $cache->addProperty('category-report');
703
        $cache->addProperty($accounts->pluck('id')->toArray());
704
        if ($cache->has()) {
705
            return $cache->get(); // @codeCoverageIgnore
706
        }
707
708
        /** @var CategoryRepositoryInterface $repository */
709
        $repository = app(CategoryRepositoryInterface::class);
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $repository is dead and can be removed.
Loading history...
710
711
        /** @var OperationsRepositoryInterface $opsRepository */
712
        $opsRepository = app(OperationsRepositoryInterface::class);
713
714
        /** @var NoCategoryRepositoryInterface $noCatRepository */
715
        $noCatRepository = app(NoCategoryRepositoryInterface::class);
716
717
        $earnedWith    = $opsRepository->listIncome($start, $end, $accounts);
718
        $spentWith     = $opsRepository->listExpenses($start, $end, $accounts);
719
        $earnedWithout = $noCatRepository->listIncome($start, $end, $accounts);
720
        $spentWithout  = $noCatRepository->listExpenses($start, $end, $accounts);
721
722
        $report = [
723
            'categories' => [],
724
            'sums'       => [],
725
        ];
726
727
        // needs four for-each loops.
728
        // TODO improve this.
729
        foreach ([$earnedWith, $spentWith] as $data) {
730
            foreach ($data as $currencyId => $currencyRow) {
731
                $report['sums'][$currencyId] = $report['sums'][$currencyId] ?? [
732
                        'spent'                   => '0',
733
                        'earned'                  => '0',
734
                        'sum'                     => '0',
735
                        'currency_id'             => $currencyRow['currency_id'],
736
                        'currency_symbol'         => $currencyRow['currency_symbol'],
737
                        'currency_name'           => $currencyRow['currency_name'],
738
                        'currency_code'           => $currencyRow['currency_code'],
739
                        'currency_decimal_places' => $currencyRow['currency_decimal_places'],
740
                    ];
741
742
743
                foreach ($currencyRow['categories'] as $categoryId => $categoryRow) {
744
                    $key                        = sprintf('%s-%s', $currencyId, $categoryId);
745
                    $report['categories'][$key] = $report['categories'][$key] ?? [
746
                            'id'                      => $categoryId,
747
                            'title'                   => $categoryRow['name'],
748
                            'currency_id'             => $currencyRow['currency_id'],
749
                            'currency_symbol'         => $currencyRow['currency_symbol'],
750
                            'currency_name'           => $currencyRow['currency_name'],
751
                            'currency_code'           => $currencyRow['currency_code'],
752
                            'currency_decimal_places' => $currencyRow['currency_decimal_places'],
753
                            'spent'                   => '0',
754
                            'earned'                  => '0',
755
                            'sum'                     => '0',
756
                        ];
757
                    // loop journals:
758
                    foreach ($categoryRow['transaction_journals'] as $journal) {
759
                        // sum of sums
760
                        $report['sums'][$currencyId]['sum'] = bcadd($report['sums'][$currencyId]['sum'], $journal['amount']);
761
                        // sum of spent:
762
                        $report['sums'][$currencyId]['spent'] = -1 === bccomp($journal['amount'], '0') ? bcadd(
763
                            $report['sums'][$currencyId]['spent'], $journal['amount']
764
                        ) : $report['sums'][$currencyId]['spent'];
765
                        // sum of earned
766
                        $report['sums'][$currencyId]['earned'] = 1 === bccomp($journal['amount'], '0') ? bcadd(
767
                            $report['sums'][$currencyId]['earned'], $journal['amount']
768
                        ) : $report['sums'][$currencyId]['earned'];
769
770
                        // sum of category
771
                        $report['categories'][$key]['sum'] = bcadd($report['categories'][$key]['sum'], $journal['amount']);
772
                        // total spent in category
773
                        $report['categories'][$key]['spent'] = -1 === bccomp($journal['amount'], '0') ? bcadd(
774
                            $report['categories'][$key]['spent'], $journal['amount']
775
                        ) : $report['categories'][$key]['spent'];
776
                        // total earned in category
777
                        $report['categories'][$key]['earned'] = 1 === bccomp($journal['amount'], '0') ? bcadd(
778
                            $report['categories'][$key]['earned'], $journal['amount']
779
                        ) : $report['categories'][$key]['earned'];
780
                    }
781
                }
782
            }
783
        }
784
        foreach ([$earnedWithout, $spentWithout] as $data) {
785
            foreach ($data as $currencyId => $currencyRow) {
786
                $report['sums'][$currencyId] = $report['sums'][$currencyId] ?? [
787
                        'spent'                   => '0',
788
                        'earned'                  => '0',
789
                        'sum'                     => '0',
790
                        'currency_id'             => $currencyRow['currency_id'],
791
                        'currency_symbol'         => $currencyRow['currency_symbol'],
792
                        'currency_name'           => $currencyRow['currency_name'],
793
                        'currency_code'           => $currencyRow['currency_code'],
794
                        'currency_decimal_places' => $currencyRow['currency_decimal_places'],
795
                    ];
796
                $key                         = sprintf('%s-0', $currencyId);
797
                $report['categories'][$key]  = $report['categories'][$key] ?? [
798
                        'id'                      => 0,
799
                        'title'                   => sprintf('%s (%s)', trans('firefly.noCategory'), $currencyRow['currency_name']),
0 ignored issues
show
Bug introduced by James Cole
It seems like trans('firefly.noCategory') can also be of type array; however, parameter $args of sprintf() does only seem to accept string, 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

799
                        'title'                   => sprintf('%s (%s)', /** @scrutinizer ignore-type */ trans('firefly.noCategory'), $currencyRow['currency_name']),
Loading history...
800
                        'currency_id'             => $currencyRow['currency_id'],
801
                        'currency_symbol'         => $currencyRow['currency_symbol'],
802
                        'currency_name'           => $currencyRow['currency_name'],
803
                        'currency_code'           => $currencyRow['currency_code'],
804
                        'currency_decimal_places' => $currencyRow['currency_decimal_places'],
805
                        'spent'                   => '0',
806
                        'earned'                  => '0',
807
                        'sum'                     => '0',
808
                    ];
809
                // loop journals:
810
                foreach ($currencyRow['transaction_journals'] as $journal) {
811
                    // sum of all
812
                    $report['sums'][$currencyId]['sum'] = bcadd($report['sums'][$currencyId]['sum'], $journal['amount']);
813
814
                    // sum of spent:
815
                    $report['sums'][$currencyId]['spent'] = -1 === bccomp($journal['amount'], '0') ? bcadd(
816
                        $report['sums'][$currencyId]['spent'], $journal['amount']
817
                    ) : $report['sums'][$currencyId]['spent'];
818
                    // sum of earned
819
                    $report['sums'][$currencyId]['earned'] = 1 === bccomp($journal['amount'], '0') ? bcadd(
820
                        $report['sums'][$currencyId]['earned'], $journal['amount']
821
                    ) : $report['sums'][$currencyId]['earned'];
822
823
                    // sum of category
824
                    $report['categories'][$key]['sum'] = bcadd($report['categories'][$key]['sum'], $journal['amount']);
825
                    // total spent in no category
826
                    $report['categories'][$key]['spent'] = -1 === bccomp($journal['amount'], '0') ? bcadd(
827
                        $report['categories'][$key]['spent'], $journal['amount']
828
                    ) : $report['categories'][$key]['spent'];
829
                    // total earned in no category
830
                    $report['categories'][$key]['earned'] = 1 === bccomp($journal['amount'], '0') ? bcadd(
831
                        $report['categories'][$key]['earned'], $journal['amount']
832
                    ) : $report['categories'][$key]['earned'];
833
                }
834
            }
835
        }
836
837
        // @codeCoverageIgnoreStart
838
        try {
839
            $result = view('reports.partials.categories', compact('report'))->render();
840
            $cache->store($result);
841
        } catch (Throwable $e) {
842
            Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
843
            $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
844
        }
845
846
        // @codeCoverageIgnoreEnd
847
848
        return $result;
849
    }
850
851
    /**
852
     * @param Collection $accounts
853
     * @param Collection $categories
854
     * @param Carbon     $start
855
     * @param Carbon     $end
856
     *
857
     * @return array|string
858
     */
859
    public function topExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
860
    {
861
        $spent  = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
862
        $result = [];
863
        foreach ($spent as $currency) {
864
            $currencyId = $currency['currency_id'];
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $currencyId is dead and can be removed.
Loading history...
865
            foreach ($currency['categories'] as $category) {
866
                foreach ($category['transaction_journals'] as $journal) {
867
                    $result[] = [
868
                        'description'              => $journal['description'],
869
                        'transaction_group_id'     => $journal['transaction_group_id'],
870
                        'amount_float'             => (float)$journal['amount'],
871
                        'amount'                   => $journal['amount'],
872
                        'date'                     => $journal['date']->formatLocalized($this->monthAndDayFormat),
873
                        'destination_account_name' => $journal['destination_account_name'],
874
                        'destination_account_id'   => $journal['destination_account_id'],
875
                        'currency_id'              => $currency['currency_id'],
876
                        'currency_name'            => $currency['currency_name'],
877
                        'currency_symbol'          => $currency['currency_symbol'],
878
                        'currency_decimal_places'  => $currency['currency_decimal_places'],
879
                        'category_id'              => $category['id'],
880
                        'category_name'            => $category['name'],
881
                    ];
882
                }
883
            }
884
        }
885
        // sort by amount_float
886
        // sort temp array by amount.
887
        $amounts = array_column($result, 'amount_float');
888
        array_multisort($amounts, SORT_ASC, $result);
889
890
        try {
891
            $result = view('reports.category.partials.top-expenses', compact('result'))->render();
892
            // @codeCoverageIgnoreStart
893
        } catch (Throwable $e) {
894
            Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
895
            $result = sprintf('Could not render view: %s', $e->getMessage());
896
        }
897
898
        return $result;
899
    }
900
901
    /**
902
     * @param Collection $accounts
903
     * @param Collection $categories
904
     * @param Carbon     $start
905
     * @param Carbon     $end
906
     *
907
     * @return array|string
908
     */
909
    public function topIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
910
    {
911
        $spent  = $this->opsRepository->listIncome($start, $end, $accounts, $categories);
912
        $result = [];
913
        foreach ($spent as $currency) {
914
            $currencyId = $currency['currency_id'];
0 ignored issues
show
Unused Code introduced by James Cole
The assignment to $currencyId is dead and can be removed.
Loading history...
915
            foreach ($currency['categories'] as $category) {
916
                foreach ($category['transaction_journals'] as $journal) {
917
                    $result[] = [
918
                        'description'             => $journal['description'],
919
                        'transaction_group_id'    => $journal['transaction_group_id'],
920
                        'amount_float'            => (float)$journal['amount'],
921
                        'amount'                  => $journal['amount'],
922
                        'date'                    => $journal['date']->formatLocalized($this->monthAndDayFormat),
923
                        'source_account_name'     => $journal['source_account_name'],
924
                        'source_account_id'       => $journal['source_account_id'],
925
                        'currency_id'             => $currency['currency_id'],
926
                        'currency_name'           => $currency['currency_name'],
927
                        'currency_symbol'         => $currency['currency_symbol'],
928
                        'currency_decimal_places' => $currency['currency_decimal_places'],
929
                        'category_id'             => $category['id'],
930
                        'category_name'           => $category['name'],
931
                    ];
932
                }
933
            }
934
        }
935
        // sort by amount_float
936
        // sort temp array by amount.
937
        $amounts = array_column($result, 'amount_float');
938
        array_multisort($amounts, SORT_DESC, $result);
939
940
        try {
941
            $result = view('reports.category.partials.top-income', compact('result'))->render();
942
            // @codeCoverageIgnoreStart
943
        } catch (Throwable $e) {
944
            Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
945
            $result = sprintf('Could not render view: %s', $e->getMessage());
946
        }
947
948
        return $result;
949
    }
950
951
    /**
952
     * @param array $array
953
     *
954
     * @return bool
955
     */
956
    private function noAmountInArray(array $array): bool
0 ignored issues
show
Unused Code introduced by James Cole
The method noAmountInArray() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
957
    {
958
        if (0 === count($array)) {
959
            return true;
960
        }
961
962
        return false;
963
    }
964
965
966
}
967