GitHub Access Token became invalid

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

app/Repositories/PiggyBank/PiggyBankRepository.php (1 issue)

Severity
1
<?php
2
/**
3
 * PiggyBankRepository.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\Repositories\PiggyBank;
24
25
use Carbon\Carbon;
26
use Exception;
27
use FireflyIII\Models\Note;
28
use FireflyIII\Models\PiggyBank;
29
use FireflyIII\Models\PiggyBankEvent;
30
use FireflyIII\Models\PiggyBankRepetition;
31
use FireflyIII\Models\TransactionJournal;
32
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
33
use FireflyIII\User;
34
use Illuminate\Support\Collection;
35
use Log;
36
37
/**
38
 * Class PiggyBankRepository.
39
 *
40
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
41
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
42
 */
43
class PiggyBankRepository implements PiggyBankRepositoryInterface
44
{
45
    /** @var User */
46
    private $user;
47
48
    /**
49
     * Constructor.
50
     */
51
    public function __construct()
52
    {
53
        if ('testing' === config('app.env')) {
54
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
55
        }
56
    }
57
58
    /**
59
     * @param PiggyBank $piggyBank
60
     * @param string $amount
61
     *
62
     * @return bool
63
     */
64
    public function addAmount(PiggyBank $piggyBank, string $amount): bool
65
    {
66
        $repetition = $this->getRepetition($piggyBank);
67
        if (null === $repetition) {
68
            return false;
69
        }
70
        $currentAmount             = $repetition->currentamount ?? '0';
71
        $repetition->currentamount = bcadd($currentAmount, $amount);
72
        $repetition->save();
73
74
        // create event
75
        $this->createEvent($piggyBank, $amount);
76
77
        return true;
78
    }
79
80
    /**
81
     * @param PiggyBankRepetition $repetition
82
     * @param string $amount
83
     *
84
     * @return string
85
     */
86
    public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount): string
87
    {
88
        $newAmount                 = bcadd($repetition->currentamount, $amount);
89
        $repetition->currentamount = $newAmount;
90
        $repetition->save();
91
92
        return $newAmount;
93
    }
94
95
    /**
96
     * @param PiggyBank $piggyBank
97
     * @param string $amount
98
     *
99
     * @return bool
100
     */
101
    public function canAddAmount(PiggyBank $piggyBank, string $amount): bool
102
    {
103
        $leftOnAccount = $this->leftOnAccount($piggyBank, new Carbon);
104
        $savedSoFar    = (string)$this->getRepetition($piggyBank)->currentamount;
105
        $leftToSave    = bcsub($piggyBank->targetamount, $savedSoFar);
106
        $maxAmount     = (string)min(round($leftOnAccount, 12), round($leftToSave, 12));
107
108
        return bccomp($amount, $maxAmount) <= 0;
109
    }
110
111
    /**
112
     * @param PiggyBank $piggyBank
113
     * @param string $amount
114
     *
115
     * @return bool
116
     */
117
    public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool
118
    {
119
        $repetition = $this->getRepetition($piggyBank);
120
        if (null === $repetition) {
121
            return false;
122
        }
123
        $savedSoFar = $repetition->currentamount;
124
125
        return bccomp($amount, $savedSoFar) <= 0;
126
    }
127
128
    /**
129
     * Correct order of piggies in case of issues.
130
     */
131
    public function correctOrder(): void
132
    {
133
        $set     = $this->user->piggyBanks()->orderBy('order', 'ASC')->get();
134
        $current = 1;
135
        foreach ($set as $piggyBank) {
136
            if ((int)$piggyBank->order !== $current) {
137
                $piggyBank->order = $current;
138
                $piggyBank->save();
139
            }
140
            $current++;
141
        }
142
    }
143
144
    /**
145
     * @param PiggyBank $piggyBank
146
     * @param string $amount
147
     *
148
     * @return PiggyBankEvent
149
     */
150
    public function createEvent(PiggyBank $piggyBank, string $amount): PiggyBankEvent
151
    {
152
        /** @var PiggyBankEvent $event */
153
        $event = PiggyBankEvent::create(['date' => Carbon::now(), 'amount' => $amount, 'piggy_bank_id' => $piggyBank->id]);
154
155
        return $event;
156
    }
157
158
    /**
159
     * @param PiggyBank $piggyBank
160
     * @param string $amount
161
     * @param TransactionJournal $journal
162
     *
163
     * @return PiggyBankEvent
164
     */
165
    public function createEventWithJournal(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): PiggyBankEvent
