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.
Completed
Push — master ( 869845...9ac565 )
by James
08:24 queued 03:31
created

BudgetController::getPeriodOverview()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 37
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 29
nc 3
nop 0
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
22
declare(strict_types=1);
23
24
namespace FireflyIII\Http\Controllers;
25
26
use Carbon\Carbon;
27
use Exception;
28
use FireflyIII\Exceptions\FireflyException;
29
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
30
use FireflyIII\Http\Requests\BudgetFormRequest;
31
use FireflyIII\Http\Requests\BudgetIncomeRequest;
32
use FireflyIII\Models\Budget;
33
use FireflyIII\Models\BudgetLimit;
34
use FireflyIII\Models\TransactionType;
35
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
36
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
37
use FireflyIII\Support\CacheProperties;
38
use Illuminate\Http\Request;
39
use Illuminate\Support\Collection;
40
use Log;
41
use Navigation;
42
use Preferences;
43
use Response;
44
use View;
45
46
/**
47
 * Class BudgetController
48
 *
49
 * @package FireflyIII\Http\Controllers
50
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
51
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
52
 */
53
class BudgetController extends Controller
54
{
55
56
    /** @var  BudgetRepositoryInterface */
57
    private $repository;
58
59
    /**
60
     *
61
     */
62
    public function __construct()
63
    {
64
        parent::__construct();
65
66
        View::share('hideBudgets', true);
67
68
        $this->middleware(
69
            function ($request, $next) {
70
                View::share('title', trans('firefly.budgets'));
71
                View::share('mainTitleIcon', 'fa-tasks');
72
                $this->repository = app(BudgetRepositoryInterface::class);
73
74
                return $next($request);
75
            }
76
        );
77
    }
78
79
    /**
80
     * @param Request $request
81
     * @param Budget  $budget
82
     *
83
     * @return \Illuminate\Http\JsonResponse
84
     */
85
    public function amount(Request $request, Budget $budget)
86
    {
87
        $amount      = intval($request->get('amount'));
88
        $start       = Carbon::createFromFormat('Y-m-d', $request->get('start'));
89
        $end         = Carbon::createFromFormat('Y-m-d', $request->get('end'));
90
        $budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
91
        if ($amount === 0) {
92
            $budgetLimit = null;
93
        }
94
        Preferences::mark();
95
96
        return Response::json(['name' => $budget->name, 'limit' => $budgetLimit ? $budgetLimit->id : 0, 'amount' => $amount]);
97
98
    }
99
100
    /**
101
     * @param Request $request
102
     *
103
     * @return View
104
     */
105
    public function create(Request $request)
106
    {
107
        // put previous url in session if not redirect from store (not "create another").
108
        if (session('budgets.create.fromStore') !== true) {
109
            $this->rememberPreviousUri('budgets.create.uri');
110
        }
111
        $request->session()->forget('budgets.create.fromStore');
112
        $request->session()->flash('gaEventCategory', 'budgets');
113
        $request->session()->flash('gaEventAction', 'create');
114
        $subTitle = (string)trans('firefly.create_new_budget');
115
116
        return view('budgets.create', compact('subTitle'));
117
    }
118
119
    /**
120
     * @param Request $request
121
     * @param Budget  $budget
122
     *
123
     * @return View
124
     */
125
    public function delete(Request $request, Budget $budget)
126
    {
127
        $subTitle = trans('firefly.delete_budget', ['name' => $budget->name]);
128
129
        // put previous url in session
130
        $this->rememberPreviousUri('budgets.delete.uri');
131
        $request->session()->flash('gaEventCategory', 'budgets');
132
        $request->session()->flash('gaEventAction', 'delete');
133
134
        return view('budgets.delete', compact('budget', 'subTitle'));
135
    }
136
137
    /**
138
     * @param Request $request
139
     * @param Budget  $budget
140
     *
141
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
142
     */
143
    public function destroy(Request $request, Budget $budget)
144
    {
145
146
        $name = $budget->name;
147
        $this->repository->destroy($budget);
148
        $request->session()->flash('success', strval(trans('firefly.deleted_budget', ['name' => $name])));
149
        Preferences::mark();
150
151
        return redirect($this->getPreviousUri('budgets.delete.uri'));
152
    }
153
154
    /**
155
     * @param Request $request
156
     * @param Budget  $budget
157
     *
158
     * @return View
159
     */
160
    public function edit(Request $request, Budget $budget)
161
    {
162
        $subTitle = trans('firefly.edit_budget', ['name' => $budget->name]);
163
164
        // put previous url in session if not redirect from store (not "return_to_edit").
165
        if (session('budgets.edit.fromUpdate') !== true) {
166
            $this->rememberPreviousUri('budgets.edit.uri');
167
        }
168
        $request->session()->forget('budgets.edit.fromUpdate');
169
        $request->session()->flash('gaEventCategory', 'budgets');
170
        $request->session()->flash('gaEventAction', 'edit');
171
172
        return view('budgets.edit', compact('budget', 'subTitle'));
173
174
    }
175
176
    /**
177
     * @param string|null $moment
178
     *
179
     * @return View
180
     *
181
     * @SuppressWarnings(PHPMD.CyclomaticComplexity) complex because of while loop
182
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
183
     */
184
    public function index(string $moment = null)
185
    {
186
        $range = Preferences::get('viewRange', '1M')->data;
187
        $start = session('start', new Carbon);
188
        $end   = session('end', new Carbon);
189
190
        // make date if present:
191
        if (!is_null($moment) || strlen(strval($moment)) !== 0) {
192
            try {
193
                $start = new Carbon($moment);
194
                $end   = Navigation::endOfPeriod($start, $range);
195
            } catch (Exception $e) {
196
                // start and end are already defined.
197
            }
198
        }
199
        $next = clone $end;
200
        $next->addDay();
201
        $prev = clone $start;
202
        $prev->subDay();
203
        $prev = Navigation::startOfPeriod($prev, $range);
204
        $this->repository->cleanupBudgets();
205
        $budgets           = $this->repository->getActiveBudgets();
206
        $inactive          = $this->repository->getInactiveBudgets();
207
        $periodStart       = $start->formatLocalized($this->monthAndDayFormat);
208
        $periodEnd         = $end->formatLocalized($this->monthAndDayFormat);
209
        $budgetInformation = $this->repository->collectBudgetInformation($budgets, $start, $end);
210
        $defaultCurrency   = app('amount')->getDefaultCurrency();
211
        $available         = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
212
        $spent             = array_sum(array_column($budgetInformation, 'spent'));
213
        $budgeted          = array_sum(array_column($budgetInformation, 'budgeted'));
214
215
        // select thing for last 12 periods:
216
        $previousLoop = [];
217
        $previousDate = clone $start;
218
        $count        = 0;
219
        while ($count < 12) {
220
            $previousDate->subDay();
221
            $previousDate          = Navigation::startOfPeriod($previousDate, $range);
222
            $format                = $previousDate->format('Y-m-d');
223
            $previousLoop[$format] = Navigation::periodShow($previousDate, $range);
224
            $count++;
225
        }
226
227
        // select thing for next 12 periods:
228
        $nextLoop = [];
229
        $nextDate = clone $end;
230
        $nextDate->addDay();
231
        $count = 0;
232
233
        while ($count < 12) {
234
            $format            = $nextDate->format('Y-m-d');
235
            $nextLoop[$format] = Navigation::periodShow($nextDate, $range);
236
            $nextDate          = Navigation::endOfPeriod($nextDate, $range);
237
            $count++;
238
            $nextDate->addDay();
239
        }
240
241
        // display info
242
        $currentMonth = Navigation::periodShow($start, $range);
243
        $nextText     = Navigation::periodShow($next, $range);
244
        $prevText     = Navigation::periodShow($prev, $range);
245
246
        return view(
247
            'budgets.index',
248
            compact(
249
                'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
250
                'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
251
                'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
252
            )
253
        );
254
    }
255
256
    /**
257
     * @param Carbon $start
258
     * @param Carbon $end
259
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
260
     *
261
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
262
     */
263
    public function infoIncome(Carbon $start, Carbon $end)
264
    {
265
        // properties for cache
266
        $cache = new CacheProperties;
267
        $cache->addProperty($start);
268
        $cache->addProperty($end);
269
        $cache->addProperty('info-income');
270
271
        if ($cache->has()) {
272
            $result = $cache->get(); // @codeCoverageIgnore
273
274
            return view('budgets.info', compact('result', 'begin', 'currentEnd'));
275
        }
276
        $result   = [
277
            'available' => '0',
278
            'earned'    => '0',
279
            'suggested' => '0',
280
        ];
281
        $currency = app('amount')->getDefaultCurrency();
282
        $range    = Preferences::get('viewRange', '1M')->data;
283
        $begin    = Navigation::subtractPeriod($start, $range, 3);
284
285
        // get average amount available.
286
        $total        = '0';
287
        $count        = 0;
288
        $currentStart = clone $begin;
289
        while ($currentStart < $start) {
290
            $currentEnd   = Navigation::endOfPeriod($currentStart, $range);
291
            $total        = bcadd($total, $this->repository->getAvailableBudget($currency, $currentStart, $currentEnd));
292
            $currentStart = Navigation::addPeriod($currentStart, $range, 0);
293
            $count++;
294
        }
295
        $result['available'] = bcdiv($total, strval($count));
296
297
        // amount earned in this period:
298
        $subDay = clone $end;
299
        $subDay->subDay();
300
        /** @var JournalCollectorInterface $collector */
301
        $collector = app(JournalCollectorInterface::class);
302
        $collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
303
        $result['earned'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
304
305
        // amount spent in period
306
        /** @var JournalCollectorInterface $collector */
307
        $collector = app(JournalCollectorInterface::class);
308
        $collector->setAllAssetAccounts()->setRange($begin, $subDay)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
309
        $result['spent'] = bcdiv(strval($collector->getJournals()->sum('transaction_amount')), strval($count));
310
        // suggestion starts with the amount spent
311
        $result['suggested'] = bcmul($result['spent'], '-1');
312
        $result['suggested'] = bccomp($result['suggested'], $result['earned']) === 1 ? $result['earned'] : $result['suggested'];
313
        // unless it's more than you earned. So min() of suggested/earned
314
315
        $cache->store($result);
316
317
318
        return view('budgets.info', compact('result', 'begin', 'currentEnd'));
319
    }
320
321
    /**
322
     * @param Request                    $request
323
     * @param JournalRepositoryInterface $repository
324
     * @param string                     $moment
325
     *
326
     * @return View
327
     *
328
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
329
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
330
     */
331
    public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
332
    {
333
        // default values:
334
        $range   = Preferences::get('viewRange', '1M')->data;
335
        $start   = null;
336
        $end     = null;
337
        $periods = new Collection;
338
339
        // prep for "all" view.
340
        if ($moment === 'all') {
341
            $subTitle = trans('firefly.all_journals_without_budget');
342
            $first    = $repository->first();
343
            $start    = $first->date ?? new Carbon;
344
            $end      = new Carbon;
345
        }
346
347
        // prep for "specific date" view.
348
        if (strlen($moment) > 0 && $moment !== 'all') {
349
            $start    = new Carbon($moment);
350
            $end      = Navigation::endOfPeriod($start, $range);
351
            $subTitle = trans(
352
                'firefly.without_budget_between',
353
                ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
354
            );
355
            $periods  = $this->getPeriodOverview();
356
        }
357
358
        // prep for current period
359
        if (strlen($moment) === 0) {
360
            $start    = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
361
            $end      = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
362
            $periods  = $this->getPeriodOverview();
363
            $subTitle = trans(
364
                'firefly.without_budget_between',
365
                ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
366
            );
367
        }
368
369
        $page     = intval($request->get('page'));
370
        $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
371
372
        /** @var JournalCollectorInterface $collector */
373
        $collector = app(JournalCollectorInterface::class);
374
        $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page)
375
                  ->withoutBudget()->withOpposingAccount();
376
        $journals = $collector->getPaginatedJournals();
377
        $journals->setPath(route('budgets.no-budget'));
378
379
        return view('budgets.no-budget', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end'));
380
    }
381
382
    /**
383
     * @param BudgetIncomeRequest $request
384
     *
385
     * @return \Illuminate\Http\RedirectResponse
386
     */
387
    public function postUpdateIncome(BudgetIncomeRequest $request)
388
    {
389
        $start           = Carbon::createFromFormat('Y-m-d', $request->string('start'));
390
        $end             = Carbon::createFromFormat('Y-m-d', $request->string('end'));
391
        $defaultCurrency = app('amount')->getDefaultCurrency();
392
        $amount          = $request->get('amount');
393
394
        $this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
395
        Preferences::mark();
396
397
        return redirect(route('budgets.index', [$start->format('Y-m-d')]));
398
    }
399
400
    /**
401
     * @param Request $request
402
     * @param Budget  $budget
403
     *
404
     * @return View
405
     */
406
    public function show(Request $request, Budget $budget)
407
    {
408
        /** @var Carbon $start */
409
        $start      = session('first', Carbon::create()->startOfYear());
410
        $end        = new Carbon;
411
        $page       = intval($request->get('page'));
412
        $pageSize   = intval(Preferences::get('transactionPageSize', 50)->data);
413
        $limits     = $this->getLimits($budget, $start, $end);
414
        $repetition = null;
415
        // collector:
416
        /** @var JournalCollectorInterface $collector */
417
        $collector = app(JournalCollectorInterface::class);
418
        $collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
419
        $journals = $collector->getPaginatedJournals();
420
        $journals->setPath(route('budgets.show', [$budget->id]));
421
422
423
        $subTitle = trans('firefly.all_journals_for_budget', ['name' => $budget->name]);
424
425
        return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
426
    }
427
428
    /**
429
     * @param Request     $request
430
     * @param Budget      $budget
431
     * @param BudgetLimit $budgetLimit
432
     *
433
     * @return View
434
     * @throws FireflyException
435
     */
436
    public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
437
    {
438
        if ($budgetLimit->budget->id !== $budget->id) {
439
            throw new FireflyException('This budget limit is not part of this budget.');
440
        }
441
442
        $page     = intval($request->get('page'));
443
        $pageSize = intval(Preferences::get('transactionPageSize', 50)->data);
444
        $subTitle = trans(
445
            'firefly.budget_in_period', [
446
                                          'name'  => $budget->name,
447
                                          'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
448
                                          'end'   => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
449
                                      ]
450
        );
451
452
        // collector:
453
        /** @var JournalCollectorInterface $collector */
454
        $collector = app(JournalCollectorInterface::class);
455
        $collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date)
456
                  ->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation();
457
        $journals = $collector->getPaginatedJournals();
458
        $journals->setPath(route('budgets.show', [$budget->id, $budgetLimit->id]));
459
        $start  = session('first', Carbon::create()->startOfYear());
460
        $end    = new Carbon;
461
        $limits = $this->getLimits($budget, $start, $end);
462
463
        return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'journals', 'subTitle'));
