GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — develop ( 402351...284222 )
by James
11:56
created

app/Http/Controllers/Chart/BudgetController.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * BudgetController.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\Chart;
24
25
use Carbon\Carbon;
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
28
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
29
use FireflyIII\Http\Controllers\Controller;
30
use FireflyIII\Models\Budget;
31
use FireflyIII\Models\BudgetLimit;
32
use FireflyIII\Models\TransactionCurrency;
33
use FireflyIII\Models\TransactionType;
34
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
35
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
36
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
37
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
38
use FireflyIII\Support\CacheProperties;
39
use FireflyIII\Support\Http\Controllers\AugumentData;
40
use FireflyIII\Support\Http\Controllers\DateCalculation;
41
use Illuminate\Http\JsonResponse;
42
use Illuminate\Support\Collection;
43
44
/**
45
 * Class BudgetController.
46
 *
47
 */
48
class BudgetController extends Controller
49
{
50
    use DateCalculation, AugumentData;
51
    /** @var GeneratorInterface Chart generation methods. */
52
    protected $generator;
53
    /** @var OperationsRepositoryInterface */
54
    protected $opsRepository;
55
    /** @var BudgetRepositoryInterface The budget repository */
56
    protected $repository;
57
    /** @var BudgetLimitRepositoryInterface */
58
    private $blRepository;
59
    /** @var NoBudgetRepositoryInterface */
60
    private $nbRepository;
61
62
    /**
63
     * BudgetController constructor.
64
     *
65
     * @codeCoverageIgnore
66
     */
67
    public function __construct()
68
    {
69
        parent::__construct();
70
71
        $this->middleware(
72
            function ($request, $next) {
73
                $this->generator     = app(GeneratorInterface::class);
74
                $this->repository    = app(BudgetRepositoryInterface::class);
75
                $this->opsRepository = app(OperationsRepositoryInterface::class);
76
                $this->blRepository  = app(BudgetLimitRepositoryInterface::class);
77
                $this->nbRepository  = app(NoBudgetRepositoryInterface::class);
78
79
                return $next($request);
80
            }
81
        );
82
    }
83
84
85
    /**
86
     * Shows overview of a single budget.
87
     *
88
     * @param Budget $budget
89
     *
90
     * @return JsonResponse
91
     */
92
    public function budget(Budget $budget): JsonResponse
93
    {
94
        /** @var Carbon $start */
95
        $start = $this->repository->firstUseDate($budget) ?? session('start', new Carbon);
96
        /** @var Carbon $end */
97
        $end   = session('end', new Carbon);
98
        $cache = new CacheProperties();
99
        $cache->addProperty($start);
100
        $cache->addProperty($end);
101
        $cache->addProperty('chart.budget.budget');
102
        $cache->addProperty($budget->id);
103
104
        if ($cache->has()) {
105
            return response()->json($cache->get()); // @codeCoverageIgnore
106
        }
107
        $step           = $this->calculateStep($start, $end); // depending on diff, do something with range of chart.
108
        $collection     = new Collection([$budget]);
109
        $chartData      = [];
110
        $loopStart      = clone $start;
111
        $loopStart      = app('navigation')->startOfPeriod($loopStart, $step);
112
        $currencies     = [];
113
        $defaultEntries = [];
114
        while ($end >= $loopStart) {
115
            /** @var Carbon $currentEnd */
116
            $loopEnd = app('navigation')->endOfPeriod($loopStart, $step);
117
            if ('1Y' === $step) {
118
                $loopEnd->subDay(); // @codeCoverageIgnore
119
            }
120
            $spent = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection);
121
            $label = trim(app('navigation')->periodShow($loopStart, $step));
122
123
            foreach ($spent as $row) {
124
                $currencyId              = $row['currency_id'];
125
                $currencies[$currencyId] = $currencies[$currencyId] ?? $row; // don't mind the field 'sum'
126
                // also store this day's sum:
127
                $currencies[$currencyId]['spent'][$label] = $row['sum'];
128
            }
129
            $defaultEntries[$label] = 0;
130
            // set loop start to the next period:
131
            $loopStart = clone $loopEnd;
132
            $loopStart->addSecond();
133
        }
134
        // loop all currencies:
135
        foreach ($currencies as $currencyId => $currency) {
136
            $chartData[$currencyId] = [
137
                'label'           => count($currencies) > 1 ? sprintf('%s (%s)', $budget->name, $currency['currency_name']) : $budget->name,
138
                'type'            => 'bar',
139
                'currency_symbol' => $currency['currency_symbol'],
140
                'currency_code'   => $currency['currency_code'],
141
                'entries'         => $defaultEntries,
142
            ];
143
            foreach ($currency['spent'] as $label => $spent) {
144
                $chartData[$currencyId]['entries'][$label] = round(bcmul($spent, '-1'), $currency['currency_decimal_places']);
0 ignored issues
show
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

144
                $chartData[$currencyId]['entries'][$label] = round(/** @scrutinizer ignore-type */ bcmul($spent, '-1'), $currency['currency_decimal_places']);
Loading history...
145
            }
146
        }
147
        $data = $this->generator->multiSet(array_values($chartData));
148
        $cache->store($data);
149
150
        return response()->json($data);
151
    }