166
    {
167
        /** @var PiggyBankEvent $event */
168
        $event = PiggyBankEvent::create(
169
            [
170
                'piggy_bank_id'          => $piggyBank->id,
171
                'transaction_journal_id' => $journal->id,
172
                'date'                   => $journal->date->format('Y-m-d'),
173
                'amount'                 => $amount]
174
        );
175
176
        return $event;
177
    }
178
179
    /**
180
     * @param PiggyBank $piggyBank
181
     *
182
     * @return bool
183
     * @throws \Exception
184
     */
185
    public function destroy(PiggyBank $piggyBank): bool
186
    {
187
        $piggyBank->delete();
188
189
        return true;
190
    }
191
192
    /**
193
     * Find by name or return NULL.
194
     *
195
     * @param string $name
196
     *
197
     * @return PiggyBank|null
198
     */
199
    public function findByName(string $name): ?PiggyBank
200
    {
201
        $set = $this->user->piggyBanks()->get(['piggy_banks.*']);
202
203
        // TODO no longer need to loop like this
204
205
        /** @var PiggyBank $piggy */
206
        foreach ($set as $piggy) {
207
            if ($piggy->name === $name) {
208
                return $piggy;
209
            }
210
        }
211
212
        return null;
213
    }
214
215
    /**
216
     * @param int $piggyBankId
217
     *
218
     * @return PiggyBank|null
219
     */
220
    public function findNull(int $piggyBankId): ?PiggyBank
221
    {
222
        $piggyBank = $this->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
223
        if (null !== $piggyBank) {
224
            return $piggyBank;
225
        }
226
227
        return null;
228
    }
229
230
    /**
231
     * @param int|null $piggyBankId
232
     * @param string|null $piggyBankName
233
     *
234
     * @return PiggyBank|null
235
     */
236
    public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank
237
    {
238
        Log::debug('Searching for piggy information.');
239
240
        if (null !== $piggyBankId) {
241
            $searchResult = $this->findNull((int)$piggyBankId);
242
            if (null !== $searchResult) {
243
                Log::debug(sprintf('Found piggy based on #%d, will return it.', $piggyBankId));
244
245
                return $searchResult;
246
            }
247
        }
248
        if (null !== $piggyBankName) {
249
            $searchResult = $this->findByName((string)$piggyBankName);
250
            if (null !== $searchResult) {
251
                Log::debug(sprintf('Found piggy based on "%s", will return it.', $piggyBankName));
252
253
                return $searchResult;
254
            }
255
        }
256
        Log::debug('Found nothing');
257
258
        return null;
259
    }
260
261
    /**
262
     * Get current amount saved in piggy bank.
263
     *
264
     * @param PiggyBank $piggyBank
265
     *
266
     * @return string
267
     */
268
    public function getCurrentAmount(PiggyBank $piggyBank): string
269
    {
270
        $rep = $this->getRepetition($piggyBank);
271
        if (null === $rep) {
272
            return '0';
273
        }
274
275
        return (string)$rep->currentamount;
276
    }
277
278
    /**
279
     * @param PiggyBank $piggyBank
280
     *
281
     * @return Collection
282
     */
283
    public function getEvents(PiggyBank $piggyBank): Collection
284
    {
285
        return $piggyBank->piggyBankEvents()->orderBy('date', 'DESC')->orderBy('id', 'DESC')->get();
286
    }
287
288
    /**
289
     * Used for connecting to a piggy bank.
290
     *
291
     * @param PiggyBank $piggyBank
292
     * @param PiggyBankRepetition $repetition
293
     * @param TransactionJournal $journal
294
     *
295
     * @return string
296
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
297
     */
298
    public function getExactAmount(PiggyBank $piggyBank, PiggyBankRepetition $repetition, TransactionJournal $journal): string
299
    {
300
        /** @var JournalRepositoryInterface $repos */
301
        $repos = app(JournalRepositoryInterface::class);
302
        $repos->setUser($this->user);
303
304
        $amount  = $repos->getJournalTotal($journal);
305
        $sources = $repos->getJournalSourceAccounts($journal)->pluck('id')->toArray();
0 ignored issues
show
Deprecated Code introduced by
The function FireflyIII\Repositories\...JournalSourceAccounts() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

305
        $sources = /** @scrutinizer ignore-deprecated */ $repos->getJournalSourceAccounts($journal)->pluck('id')->toArray();
Loading history...
306
        $room    = bcsub((string)$piggyBank->targetamount, (string)$repetition->currentamount);
307
        $compare = bcmul($repetition->currentamount, '-1');
308
        Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
309
310
        // if piggy account matches source account, the amount is positive
311
        if (in_array($piggyBank->account_id, $sources, true)) {
312
            $amount = bcmul($amount, '-1');
313
            Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id));