464
465
    }
466
467
    /**
468
     * @param BudgetFormRequest $request
469
     *
470
     * @return \Illuminate\Http\RedirectResponse
471
     */
472
    public function store(BudgetFormRequest $request)
473
    {
474
        $data   = $request->getBudgetData();
475
        $budget = $this->repository->store($data);
476
477
        $request->session()->flash('success', strval(trans('firefly.stored_new_budget', ['name' => $budget->name])));
478
        Preferences::mark();
479
480
        if (intval($request->get('create_another')) === 1) {
481
            // @codeCoverageIgnoreStart
482
            $request->session()->put('budgets.create.fromStore', true);
483
484
            return redirect(route('budgets.create'))->withInput();
485
            // @codeCoverageIgnoreEnd
486
        }
487
488
        return redirect($this->getPreviousUri('budgets.create.uri'));
489
    }
490
491
    /**
492
     * @param BudgetFormRequest $request
493
     * @param Budget            $budget
494
     *
495
     * @return \Illuminate\Http\RedirectResponse
496
     */
497
    public function update(BudgetFormRequest $request, Budget $budget)
498
    {
499
        $data = $request->getBudgetData();
500
        $this->repository->update($budget, $data);
501
502
        $request->session()->flash('success', strval(trans('firefly.updated_budget', ['name' => $budget->name])));
503
        Preferences::mark();
504
505
        if (intval($request->get('return_to_edit')) === 1) {
506
            // @codeCoverageIgnoreStart
507
            $request->session()->put('budgets.edit.fromUpdate', true);
508
509
            return redirect(route('budgets.edit', [$budget->id]))->withInput(['return_to_edit' => 1]);
510
            // @codeCoverageIgnoreEnd
511
        }
512
513
        return redirect($this->getPreviousUri('budgets.edit.uri'));
514
    }