152
153
154
    /**
155
     * Shows the amount left in a specific budget limit.
156
     *
157
     * @param Budget      $budget
158
     * @param BudgetLimit $budgetLimit
159
     *
160
     * @return JsonResponse
161
     *
162
     * @throws FireflyException
163
     */
164
    public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit): JsonResponse
165
    {
166
        if ($budgetLimit->budget->id !== $budget->id) {
167
            throw new FireflyException('This budget limit is not part of this budget.');
168
        }
169
170
        $start = clone $budgetLimit->start_date;
171
        $end   = clone $budgetLimit->end_date;
172
        $cache = new CacheProperties();
173
        $cache->addProperty($start);
174
        $cache->addProperty($end);
175
        $cache->addProperty('chart.budget.budget.limit');
176
        $cache->addProperty($budgetLimit->id);
177
        $cache->addProperty($budget->id);
178
179
        if ($cache->has()) {
180
            return response()->json($cache->get()); // @codeCoverageIgnore
181
        }
182
        $locale = app('steam')->getLocale();
183
        $entries          = [];
184
        $amount           = $budgetLimit->amount;
185
        $budgetCollection = new Collection([$budget]);
186
        while ($start <= $end) {
187
            $spent            = $this->opsRepository->spentInPeriod($budgetCollection, new Collection, $start, $start);
188
            $amount           = bcadd($amount, $spent);
189
            $format           = $start->formatLocalized((string)trans('config.month_and_day', [], $locale));
190
            $entries[$format] = $amount;
191
192
            $start->addDay();
193
        }
194
        $data = $this->generator->singleSet((string)trans('firefly.left'), $entries);
195
        // add currency symbol from budget limit:
196
        $data['datasets'][0]['currency_symbol'] = $budgetLimit->transactionCurrency->symbol;
197
        $data['datasets'][0]['currency_code']   = $budgetLimit->transactionCurrency->code;
198
        $cache->store($data);
199
200
        return response()->json($data);
201
    }
202
203
204
    /**
205
     * Shows how much is spent per asset account.
206
     *
207
     * @param Budget           $budget
208
     * @param BudgetLimit|null $budgetLimit
209
     *
210
     * @return JsonResponse
211
     */
212
    public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
213
    {
214
        /** @var GroupCollectorInterface $collector */
215
        $collector     = app(GroupCollectorInterface::class);
216
        $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id;
217
        $cache         = new CacheProperties;
218
        $cache->addProperty($budget->id);
219
        $cache->addProperty($budgetLimitId);
220
        $cache->addProperty('chart.budget.expense-asset');
221
        $start = session()->get('start');
222
        $end   = session()->get('end');
223
        if (null !== $budgetLimit) {
224
            $start = $budgetLimit->start_date;
225
            $end   = $budgetLimit->end_date;
226
            $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency);
227
        }
228
        $cache->addProperty($start);
229
        $cache->addProperty($end);
230
231
        if ($cache->has()) {
232
            return response()->json($cache->get()); // @codeCoverageIgnore
233
        }
234
        $collector->setRange($start, $end);
235
        $collector->setBudget($budget);
236
        $journals  = $collector->getExtractedJournals();
237
        $result    = [];
238
        $chartData = [];
239
240
        // group by asset account ID:
241
        foreach ($journals as $journal) {
242
            $key                    = sprintf('%d-%d', (int)$journal['source_account_id'], $journal['currency_id']);
243
            $result[$key]           = $result[$key] ?? [
244
                    'amount'          => '0',
245
                    'currency_symbol' => $journal['currency_symbol'],
246
                    'currency_code'   => $journal['currency_code'],
247
                    'currency_name'   => $journal['currency_name'],
248
                ];
249
            $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']);
250
        }
