1 | <?php |
||||||
2 | /** |
||||||
3 | * BudgetController.php |
||||||
4 | * Copyright (c) 2017 [email protected] |
||||||
5 | * |
||||||
6 | * This file is part of Firefly III. |
||||||
7 | * |
||||||
8 | * Firefly III is free software: you can redistribute it and/or modify |
||||||
9 | * it under the terms of the GNU General Public License as published by |
||||||
10 | * the Free Software Foundation, either version 3 of the License, or |
||||||
11 | * (at your option) any later version. |
||||||
12 | * |
||||||
13 | * Firefly III is distributed in the hope that it will be useful, |
||||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
16 | * GNU General Public License for more details. |
||||||
17 | * |
||||||
18 | * You should have received a copy of the GNU General Public License |
||||||
19 | * along with Firefly III. If not, see <http://www.gnu.org/licenses/>. |
||||||
20 | */ |
||||||
21 | declare(strict_types=1); |
||||||
22 | |||||||
23 | namespace FireflyIII\Http\Controllers\Chart; |
||||||
24 | |||||||
25 | use Carbon\Carbon; |
||||||
26 | use FireflyIII\Exceptions\FireflyException; |
||||||
27 | use FireflyIII\Generator\Chart\Basic\GeneratorInterface; |
||||||
28 | use FireflyIII\Helpers\Collector\JournalCollectorInterface; |
||||||
29 | use FireflyIII\Http\Controllers\Controller; |
||||||
30 | use FireflyIII\Models\AccountType; |
||||||
31 | use FireflyIII\Models\Budget; |
||||||
32 | use FireflyIII\Models\BudgetLimit; |
||||||
33 | use FireflyIII\Models\Transaction; |
||||||
34 | use FireflyIII\Models\TransactionType; |
||||||
35 | use FireflyIII\Repositories\Account\AccountRepositoryInterface; |
||||||
36 | use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; |
||||||
37 | use FireflyIII\Repositories\Category\CategoryRepositoryInterface; |
||||||
38 | use FireflyIII\Support\CacheProperties; |
||||||
39 | use Illuminate\Support\Collection; |
||||||
40 | use Steam; |
||||||
41 | |||||||
42 | /** |
||||||
43 | * Class BudgetController. |
||||||
44 | * |
||||||
45 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // can't realy be helped. |
||||||
46 | */ |
||||||
47 | class BudgetController extends Controller |
||||||
48 | { |
||||||
49 | /** @var GeneratorInterface */ |
||||||
50 | protected $generator; |
||||||
51 | |||||||
52 | /** @var BudgetRepositoryInterface */ |
||||||
53 | protected $repository; |
||||||
54 | |||||||
55 | /** |
||||||
56 | * BudgetController constructor. |
||||||
57 | */ |
||||||
58 | public function __construct() |
||||||
59 | { |
||||||
60 | parent::__construct(); |
||||||
61 | |||||||
62 | $this->middleware( |
||||||
63 | function ($request, $next) { |
||||||
64 | $this->generator = app(GeneratorInterface::class); |
||||||
65 | $this->repository = app(BudgetRepositoryInterface::class); |
||||||
66 | |||||||
67 | return $next($request); |
||||||
68 | } |
||||||
69 | ); |
||||||
70 | } |
||||||
71 | |||||||
72 | /** |
||||||
73 | * @param Budget $budget |
||||||
74 | * |
||||||
75 | * @return \Symfony\Component\HttpFoundation\Response |
||||||
76 | */ |
||||||
77 | public function budget(Budget $budget) |
||||||
78 | { |
||||||
79 | $start = $this->repository->firstUseDate($budget); |
||||||
80 | $end = session('end', new Carbon); |
||||||
81 | $cache = new CacheProperties(); |
||||||
82 | $cache->addProperty($start); |
||||||
83 | $cache->addProperty($end); |
||||||
84 | $cache->addProperty('chart.budget.budget'); |
||||||
85 | $cache->addProperty($budget->id); |
||||||
86 | |||||||
87 | if ($cache->has()) { |
||||||
88 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
89 | } |
||||||
90 | |||||||
91 | // depending on diff, do something with range of chart. |
||||||
92 | $step = '1D'; |
||||||
93 | $months = $start->diffInMonths($end); |
||||||
94 | if ($months > 3) { |
||||||
95 | $step = '1W'; |
||||||
96 | } |
||||||
97 | if ($months > 24) { |
||||||
98 | $step = '1M'; |
||||||
99 | } |
||||||
100 | if ($months > 60) { |
||||||
101 | $step = '1Y'; // @codeCoverageIgnore |
||||||
102 | } |
||||||
103 | $budgetCollection = new Collection([$budget]); |
||||||
104 | $chartData = []; |
||||||
105 | $current = clone $start; |
||||||
106 | $current = app('navigation')->startOfPeriod($current, $step); |
||||||
0 ignored issues
–
show
|
|||||||
107 | |||||||
108 | while ($end >= $current) { |
||||||
109 | $currentEnd = app('navigation')->endOfPeriod($current, $step); |
||||||
0 ignored issues
–
show
The method
endOfPeriod() does not exist on FireflyIII\Support\Facades\Navigation .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
110 | if ($step === '1Y') { |
||||||
111 | $currentEnd->subDay(); // @codeCoverageIgnore |
||||||
112 | } |
||||||
113 | $spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $current, $currentEnd); |
||||||
114 | $label = app('navigation')->periodShow($current, $step); |
||||||
0 ignored issues
–
show
The method
periodShow() does not exist on FireflyIII\Support\Facades\Navigation .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
115 | $chartData[$label] = (float)bcmul($spent, '-1'); |
||||||
116 | $current = clone $currentEnd; |
||||||
117 | $current->addDay(); |
||||||
118 | } |
||||||
119 | |||||||
120 | $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData); |
||||||
121 | |||||||
122 | $cache->store($data); |
||||||
123 | |||||||
124 | return response()->json($data); |
||||||
125 | } |
||||||
126 | |||||||
127 | /** |
||||||
128 | * Shows the amount left in a specific budget limit. |
||||||
129 | * |
||||||
130 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. |
||||||
131 | * |
||||||
132 | * @param Budget $budget |
||||||
133 | * @param BudgetLimit $budgetLimit |
||||||
134 | * |
||||||
135 | * @return \Symfony\Component\HttpFoundation\Response |
||||||
136 | * |
||||||
137 | * @throws FireflyException |
||||||
138 | */ |
||||||
139 | public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit) |
||||||
140 | { |
||||||
141 | if ($budgetLimit->budget->id !== $budget->id) { |
||||||
142 | throw new FireflyException('This budget limit is not part of this budget.'); |
||||||
143 | } |
||||||
144 | |||||||
145 | $start = clone $budgetLimit->start_date; |
||||||
0 ignored issues
–
show
|
|||||||
146 | $end = clone $budgetLimit->end_date; |
||||||
147 | $cache = new CacheProperties(); |
||||||
148 | $cache->addProperty($start); |
||||||
149 | $cache->addProperty($end); |
||||||
150 | $cache->addProperty('chart.budget.budget.limit'); |
||||||
151 | $cache->addProperty($budgetLimit->id); |
||||||
152 | $cache->addProperty($budget->id); |
||||||
153 | |||||||
154 | if ($cache->has()) { |
||||||
155 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
156 | } |
||||||
157 | |||||||
158 | $entries = []; |
||||||
159 | $amount = $budgetLimit->amount; |
||||||
0 ignored issues
–
show
|
|||||||
160 | $budgetCollection = new Collection([$budget]); |
||||||
161 | while ($start <= $end) { |
||||||
162 | $spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $start, $start); |
||||||
163 | $amount = bcadd($amount, $spent); |
||||||
164 | $format = $start->formatLocalized((string)trans('config.month_and_day')); |
||||||
165 | $entries[$format] = $amount; |
||||||
166 | |||||||
167 | $start->addDay(); |
||||||
168 | } |
||||||
169 | $data = $this->generator->singleSet((string)trans('firefly.left'), $entries); |
||||||
170 | $cache->store($data); |
||||||
171 | |||||||
172 | return response()->json($data); |
||||||
173 | } |
||||||
174 | |||||||
175 | /** |
||||||
176 | * @param Budget $budget |
||||||
177 | * @param BudgetLimit|null $budgetLimit |
||||||
178 | * |
||||||
179 | * @return \Illuminate\Http\JsonResponse |
||||||
180 | */ |
||||||
181 | public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit) |
||||||
182 | { |
||||||
183 | $cache = new CacheProperties; |
||||||
184 | $cache->addProperty($budget->id); |
||||||
185 | $cache->addProperty($budgetLimit->id ?? 0); |
||||||
186 | $cache->addProperty('chart.budget.expense-asset'); |
||||||
187 | if ($cache->has()) { |
||||||
188 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
189 | } |
||||||
190 | |||||||
191 | /** @var JournalCollectorInterface $collector */ |
||||||
192 | $collector = app(JournalCollectorInterface::class); |
||||||
193 | $collector->setAllAssetAccounts()->setBudget($budget); |
||||||
194 | if (null !== $budgetLimit->id) { |
||||||
195 | $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); |
||||||
0 ignored issues
–
show
$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
![]() |
|||||||
196 | } |
||||||
197 | |||||||
198 | $transactions = $collector->getJournals(); |
||||||
199 | $result = []; |
||||||
200 | $chartData = []; |
||||||
201 | /** @var Transaction $transaction */ |
||||||
202 | foreach ($transactions as $transaction) { |
||||||
203 | $assetId = (int)$transaction->account_id; |
||||||
204 | $result[$assetId] = $result[$assetId] ?? '0'; |
||||||
205 | $result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]); |
||||||
206 | } |
||||||
207 | |||||||
208 | $names = $this->getAccountNames(array_keys($result)); |
||||||
209 | foreach ($result as $assetId => $amount) { |
||||||
210 | $chartData[$names[$assetId]] = $amount; |
||||||
211 | } |
||||||
212 | |||||||
213 | $data = $this->generator->pieChart($chartData); |
||||||
214 | $cache->store($data); |
||||||
215 | |||||||
216 | return response()->json($data); |
||||||
217 | } |
||||||
218 | |||||||
219 | /** |
||||||
220 | * @param Budget $budget |
||||||
221 | * @param BudgetLimit|null $budgetLimit |
||||||
222 | * |
||||||
223 | * @return \Illuminate\Http\JsonResponse |
||||||
224 | */ |
||||||
225 | public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit) |
||||||
226 | { |
||||||
227 | $cache = new CacheProperties; |
||||||
228 | $cache->addProperty($budget->id); |
||||||
229 | $cache->addProperty($budgetLimit->id ?? 0); |
||||||
230 | $cache->addProperty('chart.budget.expense-category'); |
||||||
231 | if ($cache->has()) { |
||||||
232 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
233 | } |
||||||
234 | |||||||
235 | /** @var JournalCollectorInterface $collector */ |
||||||
236 | $collector = app(JournalCollectorInterface::class); |
||||||
237 | $collector->setAllAssetAccounts()->setBudget($budget)->withCategoryInformation(); |
||||||
238 | if (null !== $budgetLimit->id) { |
||||||
239 | $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); |
||||||
0 ignored issues
–
show
$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
![]() |
|||||||
240 | } |
||||||
241 | |||||||
242 | $transactions = $collector->getJournals(); |
||||||
243 | $result = []; |
||||||
244 | $chartData = []; |
||||||
245 | /** @var Transaction $transaction */ |
||||||
246 | foreach ($transactions as $transaction) { |
||||||
247 | $jrnlCatId = (int)$transaction->transaction_journal_category_id; |
||||||
248 | $transCatId = (int)$transaction->transaction_category_id; |
||||||
249 | $categoryId = max($jrnlCatId, $transCatId); |
||||||
250 | $result[$categoryId] = $result[$categoryId] ?? '0'; |
||||||
251 | $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); |
||||||
252 | } |
||||||
253 | |||||||
254 | $names = $this->getCategoryNames(array_keys($result)); |
||||||
255 | foreach ($result as $categoryId => $amount) { |
||||||
256 | $chartData[$names[$categoryId]] = $amount; |
||||||
257 | } |
||||||
258 | |||||||
259 | $data = $this->generator->pieChart($chartData); |
||||||
260 | $cache->store($data); |
||||||
261 | |||||||
262 | return response()->json($data); |
||||||
263 | } |
||||||
264 | |||||||
265 | /** |
||||||
266 | * @param Budget $budget |
||||||
267 | * @param BudgetLimit|null $budgetLimit |
||||||
268 | * |
||||||
269 | * @return \Illuminate\Http\JsonResponse |
||||||
270 | */ |
||||||
271 | public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit) |
||||||
272 | { |
||||||
273 | $cache = new CacheProperties; |
||||||
274 | $cache->addProperty($budget->id); |
||||||
275 | $cache->addProperty($budgetLimit->id ?? 0); |
||||||
276 | $cache->addProperty('chart.budget.expense-expense'); |
||||||
277 | if ($cache->has()) { |
||||||
278 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
279 | } |
||||||
280 | |||||||
281 | /** @var JournalCollectorInterface $collector */ |
||||||
282 | $collector = app(JournalCollectorInterface::class); |
||||||
283 | $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount(); |
||||||
284 | if (null !== $budgetLimit->id) { |
||||||
285 | $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); |
||||||
0 ignored issues
–
show
$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
![]() |
|||||||
286 | } |
||||||
287 | |||||||
288 | $transactions = $collector->getJournals(); |
||||||
289 | $result = []; |
||||||
290 | $chartData = []; |
||||||
291 | /** @var Transaction $transaction */ |
||||||
292 | foreach ($transactions as $transaction) { |
||||||
293 | $opposingId = (int)$transaction->opposing_account_id; |
||||||
294 | $result[$opposingId] = $result[$opposingId] ?? '0'; |
||||||
295 | $result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]); |
||||||
296 | } |
||||||
297 | |||||||
298 | $names = $this->getAccountNames(array_keys($result)); |
||||||
299 | foreach ($result as $opposingId => $amount) { |
||||||
300 | $name = $names[$opposingId] ?? 'no name'; |
||||||
301 | $chartData[$name] = $amount; |
||||||
302 | } |
||||||
303 | |||||||
304 | $data = $this->generator->pieChart($chartData); |
||||||
305 | $cache->store($data); |
||||||
306 | |||||||
307 | return response()->json($data); |
||||||
308 | } |
||||||
309 | |||||||
310 | /** |
||||||
311 | * Shows a budget list with spent/left/overspent. |
||||||
312 | * |
||||||
313 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. |
||||||
314 | * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // 46 lines, I'm fine with this. |
||||||
315 | * |
||||||
316 | * @return \Symfony\Component\HttpFoundation\Response |
||||||
317 | */ |
||||||
318 | public function frontpage() |
||||||
319 | { |
||||||
320 | $start = session('start', Carbon::now()->startOfMonth()); |
||||||
321 | $end = session('end', Carbon::now()->endOfMonth()); |
||||||
322 | // chart properties for cache: |
||||||
323 | $cache = new CacheProperties(); |
||||||
324 | $cache->addProperty($start); |
||||||
325 | $cache->addProperty($end); |
||||||
326 | $cache->addProperty('chart.budget.frontpage'); |
||||||
327 | if ($cache->has()) { |
||||||
328 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
329 | } |
||||||
330 | $budgets = $this->repository->getActiveBudgets(); |
||||||
331 | $chartData = [ |
||||||
332 | ['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'], |
||||||
333 | ['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'], |
||||||
334 | ['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'], |
||||||
335 | ]; |
||||||
336 | |||||||
337 | /** @var Budget $budget */ |
||||||
338 | foreach ($budgets as $budget) { |
||||||
339 | // get relevant repetitions: |
||||||
340 | $limits = $this->repository->getBudgetLimits($budget, $start, $end); |
||||||
0 ignored issues
–
show
It seems like
$start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $start of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $end of FireflyIII\Repositories\...face::getBudgetLimits() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
341 | $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end); |
||||||
0 ignored issues
–
show
It seems like
$start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $start of FireflyIII\Http\Controll...:getExpensesForBudget() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $end of FireflyIII\Http\Controll...:getExpensesForBudget() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
342 | |||||||
343 | foreach ($expenses as $name => $row) { |
||||||
344 | $chartData[0]['entries'][$name] = $row['spent']; |
||||||
345 | $chartData[1]['entries'][$name] = $row['left']; |
||||||
346 | $chartData[2]['entries'][$name] = $row['overspent']; |
||||||
347 | } |
||||||
348 | } |
||||||
349 | // for no budget: |
||||||
350 | $spent = $this->spentInPeriodWithout($start, $end); |
||||||
0 ignored issues
–
show
It seems like
$end can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $end of FireflyIII\Http\Controll...:spentInPeriodWithout() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$start can also be of type Illuminate\Session\Store and Illuminate\Session\SessionManager ; however, parameter $start of FireflyIII\Http\Controll...:spentInPeriodWithout() does only seem to accept Carbon\Carbon , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
351 | $name = (string)trans('firefly.no_budget'); |
||||||
352 | if (0 !== bccomp($spent, '0')) { |
||||||
353 | $chartData[0]['entries'][$name] = bcmul($spent, '-1'); |
||||||
354 | $chartData[1]['entries'][$name] = '0'; |
||||||
355 | $chartData[2]['entries'][$name] = '0'; |
||||||
356 | } |
||||||
357 | |||||||
358 | $data = $this->generator->multiSet($chartData); |
||||||
359 | $cache->store($data); |
||||||
360 | |||||||
361 | return response()->json($data); |
||||||
362 | } |
||||||
363 | |||||||
364 | /** |
||||||
365 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. |
||||||
366 | * |
||||||
367 | * @param Budget $budget |
||||||
368 | * @param Carbon $start |
||||||
369 | * @param Carbon $end |
||||||
370 | * @param Collection $accounts |
||||||
371 | * |
||||||
372 | * @return \Illuminate\Http\JsonResponse |
||||||
373 | */ |
||||||
374 | public function period(Budget $budget, Collection $accounts, Carbon $start, Carbon $end) |
||||||
375 | { |
||||||
376 | // chart properties for cache: |
||||||
377 | $cache = new CacheProperties(); |
||||||
378 | $cache->addProperty($start); |
||||||
379 | $cache->addProperty($end); |
||||||
380 | $cache->addProperty($accounts); |
||||||
381 | $cache->addProperty($budget->id); |
||||||
382 | $cache->addProperty('chart.budget.period'); |
||||||
383 | if ($cache->has()) { |
||||||
384 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
385 | } |
||||||
386 | $periods = app('navigation')->listOfPeriods($start, $end); |
||||||
0 ignored issues
–
show
The method
listOfPeriods() does not exist on FireflyIII\Support\Facades\Navigation .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
387 | $entries = $this->repository->getBudgetPeriodReport(new Collection([$budget]), $accounts, $start, $end); // get the expenses |
||||||
388 | $budgeted = $this->getBudgetedInPeriod($budget, $start, $end); |
||||||
389 | |||||||
390 | // join them into one set of data: |
||||||
391 | $chartData = [ |
||||||
392 | ['label' => (string)trans('firefly.spent'), 'type' => 'bar', 'entries' => []], |
||||||
393 | ['label' => (string)trans('firefly.budgeted'), 'type' => 'bar', 'entries' => []], |
||||||
394 | ]; |
||||||
395 | |||||||
396 | foreach (array_keys($periods) as $period) { |
||||||
397 | $label = $periods[$period]; |
||||||
398 | $spent = $entries[$budget->id]['entries'][$period] ?? '0'; |
||||||
399 | $limit = (int)($budgeted[$period] ?? 0); |
||||||
400 | $chartData[0]['entries'][$label] = round(bcmul($spent, '-1'), 12); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
401 | $chartData[1]['entries'][$label] = $limit; |
||||||
402 | } |
||||||
403 | $data = $this->generator->multiSet($chartData); |
||||||
404 | $cache->store($data); |
||||||
405 | |||||||
406 | return response()->json($data); |
||||||
407 | } |
||||||
408 | |||||||
409 | /** |
||||||
410 | * @param Collection $accounts |
||||||
411 | * @param Carbon $start |
||||||
412 | * @param Carbon $end |
||||||
413 | * |
||||||
414 | * @return \Illuminate\Http\JsonResponse |
||||||
415 | */ |
||||||
416 | public function periodNoBudget(Collection $accounts, Carbon $start, Carbon $end) |
||||||
417 | { |
||||||
418 | // chart properties for cache: |
||||||
419 | $cache = new CacheProperties(); |
||||||
420 | $cache->addProperty($start); |
||||||
421 | $cache->addProperty($end); |
||||||
422 | $cache->addProperty($accounts); |
||||||
423 | $cache->addProperty('chart.budget.no-budget'); |
||||||
424 | if ($cache->has()) { |
||||||
425 | return response()->json($cache->get()); // @codeCoverageIgnore |
||||||
426 | } |
||||||
427 | |||||||
428 | // the expenses: |
||||||
429 | $periods = app('navigation')->listOfPeriods($start, $end); |
||||||
430 | $entries = $this->repository->getNoBudgetPeriodReport($accounts, $start, $end); |
||||||
431 | $chartData = []; |
||||||
432 | |||||||
433 | // join them: |
||||||
434 | foreach (array_keys($periods) as $period) { |
||||||
435 | $label = $periods[$period]; |
||||||
436 | $spent = $entries['entries'][$period] ?? '0'; |
||||||
437 | $chartData[$label] = bcmul($spent, '-1'); |
||||||
438 | } |
||||||
439 | $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData); |
||||||
440 | $cache->store($data); |
||||||
441 | |||||||
442 | return response()->json($data); |
||||||
443 | } |
||||||
444 | |||||||
445 | /** |
||||||
446 | * @param array $accountIds |
||||||
447 | * |
||||||
448 | * @return array |
||||||
449 | */ |
||||||
450 | private function getAccountNames(array $accountIds): array |
||||||
451 | { |
||||||
452 | /** @var AccountRepositoryInterface $repository */ |
||||||
453 | $repository = app(AccountRepositoryInterface::class); |
||||||
454 | $accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::CASH]); |
||||||
455 | $grouped = $accounts->groupBy('id')->toArray(); |
||||||
456 | $return = []; |
||||||
457 | foreach ($accountIds as $accountId) { |
||||||
458 | if (isset($grouped[$accountId])) { |
||||||
459 | $return[$accountId] = $grouped[$accountId][0]['name']; |
||||||
460 | } |
||||||
461 | } |
||||||
462 | $return[0] = '(no name)'; |
||||||
463 | |||||||
464 | return $return; |
||||||
465 | } |
||||||
466 | |||||||
467 | /** |
||||||
468 | * @param Budget $budget |
||||||
469 | * @param Carbon $start |
||||||
470 | * @param Carbon $end |
||||||
471 | * |
||||||
472 | * @return array |
||||||
473 | */ |
||||||
474 | private function getBudgetedInPeriod(Budget $budget, Carbon $start, Carbon $end): array |
||||||
475 | { |
||||||
476 | $key = app('navigation')->preferredCarbonFormat($start, $end); |
||||||
0 ignored issues
–
show
The method
preferredCarbonFormat() does not exist on FireflyIII\Support\Facades\Navigation .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
477 | $range = app('navigation')->preferredRangeFormat($start, $end); |
||||||
0 ignored issues
–
show
The method
preferredRangeFormat() does not exist on FireflyIII\Support\Facades\Navigation .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
478 | $current = clone $start; |
||||||
479 | $budgeted = []; |
||||||
480 | while ($current < $end) { |
||||||
481 | $currentStart = app('navigation')->startOfPeriod($current, $range); |
||||||
482 | $currentEnd = app('navigation')->endOfPeriod($current, $range); |
||||||
483 | $budgetLimits = $this->repository->getBudgetLimits($budget, $currentStart, $currentEnd); |
||||||
484 | $index = $currentStart->format($key); |
||||||
485 | $budgeted[$index] = $budgetLimits->sum('amount'); |
||||||
486 | $currentEnd->addDay(); |
||||||
487 | $current = clone $currentEnd; |
||||||
488 | } |
||||||
489 | |||||||
490 | return $budgeted; |
||||||
491 | } |
||||||
492 | |||||||
493 | /** |
||||||
494 | * Small helper function for some of the charts. |
||||||
495 | * |
||||||
496 | * @param array $categoryIds |
||||||
497 | * |
||||||
498 | * @return array |
||||||
499 | */ |
||||||
500 | private function getCategoryNames(array $categoryIds): array |
||||||
501 | { |
||||||
502 | /** @var CategoryRepositoryInterface $repository */ |
||||||
503 | $repository = app(CategoryRepositoryInterface::class); |
||||||
504 | $categories = $repository->getCategories(); |
||||||
505 | $grouped = $categories->groupBy('id')->toArray(); |
||||||
506 | $return = []; |
||||||
507 | foreach ($categoryIds as $categoryId) { |
||||||
508 | if (isset($grouped[$categoryId])) { |
||||||
509 | $return[$categoryId] = $grouped[$categoryId][0]['name']; |
||||||
510 | } |
||||||
511 | } |
||||||
512 | $return[0] = trans('firefly.noCategory'); |
||||||
513 | |||||||
514 | return $return; |
||||||
515 | } |
||||||
516 | |||||||
517 | /** |
||||||
518 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 6 but ok. |
||||||
519 | * |
||||||
520 | * @param Collection $limits |
||||||
521 | * @param Budget $budget |
||||||
522 | * @param Carbon $start |
||||||
523 | * @param Carbon $end |
||||||
524 | * |
||||||
525 | * @return array |
||||||
526 | */ |
||||||
527 | private function getExpensesForBudget(Collection $limits, Budget $budget, Carbon $start, Carbon $end): array |
||||||
528 | { |
||||||
529 | $return = []; |
||||||
530 | if (0 === $limits->count()) { |
||||||
531 | $spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); |
||||||
532 | if (0 !== bccomp($spent, '0')) { |
||||||
533 | $return[$budget->name]['spent'] = bcmul($spent, '-1'); |
||||||
534 | $return[$budget->name]['left'] = 0; |
||||||
535 | $return[$budget->name]['overspent'] = 0; |
||||||
536 | } |
||||||
537 | |||||||
538 | return $return; |
||||||
539 | } |
||||||
540 | |||||||
541 | $rows = $this->spentInPeriodMulti($budget, $limits); |
||||||
542 | foreach ($rows as $name => $row) { |
||||||
543 | if (0 !== bccomp($row['spent'], '0') || 0 !== bccomp($row['left'], '0')) { |
||||||
544 | $return[$name] = $row; |
||||||
545 | } |
||||||
546 | } |
||||||
547 | unset($rows, $row); |
||||||
548 | |||||||
549 | return $return; |
||||||
550 | } |
||||||
551 | |||||||
552 | /** |
||||||
553 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's exactly five. |
||||||
554 | * |
||||||
555 | * Returns an array with the following values: |
||||||
556 | * 0 => |
||||||
557 | * 'name' => name of budget + repetition |
||||||
558 | * 'left' => left in budget repetition (always zero) |
||||||
559 | * 'overspent' => spent more than budget repetition? (always zero) |
||||||
560 | * 'spent' => actually spent in period for budget |
||||||
561 | * 1 => (etc) |
||||||
562 | * |
||||||
563 | * @param Budget $budget |
||||||
564 | * @param Collection $limits |
||||||
565 | * |
||||||
566 | * @return array |
||||||
567 | */ |
||||||
568 | private function spentInPeriodMulti(Budget $budget, Collection $limits): array |
||||||
569 | { |
||||||
570 | $return = []; |
||||||
571 | $format = (string)trans('config.month_and_day'); |
||||||
572 | $name = $budget->name; |
||||||
573 | /** @var BudgetLimit $budgetLimit */ |
||||||
574 | foreach ($limits as $budgetLimit) { |
||||||
575 | $expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date); |
||||||
576 | $expenses = Steam::positive($expenses); |
||||||
0 ignored issues
–
show
The method
positive() does not exist on FireflyIII\Support\Facades\Steam . Since you implemented __callStatic , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
577 | |||||||
578 | if ($limits->count() > 1) { |
||||||
579 | $name = $budget->name . ' ' . trans( |
||||||
0 ignored issues
–
show
Are you sure
trans('firefly.between_d...matLocalized($format))) of type null|string|array can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
580 | 'firefly.between_dates', |
||||||
581 | [ |
||||||
582 | 'start' => $budgetLimit->start_date->formatLocalized($format), |
||||||
583 | 'end' => $budgetLimit->end_date->formatLocalized($format), |
||||||
584 | ] |
||||||
585 | ); |
||||||
586 | } |
||||||
587 | /* |
||||||
588 | * amount: amount of budget limit |
||||||
589 | * left: amount of budget limit min spent, or 0 when < 0. |
||||||
590 | * spent: spent, or amount of budget limit when > amount |
||||||
591 | */ |
||||||
592 | $amount = $budgetLimit->amount; |
||||||
593 | $leftInLimit = bcsub($amount, $expenses); |
||||||
594 | $hasOverspent = bccomp($leftInLimit, '0') === -1; |
||||||
595 | |||||||
596 | $left = $hasOverspent ? '0' : bcsub($amount, $expenses); |
||||||
597 | $spent = $hasOverspent ? $amount : $expenses; |
||||||
598 | $overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0'; |
||||||
599 | |||||||
600 | $return[$name] = [ |
||||||
601 | 'left' => $left, |
||||||
602 | 'overspent' => $overspent, |
||||||
603 | 'spent' => $spent, |
||||||
604 | ]; |
||||||
605 | } |
||||||
606 | |||||||
607 | return $return; |
||||||
608 | } |
||||||
609 | |||||||
610 | /** |
||||||
611 | * Returns an array with the following values: |
||||||
612 | * 'name' => "no budget" in local language |
||||||
613 | * 'repetition_left' => left in budget repetition (always zero) |
||||||
614 | * 'repetition_overspent' => spent more than budget repetition? (always zero) |
||||||
615 | * 'spent' => actually spent in period for budget. |
||||||
616 | * |
||||||
617 | * @param Carbon $start |
||||||
618 | * @param Carbon $end |
||||||
619 | * |
||||||
620 | * @return string |
||||||
621 | */ |
||||||
622 | private function spentInPeriodWithout(Carbon $start, Carbon $end): string |
||||||
623 | { |
||||||
624 | // collector |
||||||
625 | /** @var JournalCollectorInterface $collector */ |
||||||
626 | $collector = app(JournalCollectorInterface::class); |
||||||
627 | $types = [TransactionType::WITHDRAWAL]; |
||||||
628 | $collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget(); |
||||||
629 | $journals = $collector->getJournals(); |
||||||
630 | $sum = '0'; |
||||||
631 | /** @var Transaction $entry */ |
||||||
632 | foreach ($journals as $entry) { |
||||||
633 | $sum = bcadd($entry->transaction_amount, $sum); |
||||||
634 | } |
||||||
635 | |||||||
636 | return $sum; |
||||||
637 | } |
||||||
638 | } |
||||||
639 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.