314
        }
315
316
        // if the amount is positive, make sure it fits in piggy bank:
317
        if (1 === bccomp($amount, '0') && bccomp($room, $amount) === -1) {
318
            // amount is positive and $room is smaller than $amount
319
            Log::debug(sprintf('Room in piggy bank for extra money is %f', $room));
320
            Log::debug(sprintf('There is NO room to add %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
321
            Log::debug(sprintf('New amount is %f', $room));
322
323
            return $room;
324
        }
325
326
        // amount is negative and $currentamount is smaller than $amount
327
        if (bccomp($amount, '0') === -1 && 1 === bccomp($compare, $amount)) {
328
            Log::debug(sprintf('Max amount to remove is %f', $repetition->currentamount));
329
            Log::debug(sprintf('Cannot remove %f from piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name));
330
            Log::debug(sprintf('New amount is %f', $compare));
331
332
            return $compare;
333
        }
334
335
        return $amount;
336
    }
337
338
    /**
339
     * @return int
340
     */
341
    public function getMaxOrder(): int
342
    {
343
        return (int)$this->user->piggyBanks()->max('order');
344
    }
345
346
    /**
347
     * Return note for piggy bank.
348
     *
349
     * @param PiggyBank $piggyBank
350
     *
351
     * @return string
352
     */
353
    public function getNoteText(PiggyBank $piggyBank): string
354
    {
355
        /** @var Note $note */
356
        $note = $piggyBank->notes()->first();
357
        if (null === $note) {
358
            return '';
359
        }
360
361
        return $note->text;
362
    }
363
364
    /**
365
     * @return Collection
366
     */
367
    public function getPiggyBanks(): Collection
368
    {
369
        return $this->user->piggyBanks()->orderBy('order', 'ASC')->get();
370
    }
371
372
    /**
373
     * Also add amount in name.
374
     *
375
     * @return Collection
376
     */
377
    public function getPiggyBanksWithAmount(): Collection
378
    {
379
380
        $currency = app('amount')->getDefaultCurrency();
381
382
        $set = $this->getPiggyBanks();
383
        /** @var PiggyBank $piggy */
384
        foreach ($set as $piggy) {
385
            $currentAmount = $this->getRepetition($piggy)->currentamount ?? '0';
386
            $piggy->name   = $piggy->name . ' (' . app('amount')->formatAnything($currency, $currentAmount, false) . ')';
387
        }
388
389
390
        return $set;
391
    }
392
393
    /**
394
     * @param PiggyBank $piggyBank
395
     *
396
     * @return PiggyBankRepetition|null
397
     */
398
    public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition
399
    {
400
        return $piggyBank->piggyBankRepetitions()->first();
401
    }
402
403
    /**
404
     * Returns the suggested amount the user should save per month, or "".
405
     *
406
     * @param PiggyBank $piggyBank
407
     *
408
     * @return string
409
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
410
     */
411
    public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string
412
    {
413
        $savePerMonth = '0';
414
        $repetition   = $this->getRepetition($piggyBank);
415
        if (null === $repetition) {
416
            return $savePerMonth;
417
        }
418
        if (null !== $piggyBank->targetdate && $repetition->currentamount < $piggyBank->targetamount) {
419
            $now             = Carbon::now();
420
            $diffInMonths    = $now->diffInMonths($piggyBank->targetdate, false);
421
            $remainingAmount = bcsub($piggyBank->targetamount, $repetition->currentamount);
422
423
            // more than 1 month to go and still need money to save:
424
            if ($diffInMonths > 0 && 1 === bccomp($remainingAmount, '0')) {
425
                $savePerMonth = bcdiv($remainingAmount, (string)$diffInMonths);
426
            }
427
428
            // less than 1 month to go but still need money to save:
429
            if (0 === $diffInMonths && 1 === bccomp($remainingAmount, '0')) {
430
                $savePerMonth = $remainingAmount;
431
            }
432
        }
433
434
        return $savePerMonth;
435
    }
436
437
    /**
438
     * Get for piggy account what is left to put in piggies.
439
     *
440
     * @param PiggyBank $piggyBank
441
     * @param Carbon $date
442
     *
443
     * @return string
444
     */
445
    public function leftOnAccount(PiggyBank $piggyBank, Carbon $date): string
446
    {
447
448
        $balance = app('steam')->balanceIgnoreVirtual($piggyBank->account, $date);
449
450
        /** @var Collection $piggies */
451
        $piggies = $piggyBank->account->piggyBanks;
452
453
        /** @var PiggyBank $current */
454
        foreach ($piggies as $current) {
455
            $repetition = $this->getRepetition($current);
456
            if (null !== $repetition) {
457
                $balance = bcsub($balance, $repetition->currentamount);
458
            }
459
        }
460
461
        return $balance;
462
    }
463
464
    /**
465
     * @param PiggyBank $piggyBank
466
     * @param string $amount
467
     *
468
     * @return bool
469
     */
470
    public function removeAmount(PiggyBank $piggyBank, string $amount): bool
471
    {
472
        $repetition                = $this->getRepetition($piggyBank);
473
        $repetition->currentamount = bcsub($repetition->currentamount, $amount);
474
        $repetition->save();
475
476
        // create event
477
        $this->createEvent($piggyBank, bcmul($amount, '-1'));
478
479
        return true;
480
    }
481
482
    /**
483
     * set id of piggy bank.
484
     *
485
     * @param PiggyBank $piggyBank
486
     * @param int $order
487
     *
488
     * @return bool
489
     */
490
    public function setOrder(PiggyBank $piggyBank, int $order): bool
491
    {
492
        $piggyBank->order = $order;
493
        $piggyBank->save();
494
495
        return true;
496
    }
497
498
    /**
499
     * @param User $user
500
     */
501
    public function setUser(User $user): void
502
    {
503
        $this->user = $user;
504
    }
505
506
    /**
507
     * @param array $data
508
     *
509
     * @return PiggyBank|null
510
     */
511
    public function store(array $data): ?PiggyBank
512
    {
513
        $data['order'] = $this->getMaxOrder() + 1;
514
        /** @var PiggyBank $piggyBank */
515
        $piggyBank = PiggyBank::create($data);
516
517
        $this->updateNote($piggyBank, $data['notes']);
518
519
        // repetition is auto created.
520
        $repetition = $this->getRepetition($piggyBank);
521
        if (null !== $repetition && isset($data['current_amount'])) {
522
            $repetition->currentamount = $data['current_amount'];
523
            $repetition->save();
524
        }
525
526
        return $piggyBank;
527
    }
528
529
    /**
530
     * @param PiggyBank $piggyBank
531
     * @param array $data
532
     *
533
     * @return PiggyBank
534
     */
535
    public function update(PiggyBank $piggyBank, array $data): PiggyBank
536
    {
537
        $piggyBank->name         = $data['name'];
538
        $piggyBank->account_id   = (int)$data['account_id'];
539
        $piggyBank->targetamount = $data['targetamount'];
540
        $piggyBank->targetdate   = $data['targetdate'];
541
        $piggyBank->startdate    = $data['startdate'] ?? $piggyBank->startdate;
542
543
        $piggyBank->save();
544
545
        $this->updateNote($piggyBank, $data['notes']);
546
547
        // if the piggy bank is now smaller than the current relevant rep,
548
        // remove money from the rep.
549
        $repetition = $this->getRepetition($piggyBank);
550
        if ($repetition->currentamount > $piggyBank->targetamount) {
551
            $diff = bcsub($piggyBank->targetamount, $repetition->currentamount);
552
            $this->createEvent($piggyBank, $diff);
553
554
            $repetition->currentamount = $piggyBank->targetamount;
555
            $repetition->save();
556
        }
557
558
        return $piggyBank;
559
    }
560
561
    /**
562
     * @param PiggyBank $piggyBank
563
     * @param string $note
564
     *
565
     * @return bool
566
     * @throws \Exception
567
     */
568
    private function updateNote(PiggyBank $piggyBank, string $note): bool
569
    {
570
        if ('' === $note) {
571
            $dbNote = $piggyBank->notes()->first();
572
            if (null !== $dbNote) {
573
                try {
574
                    $dbNote->delete();
575
                } catch (Exception $e) {
576
                    Log::debug(sprintf('Could not delete note: %s', $e->getMessage()));
577
                }
578
            }
579
580
            return true;
581
        }
582
        $dbNote = $piggyBank->notes()->first();
583
        if (null === $dbNote) {
584
            $dbNote = new Note();
585
            $dbNote->noteable()->associate($piggyBank);
586
        }
587
        $dbNote->text = trim($note);
588
        $dbNote->save();
589
590
        return true;
591
    }
592
}
593