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
Bug
introduced
by
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 |