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 | // echo '<hr>'; |
||||||
115 | while ($end >= $loopStart) { |
||||||
116 | /** @var Carbon $currentEnd */ |
||||||
117 | $loopEnd = app('navigation')->endOfPeriod($loopStart, $step); |
||||||
118 | if ('1Y' === $step) { |
||||||
119 | $loopEnd->subDay(); // @codeCoverageIgnore |
||||||
120 | } |
||||||
121 | $spent = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection); |
||||||
122 | $label = trim(app('navigation')->periodShow($loopStart, $step)); |
||||||
123 | |||||||
124 | foreach ($spent as $row) { |
||||||
125 | $currencyId = $row['currency_id']; |
||||||
126 | $currencies[$currencyId] = $currencies[$currencyId] ?? $row; // don't mind the field 'sum' |
||||||
127 | // also store this day's sum: |
||||||
128 | $currencies[$currencyId]['spent'][$label] = $row['sum']; |
||||||
129 | } |
||||||
130 | $defaultEntries[$label] = 0; |
||||||
131 | // set loop start to the next period: |
||||||
132 | $loopStart = clone $loopEnd; |
||||||
133 | $loopStart->addSecond(); |
||||||
134 | } |
||||||
135 | // loop all currencies: |
||||||
136 | foreach ($currencies as $currencyId => $currency) { |
||||||
137 | $chartData[$currencyId] = [ |
||||||
138 | 'label' => count($currencies) > 1 ? sprintf('%s (%s)', $budget->name, $currency['currency_name']) : $budget->name, |
||||||
139 | 'type' => 'bar', |
||||||
140 | 'currency_symbol' => $currency['currency_symbol'], |
||||||
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 | |||||||
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')); |
||||||
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 | $cache->store($data); |
||||||
198 | |||||||
199 | return response()->json($data); |
||||||
200 | } |
||||||
201 | |||||||
202 | |||||||
203 | /** |
||||||
204 | * Shows how much is spent per asset account. |
||||||
205 | * |
||||||
206 | * @param Budget $budget |
||||||
207 | * @param BudgetLimit|null $budgetLimit |
||||||
208 | * |
||||||
209 | * @return JsonResponse |
||||||
210 | */ |
||||||
211 | public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse |
||||||
212 | { |
||||||
213 | /** @var GroupCollectorInterface $collector */ |
||||||
214 | $collector = app(GroupCollectorInterface::class); |
||||||
215 | $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id; |
||||||
216 | $cache = new CacheProperties; |
||||||
217 | $cache->addProperty($budget->id); |
||||||
218 | $cache->addProperty($budgetLimitId); |
||||||
219 | $cache->addProperty('chart.budget.expense-asset'); |
||||||
220 | $collector->setRange(session()->get('start'), session()->get('end')); |
||||||
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); |
||||||
0 ignored issues
–
show
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
227 | } |
||||||
228 | $cache->addProperty($start); |
||||||
229 | $cache->addProperty($end); |
||||||
230 | |||||||
231 | if ($cache->has()) { |
||||||
232 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
233 | } |
||||||
234 | $collector->setBudget($budget); |
||||||
235 | $journals = $collector->getExtractedJournals(); |
||||||
236 | $result = []; |
||||||
237 | $chartData = []; |
||||||
238 | |||||||
239 | // group by asset account ID: |
||||||
240 | foreach ($journals as $journal) { |
||||||
241 | $key = sprintf('%d-%d', (int)$journal['source_account_id'], $journal['currency_id']); |
||||||
242 | $result[$key] = $result[$key] ?? [ |
||||||
243 | 'amount' => '0', |
||||||
244 | 'currency_symbol' => $journal['currency_symbol'], |
||||||
245 | 'currency_name' => $journal['currency_name'], |
||||||
246 | ]; |
||||||
247 | $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); |
||||||
248 | } |
||||||
249 | |||||||
250 | $names = $this->getAccountNames(array_keys($result)); |
||||||
251 | foreach ($result as $combinedId => $info) { |
||||||
252 | $parts = explode('-', $combinedId); |
||||||
253 | $assetId = (int)$parts[0]; |
||||||
254 | $title = sprintf('%s (%s)', $names[$assetId] ?? '(empty)', $info['currency_name']); |
||||||
255 | $chartData[$title] |
||||||
256 | = [ |
||||||
257 | 'amount' => $info['amount'], |
||||||
258 | 'currency_symbol' => $info['currency_symbol'], |
||||||
259 | ]; |
||||||
260 | } |
||||||
261 | |||||||
262 | $data = $this->generator->multiCurrencyPieChart($chartData); |
||||||
263 | $cache->store($data); |
||||||
264 | |||||||
265 | return response()->json($data); |
||||||
266 | } |
||||||
267 | |||||||
268 | |||||||
269 | /** |
||||||
270 | * Shows how much is spent per category. |
||||||
271 | * |
||||||
272 | * @param Budget $budget |
||||||
273 | * @param BudgetLimit|null $budgetLimit |
||||||
274 | * |
||||||
275 | * @return JsonResponse |
||||||
276 | */ |
||||||
277 | public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse |
||||||
278 | { |
||||||
279 | /** @var GroupCollectorInterface $collector */ |
||||||
280 | $collector = app(GroupCollectorInterface::class); |
||||||
281 | $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id; |
||||||
282 | $cache = new CacheProperties; |
||||||
283 | $cache->addProperty($budget->id); |
||||||
284 | $cache->addProperty($budgetLimitId); |
||||||
285 | $cache->addProperty('chart.budget.expense-category'); |
||||||
286 | $collector->setRange(session()->get('start'), session()->get('end')); |
||||||
287 | $start = session()->get('start'); |
||||||
288 | $end = session()->get('end'); |
||||||
289 | if (null !== $budgetLimit) { |
||||||
290 | $start = $budgetLimit->start_date; |
||||||
291 | $end = $budgetLimit->end_date; |
||||||
292 | $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency); |
||||||
0 ignored issues
–
show
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
293 | } |
||||||
294 | $cache->addProperty($start); |
||||||
295 | $cache->addProperty($end); |
||||||
296 | |||||||
297 | if ($cache->has()) { |
||||||
298 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
299 | } |
||||||
300 | $collector->setBudget($budget)->withCategoryInformation(); |
||||||
301 | $journals = $collector->getExtractedJournals(); |
||||||
302 | $result = []; |
||||||
303 | $chartData = []; |
||||||
304 | foreach ($journals as $journal) { |
||||||
305 | $key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']); |
||||||
306 | $result[$key] = $result[$key] ?? [ |
||||||
307 | 'amount' => '0', |
||||||
308 | 'currency_symbol' => $journal['currency_symbol'], |
||||||
309 | 'currency_name' => $journal['currency_name'], |
||||||
310 | ]; |
||||||
311 | $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); |
||||||
312 | } |
||||||
313 | |||||||
314 | $names = $this->getCategoryNames(array_keys($result)); |
||||||
315 | foreach ($result as $combinedId => $info) { |
||||||
316 | $parts = explode('-', $combinedId); |
||||||
317 | $categoryId = (int)$parts[0]; |
||||||
318 | $title = sprintf('%s (%s)', $names[$categoryId] ?? '(empty)', $info['currency_name']); |
||||||
319 | $chartData[$title] = [ |
||||||
320 | 'amount' => $info['amount'], |
||||||
321 | 'currency_symbol' => $info['currency_symbol'], |
||||||
322 | ]; |
||||||
323 | } |
||||||
324 | $data = $this->generator->multiCurrencyPieChart($chartData); |
||||||
325 | $cache->store($data); |
||||||
326 | |||||||
327 | return response()->json($data); |
||||||
328 | } |
||||||
329 | |||||||
330 | |||||||
331 | /** |
||||||
332 | * Shows how much is spent per expense account. |
||||||
333 | * |
||||||
334 | * |
||||||
335 | * @param Budget $budget |
||||||
336 | * @param BudgetLimit|null $budgetLimit |
||||||
337 | * |
||||||
338 | * @return JsonResponse |
||||||
339 | */ |
||||||
340 | public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse |
||||||
341 | { |
||||||
342 | /** @var GroupCollectorInterface $collector */ |
||||||
343 | $collector = app(GroupCollectorInterface::class); |
||||||
344 | $budgetLimitId = null === $budgetLimit ? 0 : $budgetLimit->id; |
||||||
345 | $cache = new CacheProperties; |
||||||
346 | $cache->addProperty($budget->id); |
||||||
347 | $cache->addProperty($budgetLimitId); |
||||||
348 | $cache->addProperty('chart.budget.expense-expense'); |
||||||
349 | $collector->setRange(session()->get('start'), session()->get('end')); |
||||||
350 | $start = session()->get('start'); |
||||||
351 | $end = session()->get('end'); |
||||||
352 | if (null !== $budgetLimit) { |
||||||
353 | $start = $budgetLimit->start_date; |
||||||
354 | $end = $budgetLimit->end_date; |
||||||
355 | $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date)->setCurrency($budgetLimit->transactionCurrency); |
||||||
0 ignored issues
–
show
$budgetLimit->end_date of type string is incompatible with the type Carbon\Carbon expected by parameter $end of FireflyIII\Helpers\Colle...orInterface::setRange() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
356 | } |
||||||
357 | $cache->addProperty($start); |
||||||
358 | $cache->addProperty($end); |
||||||
359 | |||||||
360 | if ($cache->has()) { |
||||||
361 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
362 | } |
||||||
363 | |||||||
364 | $collector->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withAccountInformation(); |
||||||
365 | $journals = $collector->getExtractedJournals(); |
||||||
366 | $result = []; |
||||||
367 | $chartData = []; |
||||||
368 | /** @var array $journal */ |
||||||
369 | foreach ($journals as $journal) { |
||||||
370 | $key = sprintf('%d-%d', $journal['destination_account_id'], $journal['currency_id']); |
||||||
371 | $result[$key] = $result[$key] ?? [ |
||||||
372 | 'amount' => '0', |
||||||
373 | 'currency_symbol' => $journal['currency_symbol'], |
||||||
374 | 'currency_name' => $journal['currency_name'], |
||||||
375 | ]; |
||||||
376 | $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); |
||||||
377 | } |
||||||
378 | |||||||
379 | $names = $this->getAccountNames(array_keys($result)); |
||||||
380 | foreach ($result as $combinedId => $info) { |
||||||
381 | $parts = explode('-', $combinedId); |
||||||
382 | $opposingId = (int)$parts[0]; |
||||||
383 | $name = $names[$opposingId] ?? 'no name'; |
||||||
384 | $title = sprintf('%s (%s)', $name, $info['currency_name']); |
||||||
385 | $chartData[$title] = [ |
||||||
386 | 'amount' => $info['amount'], |
||||||
387 | 'currency_symbol' => $info['currency_symbol'], |
||||||
388 | ]; |
||||||
389 | } |
||||||
390 | |||||||
391 | $data = $this->generator->multiCurrencyPieChart($chartData); |
||||||
392 | $cache->store($data); |
||||||
393 | |||||||
394 | return response()->json($data); |
||||||
395 | } |
||||||
396 | |||||||
397 | |||||||
398 | /** |
||||||
399 | * Shows a budget list with spent/left/overspent. |
||||||
400 | * |
||||||
401 | * TODO there are cases when this chart hides expenses: when budget has limits |
||||||
402 | * and limits are found and used, but the expense is in another currency. |
||||||
403 | * |
||||||
404 | * @return JsonResponse |
||||||
405 | * |
||||||
406 | */ |
||||||
407 | public function frontpage(): JsonResponse |
||||||
408 | { |
||||||
409 | $start = session('start', Carbon::now()->startOfMonth()); |
||||||
410 | $end = session('end', Carbon::now()->endOfMonth()); |
||||||
411 | // chart properties for cache: |
||||||
412 | $cache = new CacheProperties(); |
||||||
413 | $cache->addProperty($start); |
||||||
414 | $cache->addProperty($end); |
||||||
415 | $cache->addProperty('chart.budget.frontpage'); |
||||||
416 | if ($cache->has()) { |
||||||
417 | // return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
418 | } |
||||||
419 | $budgets = $this->repository->getActiveBudgets(); |
||||||
420 | $chartData = [ |
||||||
421 | ['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'], |
||||||
422 | ['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'], |
||||||
423 | ['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'], |
||||||
424 | ]; |
||||||
425 | |||||||
426 | /** @var Budget $budget */ |
||||||
427 | foreach ($budgets as $budget) { |
||||||
428 | $limits = $this->blRepository->getBudgetLimits($budget, $start, $end); |
||||||
0 ignored issues
–
show
It seems like
$start can also be of type Illuminate\Session\SessionManager and Illuminate\Session\Store ; however, parameter $start of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon|null , 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
Loading history...
It seems like
$end can also be of type Illuminate\Session\SessionManager and Illuminate\Session\Store ; however, parameter $end of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon|null , 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
Loading history...
|
|||||||
429 | if (0 === $limits->count()) { |
||||||
430 | $spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget]), null); |
||||||
0 ignored issues
–
show
It seems like
$end can also be of type Illuminate\Session\SessionManager and Illuminate\Session\Store ; however, parameter $end of FireflyIII\Repositories\...nterface::sumExpenses() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
It seems like
$start can also be of type Illuminate\Session\SessionManager and Illuminate\Session\Store ; however, parameter $start of FireflyIII\Repositories\...nterface::sumExpenses() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
431 | /** @var array $entry */ |
||||||
432 | foreach ($spent as $entry) { |
||||||
433 | $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']); |
||||||
434 | $chartData[0]['entries'][$title] = bcmul($entry['sum'], '-1'); // spent |
||||||
435 | $chartData[1]['entries'][$title] = 0; // left to spend |
||||||
436 | $chartData[2]['entries'][$title] = 0; // overspent |
||||||
437 | } |
||||||
438 | } |
||||||
439 | if (0 !== $limits->count()) { |
||||||
440 | /** @var BudgetLimit $limit */ |
||||||
441 | foreach ($limits as $limit) { |
||||||
442 | $spent = $this->opsRepository->sumExpenses( |
||||||
443 | $limit->start_date, |
||||||
444 | $limit->end_date, |
||||||
445 | null, |
||||||
446 | new Collection([$budget]), |
||||||
447 | $limit->transactionCurrency |
||||||
448 | ); |
||||||
449 | /** @var array $entry */ |
||||||
450 | foreach ($spent as $entry) { |
||||||
451 | $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']); |
||||||
452 | if ($limit->start_date->startOfDay()->ne($start->startOfDay()) || $limit->end_date->startOfDay()->ne($end->startOfDay())) { |
||||||
453 | $title = sprintf( |
||||||
454 | '%s (%s) (%s - %s)', |
||||||
455 | $budget->name, |
||||||
456 | $entry['currency_name'], |
||||||
457 | $limit->start_date->formatLocalized($this->monthAndDayFormat), |
||||||
458 | $limit->end_date->formatLocalized($this->monthAndDayFormat) |
||||||
459 | ); |
||||||
460 | } |
||||||
461 | $sumSpent = bcmul($entry['sum'], '-1'); // spent |
||||||
462 | $chartData[0]['entries'][$title] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent; |
||||||
463 | $chartData[1]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($entry['sum'], $limit->amount) |
||||||
464 | : '0'; |
||||||
465 | $chartData[2]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? |
||||||
466 | '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1'); |
||||||
467 | } |
||||||
468 | } |
||||||
469 | } |
||||||
470 | } |
||||||
471 | $data = $this->generator->multiSet($chartData); |
||||||
472 | $cache->store($data); |
||||||
473 | |||||||
474 | return response()->json($data); |
||||||
475 | } |
||||||
476 | |||||||
477 | |||||||
478 | /** |
||||||
479 | * Shows a budget overview chart (spent and budgeted). |
||||||
480 | * |
||||||
481 | * @param Budget $budget |
||||||
482 | * @param TransactionCurrency $currency |
||||||
483 | * @param Collection $accounts |
||||||
484 | * @param Carbon $start |
||||||
485 | * @param Carbon $end |
||||||
486 | * |
||||||
487 | * @return JsonResponse |
||||||
488 | */ |
||||||
489 | public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse |
||||||
490 | { |
||||||
491 | // chart properties for cache: |
||||||
492 | $cache = new CacheProperties(); |
||||||
493 | $cache->addProperty($start); |
||||||
494 | $cache->addProperty($end); |
||||||
495 | $cache->addProperty($accounts); |
||||||
496 | $cache->addProperty($budget->id); |
||||||
497 | $cache->addProperty($currency->id); |
||||||
498 | $cache->addProperty('chart.budget.period'); |
||||||
499 | if ($cache->has()) { |
||||||
500 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
501 | } |
||||||
502 | $titleFormat = app('navigation')->preferredCarbonLocalizedFormat($start, $end); |
||||||
503 | $preferredRange = app('navigation')->preferredRangeFormat($start, $end); |
||||||
504 | $chartData = [ |
||||||
505 | [ |
||||||
506 | 'label' => (string)trans('firefly.box_spent_in_currency', ['currency' => $currency->name]), |
||||||
507 | 'type' => 'bar', |
||||||
508 | 'entries' => [], |
||||||
509 | 'currency_symbol' => $currency->symbol, |
||||||
510 | ], |
||||||
511 | [ |
||||||
512 | 'label' => (string)trans('firefly.box_budgeted_in_currency', ['currency' => $currency->name]), |
||||||
513 | 'type' => 'bar', |
||||||
514 | 'currency_symbol' => $currency->symbol, |
||||||
515 | 'entries' => [], |
||||||
516 | ], |
||||||
517 | ]; |
||||||
518 | |||||||
519 | $currentStart = clone $start; |
||||||
520 | while ($currentStart <= $end) { |
||||||
521 | $currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange); |
||||||
522 | $title = $currentStart->formatLocalized($titleFormat); |
||||||
523 | $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); |
||||||
524 | |||||||
525 | // default limit is no limit: |
||||||
526 | $chartData[0]['entries'][$title] = 0; |
||||||
527 | |||||||
528 | // default spent is not spent at all. |
||||||
529 | $chartData[1]['entries'][$title] = 0; |
||||||
530 | |||||||
531 | // get budget limit in this period for this currency. |
||||||
532 | $limit = $this->blRepository->find($budget, $currency, $currentStart, $currentEnd); |
||||||
533 | if (null !== $limit) { |
||||||
534 | $chartData[1]['entries'][$title] = round($limit->amount, $currency->decimal_places); |
||||||
0 ignored issues
–
show
$limit->amount 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
Loading history...
|
|||||||
535 | } |
||||||
536 | |||||||
537 | // get spent amount in this period for this currency. |
||||||
538 | $sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection([$budget]), $currency); |
||||||
539 | $amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0'); |
||||||
540 | $chartData[0]['entries'][$title] = round($amount, $currency->decimal_places); |
||||||
541 | |||||||
542 | $currentStart = clone $currentEnd; |
||||||
543 | $currentStart->addDay()->startOfDay(); |
||||||
544 | } |
||||||
545 | |||||||
546 | $data = $this->generator->multiSet($chartData); |
||||||
547 | $cache->store($data); |
||||||
548 | |||||||
549 | return response()->json($data); |
||||||
550 | } |
||||||
551 | |||||||
552 | |||||||
553 | /** |
||||||
554 | * Shows a chart for transactions without a budget. |
||||||
555 | * |
||||||
556 | * @param TransactionCurrency $currency |
||||||
557 | * @param Collection $accounts |
||||||
558 | * @param Carbon $start |
||||||
559 | * @param Carbon $end |
||||||
560 | * |
||||||
561 | * @return JsonResponse |
||||||
562 | */ |
||||||
563 | public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse |
||||||
564 | { |
||||||
565 | // chart properties for cache: |
||||||
566 | $cache = new CacheProperties(); |
||||||
567 | $cache->addProperty($start); |
||||||
568 | $cache->addProperty($end); |
||||||
569 | $cache->addProperty($accounts); |
||||||
570 | $cache->addProperty($currency->id); |
||||||
571 | $cache->addProperty('chart.budget.no-budget'); |
||||||
572 | if ($cache->has()) { |
||||||
573 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
574 | } |
||||||
575 | |||||||
576 | // the expenses: |
||||||
577 | $titleFormat = app('navigation')->preferredCarbonLocalizedFormat($start, $end); |
||||||
578 | $chartData = []; |
||||||
579 | $currentStart = clone $start; |
||||||
580 | $preferredRange = app('navigation')->preferredRangeFormat($start, $end); |
||||||
581 | while ($currentStart <= $end) { |
||||||
582 | $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); |
||||||
583 | $title = $currentStart->formatLocalized($titleFormat); |
||||||
584 | $sum = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency); |
||||||
585 | $amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0'); |
||||||
586 | $chartData[$title] = round($amount, $currency->decimal_places); |
||||||
0 ignored issues
–
show
$amount 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
Loading history...
|
|||||||
587 | $currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0); |
||||||
588 | } |
||||||
589 | |||||||
590 | $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData); |
||||||
591 | $cache->store($data); |
||||||
592 | |||||||
593 | return response()->json($data); |
||||||
594 | } |
||||||
595 | } |
||||||
596 |