251
252
        $names = $this->getAccountNames(array_keys($result));
253
        foreach ($result as $combinedId => $info) {
254
            $parts   = explode('-', $combinedId);
255
            $assetId = (int)$parts[0];
256
            $title   = sprintf('%s (%s)', $names[$assetId] ?? '(empty)', $info['currency_name']);
257
            $chartData[$title]
258
                     = [
259
                'amount'          => $info['amount'],
260
                'currency_symbol' => $info['currency_symbol'],
261
                'currency_code' => $info['currency_code'],
262
            ];
263
        }
264
265
        $data = $this->generator->multiCurrencyPieChart($chartData);
266
        $cache->store($data);
267
268
        return response()->json($data);
269
    }
270
271
272
    /**
273
     * Shows how much is spent per category.
274
     *
275
     * @param Budget           $budget
276
     * @param BudgetLimit|null $budgetLimit
277
     *
278
     * @return JsonResponse
279
     */
280
    public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
281
    {
282
        /** @var GroupCollectorInterface $collector */
283
        $collector     = app(GroupCollectorInterface::class);
284
        $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id;
285
        $cache         = new CacheProperties;
286
        $cache->addProperty($budget->id);
287
        $cache->addProperty($budgetLimitId);
288
        $cache->addProperty('chart.budget.expense-category');
289
        $start = session()->get('start');
290
        $end   = session()->get('end');
291
        if (null !== $budgetLimit) {
292
            $start = $budgetLimit->start_date;
293
            $end   = $budgetLimit->end_date;
294
            $collector->setCurrency($budgetLimit->transactionCurrency);
295
        }
296
        $cache->addProperty($start);
297
        $cache->addProperty($end);
298
299
        if ($cache->has()) {
300
            return response()->json($cache->get()); // @codeCoverageIgnore
301
        }
302
        $collector->setRange($start, $end);
303
        $collector->setBudget($budget)->withCategoryInformation();
304
        $journals  = $collector->getExtractedJournals();
305
        $result    = [];
306
        $chartData = [];
307
        foreach ($journals as $journal) {
308
            $key                    = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
309
            $result[$key]           = $result[$key] ?? [
310
                    'amount'          => '0',
311
                    'currency_symbol' => $journal['currency_symbol'],
312
                    'currency_code'   => $journal['currency_code'],
313
                    'currency_symbol' => $journal['currency_symbol'],
314
                    'currency_name'   => $journal['currency_name'],
315
                ];
316
            $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']);
317
        }
318
319
        $names = $this->getCategoryNames(array_keys($result));
320
        foreach ($result as $combinedId => $info) {
321
            $parts             = explode('-', $combinedId);
322
            $categoryId        = (int)$parts[0];
323
            $title             = sprintf('%s (%s)', $names[$categoryId] ?? '(empty)', $info['currency_name']);
324
            $chartData[$title] = [
325
                'amount'          => $info['amount'],
326
                'currency_symbol' => $info['currency_symbol'],
327
                'currency_code' => $info['currency_code'],
328
            ];
329
        }
330
        $data = $this->generator->multiCurrencyPieChart($chartData);
331
        $cache->store($data);
332
333
        return response()->json($data);
334
    }
335
336
337
    /**
338
     * Shows how much is spent per expense account.
339
     *
340
     *
341
     * @param Budget           $budget
342
     * @param BudgetLimit|null $budgetLimit
343
     *
344
     * @return JsonResponse
345
     */
346
    public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
347
    {
348
        /** @var GroupCollectorInterface $collector */
349
        $collector     = app(GroupCollectorInterface::class);
350
        $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id;
351
        $cache         = new CacheProperties;
352
        $cache->addProperty($budget->id);
353
        $cache->addProperty($budgetLimitId);
354
        $cache->addProperty('chart.budget.expense-expense');
355
        $start = session()->get('start');
356
        $end   = session()->get('end');
357
        if (null !== $budgetLimit) {
358
            $start = $budgetLimit->start_date;
359
            $end   = $budgetLimit->end_date;
360
            $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency);
361
        }
362
        $cache->addProperty($start);
363
        $cache->addProperty($end);
364
365
        if ($cache->has()) {
366
            return response()->json($cache->get()); // @codeCoverageIgnore
367
        }