515
516
    /**
517
     * @param Carbon $start
518
     * @param Carbon $end
519
     *
520
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
521
     */
522
    public function updateIncome(Carbon $start, Carbon $end)
523
    {
524
        $defaultCurrency = app('amount')->getDefaultCurrency();
525
        $available       = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
526
        $available       = round($available, $defaultCurrency->decimal_places);
527
528
        return view('budgets.income', compact('available', 'start', 'end'));
529
    }
530
531
    /**
532
     * @param Budget $budget
533
     * @param Carbon $start
534
     * @param Carbon $end
535
     *
536
     * @return Collection
537
     */
538
    private function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection
539
    {
540
        // properties for cache
541
        $cache = new CacheProperties;
542
        $cache->addProperty($start);
543
        $cache->addProperty($end);
544
        $cache->addProperty($budget->id);
545
        $cache->addProperty('get-limits');
546
547
        if ($cache->has()) {
548
            return $cache->get(); // @codeCoverageIgnore
549
        }
550
551
        $set    = $this->repository->getBudgetLimits($budget, $start, $end);
552
        $limits = new Collection();
553
554
        /** @var BudgetLimit $entry */
555
        foreach ($set as $entry) {
556
            $entry->spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection(), $entry->start_date, $entry->end_date);
557
            $limits->push($entry);
558
        }