368
        $collector->setRange($start, $end);
369
        $collector->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withAccountInformation();
370
        $journals  = $collector->getExtractedJournals();
371
        $result    = [];
372
        $chartData = [];
373
        /** @var array $journal */
374
        foreach ($journals as $journal) {
375
            $key                    = sprintf('%d-%d', $journal['destination_account_id'], $journal['currency_id']);
376
            $result[$key]           = $result[$key] ?? [
377
                    'amount'          => '0',
378
                    'currency_symbol' => $journal['currency_symbol'],
379
                    'currency_code'   => $journal['currency_code'],
380
                    'currency_name'   => $journal['currency_name'],
381
                ];
382
            $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']);
383
        }
384
385
        $names = $this->getAccountNames(array_keys($result));
386
        foreach ($result as $combinedId => $info) {
387
            $parts             = explode('-', $combinedId);
388
            $opposingId        = (int)$parts[0];
389
            $name              = $names[$opposingId] ?? 'no name';
390
            $title             = sprintf('%s (%s)', $name, $info['currency_name']);
391
            $chartData[$title] = [
392
                'amount'          => $info['amount'],
393
                'currency_symbol' => $info['currency_symbol'],
394
                'currency_code' => $info['currency_code'],
395
            ];
396
        }
397
398
        $data = $this->generator->multiCurrencyPieChart($chartData);
399
        $cache->store($data);
400
401
        return response()->json($data);
402
    }
403
404
405
    /**
406
     * Shows a budget list with spent/left/overspent.
407
     *
408
     * TODO there are cases when this chart hides expenses: when budget has limits
409
     * and limits are found and used, but the expense is in another currency.
410
     *
411
     * @return JsonResponse
412
     *
413
     */
414
    public function frontpage(): JsonResponse
415
    {
416
        $start = session('start', Carbon::now()->startOfMonth());
417
        $end   = session('end', Carbon::now()->endOfMonth());
418
        // chart properties for cache:
419
        $cache = new CacheProperties();
420
        $cache->addProperty($start);
421
        $cache->addProperty($end);
422
        $cache->addProperty('chart.budget.frontpage');
423
        if ($cache->has()) {
424
             return response()->json($cache->get()); // @codeCoverageIgnore
425
        }
426
        $budgets   = $this->repository->getActiveBudgets();
427
        $chartData = [
428
            ['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
429
            ['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
430
            ['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
431
        ];
432
433
        /** @var Budget $budget */
434
        foreach ($budgets as $budget) {
435
            $limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
436
            if (0 === $limits->count()) {
437
                $spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget]), null);
438
                /** @var array $entry */
439
                foreach ($spent as $entry) {
440
                    $title                           = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
441
                    $chartData[0]['entries'][$title] = bcmul($entry['sum'], '-1'); // spent
442
                    $chartData[1]['entries'][$title] = 0; // left to spend
443
                    $chartData[2]['entries'][$title] = 0; // overspent
444
                }
445
            }
446
            if (0 !== $limits->count()) {
447
                /** @var BudgetLimit $limit */
448
                foreach ($limits as $limit) {
449
                    $spent = $this->opsRepository->sumExpenses(
450
                        $limit->start_date,
451
                        $limit->end_date,
452
                        null,
453
                        new Collection([$budget]),
454
                        $limit->transactionCurrency
455
                    );
456
                    /** @var array $entry */
457
                    foreach ($spent as $entry) {
458
                        $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
459
                        if ($limit->start_date->startOfDay()->ne($start->startOfDay()) || $limit->end_date->startOfDay()->ne($end->startOfDay())) {
460
                            $title = sprintf(
461
                                '%s (%s) (%s - %s)',
462
                                $budget->name,
463
                                $entry['currency_name'],
464
                                $limit->start_date->formatLocalized($this->monthAndDayFormat),
465
                                $limit->end_date->formatLocalized($this->monthAndDayFormat)
466
                            );
467
                        }
468
                        $sumSpent                        = bcmul($entry['sum'], '-1'); // spent
469
                        $chartData[0]['entries'][$title] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent;
470
                        $chartData[1]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($entry['sum'], $limit->amount)
471
                            : '0';
472
                        $chartData[2]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ?
473
                            '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1');
474
                    }
475
                }
476
            }
477
        }
478
        $data = $this->generator->multiSet($chartData);
479
        $cache->store($data);