559
        $cache->store($limits);
560
561
        return $set;
562
    }
563
564
    /**
565
     * @return Collection
566
     */
567
    private function getPeriodOverview(): Collection
568
    {
569
        $repository = app(JournalRepositoryInterface::class);
570
        $first      = $repository->first();
571
        $start      = $first->date ?? new Carbon;
572
        $range      = Preferences::get('viewRange', '1M')->data;
573
        $start      = Navigation::startOfPeriod($start, $range);
574
        $end        = Navigation::endOfX(new Carbon, $range, null);
575
        $entries    = new Collection;
576
        $cache      = new CacheProperties;
577
        $cache->addProperty($start);
578
        $cache->addProperty($end);
579
        $cache->addProperty('no-budget-period-entries');
580
581
        if ($cache->has()) {
582
            return $cache->get(); // @codeCoverageIgnore
583
        }
584
585
        Log::debug('Going to get period expenses and incomes.');
586
        while ($end >= $start) {
587
            $end        = Navigation::startOfPeriod($end, $range);
588
            $currentEnd = Navigation::endOfPeriod($end, $range);
589
            /** @var JournalCollectorInterface $collector */
590
            $collector = app(JournalCollectorInterface::class);
591
            $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
592
            $set      = $collector->getJournals();
593
            $sum      = $set->sum('transaction_amount') ?? '0';
594
            $journals = $set->count();
595
            $dateStr  = $end->format('Y-m-d');
596
            $dateName = Navigation::periodShow($end, $range);
597
            $entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $end,]);
598
            $end = Navigation::subtractPeriod($end, $range, 1);
599
        }
600
        $cache->store($entries);
601
602
        return $entries;
603
    }
604
605
}
606