480
481
        return response()->json($data);
482
    }
483
484
485
    /**
486
     * Shows a budget overview chart (spent and budgeted).
487
     *
488
     * @param Budget              $budget
489
     * @param TransactionCurrency $currency
490
     * @param Collection          $accounts
491
     * @param Carbon              $start
492
     * @param Carbon              $end
493
     *
494
     * @return JsonResponse
495
     */
496
    public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
497
    {
498
        // chart properties for cache:
499
        $cache = new CacheProperties();
500
        $cache->addProperty($start);
501
        $cache->addProperty($end);
502
        $cache->addProperty($accounts);
503
        $cache->addProperty($budget->id);
504
        $cache->addProperty($currency->id);
505
        $cache->addProperty('chart.budget.period');
506
        if ($cache->has()) {
507
            return response()->json($cache->get()); // @codeCoverageIgnore
508
        }
509
        $titleFormat    = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
510
        $preferredRange = app('navigation')->preferredRangeFormat($start, $end);
511
        $chartData      = [
512
            [
513
                'label'           => (string) trans('firefly.box_spent_in_currency', ['currency' => $currency->name]),
514
                'type'            => 'bar',
515
                'entries'         => [],
516
                'currency_symbol' => $currency->symbol,
517
                'currency_code'   => $currency->code,
518
            ],
519
            [
520
                'label'           => (string) trans('firefly.box_budgeted_in_currency', ['currency' => $currency->name]),
521
                'type'            => 'bar',
522
                'currency_symbol' => $currency->symbol,
523
                'currency_code'   => $currency->code,
524
                'entries'         => [],
525
            ],
526
        ];
527
528
        $currentStart = clone $start;
529
        while ($currentStart <= $end) {
530
            $currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange);
531
            $title        = $currentStart->formatLocalized($titleFormat);
532
            $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange);
533
534
            // default limit is no limit:
535
            $chartData[0]['entries'][$title] = 0;
536
537
            // default spent is not spent at all.
538
            $chartData[1]['entries'][$title] = 0;
539
540
            // get budget limit in this period for this currency.
541
            $limit = $this->blRepository->find($budget, $currency, $currentStart, $currentEnd);
542
            if (null !== $limit) {
543
                $chartData[1]['entries'][$title] = round($limit->amount, $currency->decimal_places);
544
            }
545
546
            // get spent amount in this period for this currency.
547
            $sum                             = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection([$budget]), $currency);
548
            $amount                          = app('steam')->positive($sum[$currency->id]['sum'] ?? '0');
549
            $chartData[0]['entries'][$title] = round($amount, $currency->decimal_places);
550
551
            $currentStart = clone $currentEnd;
552
            $currentStart->addDay()->startOfDay();
553
        }
554
555
        $data = $this->generator->multiSet($chartData);
556
        $cache->store($data);
557
558
        return response()->json($data);
559
    }
560
561
562
    /**
563
     * Shows a chart for transactions without a budget.
564
     *
565
     * @param TransactionCurrency $currency
566
     * @param Collection          $accounts
567
     * @param Carbon              $start
568
     * @param Carbon              $end
569
     *
570
     * @return JsonResponse
571
     */
572
    public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
573
    {
574
        // chart properties for cache:
575
        $cache = new CacheProperties();
576
        $cache->addProperty($start);
577
        $cache->addProperty($end);
578
        $cache->addProperty($accounts);
579
        $cache->addProperty($currency->id);
580
        $cache->addProperty('chart.budget.no-budget');
581
        if ($cache->has()) {
582
            return response()->json($cache->get()); // @codeCoverageIgnore
583
        }
584
585
        // the expenses:
586
        $titleFormat    = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
587
        $chartData      = [];
588
        $currentStart   = clone $start;
589
        $preferredRange = app('navigation')->preferredRangeFormat($start, $end);
590
        while ($currentStart <= $end) {
591
            $currentEnd        = app('navigation')->endOfPeriod($currentStart, $preferredRange);
592
            $title             = $currentStart->formatLocalized($titleFormat);
593
            $sum               = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency);
594
            $amount            = app('steam')->positive($sum[$currency->id]['sum'] ?? '0');
595
            $chartData[$title] = round($amount, $currency->decimal_places);
596
            $currentStart      = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
597
        }
598
599
        $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData);
600
        $cache->store($data);
601
602
        return response()->json($data);
603
    }
604
}
605