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.

AccountRepository::expandWithDoubles()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 25
rs 9.3888
cc 5
nc 5
nop 1
1
<?php
2
/**
3
 * AccountRepository.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\Repositories\Account;
24
25
use Carbon\Carbon;
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Factory\AccountFactory;
28
use FireflyIII\Models\Account;
29
use FireflyIII\Models\AccountMeta;
30
use FireflyIII\Models\AccountType;
31
use FireflyIII\Models\Location;
32
use FireflyIII\Models\TransactionCurrency;
33
use FireflyIII\Models\TransactionGroup;
34
use FireflyIII\Models\TransactionJournal;
35
use FireflyIII\Models\TransactionType;
36
use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
37
use FireflyIII\Services\Internal\Update\AccountUpdateService;
38
use FireflyIII\User;
39
use Illuminate\Database\Eloquent\Relations\HasMany;
40
use Illuminate\Support\Collection;
41
use Log;
42
43
/**
44
 * Class AccountRepository.
45
 *
46
 */
47
class AccountRepository implements AccountRepositoryInterface
48
{
49
50
    /** @var User */
51
    private $user;
52
53
    /**
54
     * Constructor.
55
     */
56
    public function __construct()
57
    {
58
        if ('testing' === config('app.env')) {
59
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
60
        }
61
    }
62
63
    /**
64
     * @param array $types
65
     *
66
     * @return int
67
     */
68
    public function count(array $types): int
69
    {
70
        return $this->user->accounts()->accountTypeIn($types)->count();
71
    }
72
73
    /**
74
     * Moved here from account CRUD.
75
     *
76
     * @param Account      $account
77
     * @param Account|null $moveTo
78
     *
79
     * @return bool
80
     *
81
82
     */
83
    public function destroy(Account $account, ?Account $moveTo): bool
84
    {
85
        /** @var AccountDestroyService $service */
86
        $service = app(AccountDestroyService::class);
87
        $service->destroy($account, $moveTo);
88
89
        return true;
90
    }
91
92
    /**
93
     * Find account with same name OR same IBAN or both, but not the same type or ID.
94
     *
95
     * @param Collection $accounts
96
     *
97
     * @return Collection
98
     */
99
    public function expandWithDoubles(Collection $accounts): Collection
100
    {
101
        $result = new Collection;
102
        /** @var Account $account */
103
        foreach ($accounts as $account) {
104
            $byName = $this->user->accounts()->where('name', $account->name)
105
                                 ->where('id', '!=', $account->id)->first();
106
            if (null !== $byName) {
107
                $result->push($account);
108
                $result->push($byName);
109
                continue;
110
            }
111
            if (null !== $account->iban) {
112
                $byIban = $this->user->accounts()->where('iban', $account->iban)
113
                                     ->where('id', '!=', $account->id)->first();
114
                if (null !== $byIban) {
115
                    $result->push($account);
116
                    $result->push($byIban);
117
                    continue;
118
                }
119
            }
120
            $result->push($account);
121
        }
122
123
        return $result;
124
125
    }
126
127
    /**
128
     * @param string $number
129
     * @param array  $types
130
     *
131
     * @return Account|null
132
     */
133
    public function findByAccountNumber(string $number, array $types): ?Account
134
    {
135
        $query = $this->user->accounts()
136
                            ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
137
                            ->where('account_meta.name', 'account_number')
138
                            ->where('account_meta.data', json_encode($number));
139
140
        if (count($types) > 0) {
141
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
142
            $query->whereIn('account_types.type', $types);
143
        }
144
145
        /** @var Collection $accounts */
146
        $accounts = $query->get(['accounts.*']);
147
        if ($accounts->count() > 0) {
148
            return $accounts->first();
149
        }
150
151
        return null;
152
    }
153
154
    /**
155
     * @param string $iban
156
     * @param array  $types
157
     *
158
     * @return Account|null
159
     */
160
    public function findByIbanNull(string $iban, array $types): ?Account
161
    {
162
        $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
163
164
        if (count($types) > 0) {
165
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
166
            $query->whereIn('account_types.type', $types);
167
        }
168
169
        // TODO a loop like this is no longer necessary
170
171
        $accounts = $query->get(['accounts.*']);
172
        /** @var Account $account */
173
        foreach ($accounts as $account) {
174
            if ($account->iban === $iban) {
175
                return $account;
176
            }
177
        }
178
179
        return null;
180
    }
181
182
    /**
183
     * @param string $name
184
     * @param array  $types
185
     *
186
     * @return Account|null
187
     */
188
    public function findByName(string $name, array $types): ?Account
189
    {
190
        $query = $this->user->accounts();
191
192
        if (count($types) > 0) {
193
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
194
            $query->whereIn('account_types.type', $types);
195
        }
196
        Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]);
197
198
        $accounts = $query->get(['accounts.*']);
199
200
        // TODO no longer need to loop like this
201
202
        /** @var Account $account */
203
        foreach ($accounts as $account) {
204
            if ($account->name === $name) {
205
                Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
206
207
                return $account;
208
            }
209
        }
210
        Log::debug(sprintf('There is no account with name "%s" of types', $name), $types);
211
212
        return null;
213
    }
214
215
    /**
216
     * @param int $accountId
217
     *
218
     * @return Account|null
219
     */
220
    public function findNull(int $accountId): ?Account
221
    {
222
        return $this->user->accounts()->find($accountId);
223
    }
224
225
    /**
226
     * @param Account $account
227
     *
228
     * @return TransactionCurrency|null
229
     */
230
    public function getAccountCurrency(Account $account): ?TransactionCurrency
231
    {
232
        $currencyId = (int)$this->getMetaValue($account, 'currency_id');
233
        if ($currencyId > 0) {
234
            return TransactionCurrency::find($currencyId);
235
        }
236
237
        return null;
238
    }
239
240
    /**
241
     * @param Account $account
242
     *
243
     * @return string
244
     */
245
    public function getAccountType(Account $account): string
246
    {
247
        return $account->accountType->type;
248
    }
249
250
    /**
251
     * Return account type or null if not found.
252
     *
253
     * @param string $type
254
     *
255
     * @return AccountType|null
256
     */
257
    public function getAccountTypeByType(string $type): ?AccountType
258
    {
259
        return AccountType::whereType($type)->first();
260
    }
261
262
    /**
263
     * @param array $accountIds
264
     *
265
     * @return Collection
266
     */
267
    public function getAccountsById(array $accountIds): Collection
268
    {
269
        /** @var Collection $result */
270
        $query = $this->user->accounts();
271
272
        if (count($accountIds) > 0) {
273
            $query->whereIn('accounts.id', $accountIds);
274
        }
275
        $query->orderBy('accounts.active', 'DESC');
276
        $query->orderBy('accounts.name', 'ASC');
277
278
        $result = $query->get(['accounts.*']);
279
280
        return $result;
281
    }
282
283
    /**
284
     * @param array $types
285
     *
286
     * @return Collection
287
     */
288
    public function getAccountsByType(array $types): Collection
289
    {
290
        /** @var Collection $result */
291
        $query = $this->user->accounts();
292
        if (count($types) > 0) {
293
            $query->accountTypeIn($types);
294
        }
295
        $query->orderBy('accounts.active', 'DESC');
296
        $query->orderBy('accounts.name', 'ASC');
297
        $result = $query->get(['accounts.*']);
298
299
300
        return $result;
301
    }
302
303
    /**
304
     * @param array $types
305
     *
306
     * @return Collection
307
     */
308
    public function getActiveAccountsByType(array $types): Collection
309
    {
310
        /** @var Collection $result */
311
        $query = $this->user->accounts()->with(
312
            ['accountmeta' => function (HasMany $query) {
313
                $query->where('name', 'account_role');
314
            }, 'attachments']
315
        );
316
        if (count($types) > 0) {
317
            $query->accountTypeIn($types);
318
        }
319
        $query->where('active', 1);
320
        $query->orderBy('accounts.account_type_id', 'ASC');
321
        $query->orderBy('accounts.name', 'ASC');
322
323
        return $query->get(['accounts.*']);
324
    }
325
326
    /**
327
     * @return Account
328
     *
329
     * @throws FireflyException
330
     */
331
    public function getCashAccount(): Account
332
    {
333
        /** @var AccountType $type */
334
        $type = AccountType::where('type', AccountType::CASH)->first();
335
        /** @var AccountFactory $factory */
336
        $factory = app(AccountFactory::class);
337
        $factory->setUser($this->user);
338
339
        return $factory->findOrCreate('Cash account', $type->type);
340
    }
341
342
    /**
343
     * Return meta value for account. Null if not found.
344
     *
345
     * @param Account $account
346
     * @param string  $field
347
     *
348
     * @return null|string
349
     */
350
    public function getMetaValue(Account $account, string $field): ?string
351
    {
352
        /** @var AccountMeta $meta */
353
        $meta = $account->accountMeta()->where('name', $field)->first();
354
        if (null === $meta) {
355
            return null;
356
        }
357
358
        return (string)$meta->data;
359
    }
360
361
    /**
362
     * Get note text or null.
363
     *
364
     * @param Account $account
365
     *
366
     * @return null|string
367
     */
368
    public function getNoteText(Account $account): ?string
369
    {
370
        $note = $account->notes()->first();
371
372
        if (null === $note) {
373
            return null;
374
        }
375
376
        return $note->text;
377
    }
378
379
    /**
380
     * @param Account $account
381
     *
382
     * @return TransactionJournal|null
383
     */
384
    public function getOpeningBalance(Account $account): ?TransactionJournal
385
    {
386
        return TransactionJournal
387
            ::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
388
            ->where('transactions.account_id', $account->id)
389
            ->transactionTypes([TransactionType::OPENING_BALANCE])
390
            ->first(['transaction_journals.*']);
391
    }
392
393
    /**
394
     * Returns the amount of the opening balance for this account.
395
     *
396
     * @param Account $account
397
     *
398
     * @return string
399
     */
400
    public function getOpeningBalanceAmount(Account $account): ?string
401
    {
402
403
        $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
404
                                     ->where('transactions.account_id', $account->id)
405
                                     ->transactionTypes([TransactionType::OPENING_BALANCE])
406
                                     ->first(['transaction_journals.*']);
407
        if (null === $journal) {
408
            return null;
409
        }
410
        $transaction = $journal->transactions()->where('account_id', $account->id)->first();
411
        if (null === $transaction) {
412
            return null;
413
        }
414
415
        return (string)$transaction->amount;
416
    }
417
418
    /**
419
     * Return date of opening balance as string or null.
420
     *
421
     * @param Account $account
422
     *
423
     * @return null|string
424
     */
425
    public function getOpeningBalanceDate(Account $account): ?string
426
    {
427
        $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
428
                                     ->where('transactions.account_id', $account->id)
429
                                     ->transactionTypes([TransactionType::OPENING_BALANCE])
430
                                     ->first(['transaction_journals.*']);
431
        if (null === $journal) {
432
            return null;
433
        }
434
435
        return $journal->date->format('Y-m-d');
436
    }
437
438
    /**
439
     * @param Account $account
440
     *
441
     * @return TransactionGroup|null
442
     */
443
    public function getOpeningBalanceGroup(Account $account): ?TransactionGroup
444
    {
445
        $journal = $this->getOpeningBalance($account);
446
        $group   = null;
447
        if (null !== $journal) {
448
            $group = $journal->transactionGroup;
449
        }
450
451
        return $group;
452
    }
453
454
    /**
455
     * @param Account $account
456
     *
457
     * @return Collection
458
     */
459
    public function getPiggyBanks(Account $account): Collection
460
    {
461
        return $account->piggyBanks()->get();
462
    }
463
464
    /**
465
     * @param Account $account
466
     *
467
     * @return Account|null
468
     *
469
     * @throws FireflyException
470
     */
471
    public function getReconciliation(Account $account): ?Account
472
    {
473
        if (AccountType::ASSET !== $account->accountType->type) {
474
            throw new FireflyException(sprintf('%s is not an asset account.', $account->name));
475
        }
476
477
        $name = trans('firefly.reconciliation_account_name', ['name' => $account->name]);
478
479
        /** @var AccountType $type */
480
        $type     = AccountType::where('type', AccountType::RECONCILIATION)->first();
481
        $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get();
482
483
        // TODO no longer need to loop like this
484
485
        /** @var Account $current */
486
        foreach ($accounts as $current) {
487
            if ($current->name === $name) {
488
                return $current;
489
            }
490
        }
491
        /** @var AccountFactory $factory */
492
        $factory = app(AccountFactory::class);
493
        $factory->setUser($account->user);
494
        $account = $factory->findOrCreate($name, $type->type);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type array and array; however, parameter $accountName of FireflyIII\Factory\AccountFactory::findOrCreate() does only seem to accept string, 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 ignore-type  annotation

494
        $account = $factory->findOrCreate(/** @scrutinizer ignore-type */ $name, $type->type);
Loading history...
495
496
        return $account;
497
    }
498
499
    /**
500
     * @param Account $account
501
     *
502
     * @return bool
503
     */
504
    public function isLiability(Account $account): bool
505
    {
506
        return in_array($account->accountType->type, [AccountType::CREDITCARD, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true);
507
    }
508
509
    /**
510
     * Returns the date of the very first transaction in this account.
511
     *
512
     * @param Account $account
513
     *
514
     * @return TransactionJournal|null
515
     */
516
    public function oldestJournal(Account $account): ?TransactionJournal
517
    {
518
        $first = $account->transactions()
519
                         ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
520
                         ->orderBy('transaction_journals.date', 'ASC')
521
                         ->orderBy('transaction_journals.order', 'DESC')
522
                         ->where('transaction_journals.user_id', $this->user->id)
523
                         ->orderBy('transaction_journals.id', 'ASC')
524
                         ->first(['transaction_journals.id']);
525
        if (null !== $first) {
526
            return TransactionJournal::find((int)$first->id);
527
        }
528
529
        return null;
530
    }
531
532
    /**
533
     * Returns the date of the very first transaction in this account.
534
     *
535
     * @param Account $account
536
     *
537
     * @return Carbon|null
538
     */
539
    public function oldestJournalDate(Account $account): ?Carbon
540
    {
541
        $result  = null;
542
        $journal = $this->oldestJournal($account);
543
        if (null !== $journal) {
544
            $result = $journal->date;
545
        }
546
547
        return $result;
548
    }
549
550
    /**
551
     * @param string $query
552
     * @param array  $types
553
     *
554
     * @return Collection
555
     */
556
    public function searchAccount(string $query, array $types): Collection
557
    {
558
        $dbQuery = $this->user->accounts()
559
                              ->where('active', 1)
560
                              ->orderBy('accounts.name', 'ASC')
561
                              ->with(['accountType']);
562
        if ('' !== $query) {
563
            // split query on spaces just in case:
564
            $parts = explode(' ', $query);
565
            foreach($parts as $part) {
566
                $search = sprintf('%%%s%%', $part);
567
                $dbQuery->where('name', 'LIKE', $search);
568
            }
569
570
        }
571
        if (count($types) > 0) {
572
            $dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
573
            $dbQuery->whereIn('account_types.type', $types);
574
        }
575
576
        return $dbQuery->get(['accounts.*']);
577
    }
578
579
    /**
580
     * @param User $user
581
     */
582
    public function setUser(User $user): void
583
    {
584
        $this->user = $user;
585
    }
586
587
    /**
588
     * @param array $data
589
     *
590
     * @return Account
591
     * @throws FireflyException
592
     */
593
    public function store(array $data): Account
594
    {
595
        /** @var AccountFactory $factory */
596
        $factory = app(AccountFactory::class);
597
        $factory->setUser($this->user);
598
599
        return $factory->create($data);
600
    }
601
602
    /**
603
     * @param Account $account
604
     * @param array   $data
605
     *
606
     * @return Account
607
     * @throws FireflyException
608
     */
609
    public function update(Account $account, array $data): Account
610
    {
611
        /** @var AccountUpdateService $service */
612
        $service = app(AccountUpdateService::class);
613
        $account = $service->update($account, $data);
614
615
        return $account;
616
    }
617
618
    /**
619
     * @param array $types
620
     *
621
     * @return Collection
622
     */
623
    public function getInactiveAccountsByType(array $types): Collection
624
    {
625
        /** @var Collection $result */
626
        $query = $this->user->accounts()->with(
627
            ['accountmeta' => function (HasMany $query) {
628
                $query->where('name', 'account_role');
629
            }]
630
        );
631
        if (count($types) > 0) {
632
            $query->accountTypeIn($types);
633
        }
634
        $query->where('active', 0);
635
        $query->orderBy('accounts.account_type_id', 'ASC');
636
        $query->orderBy('accounts.name', 'ASC');
637
638
        return $query->get(['accounts.*']);
639
    }
640
641
    /**
642
     * @inheritDoc
643
     */
644
    public function getLocation(Account $account): ?Location
645
    {
646
        return $account->locations()->first();
647
    }
648
649
    /**
650
     * @inheritDoc
651
     */
652
    public function getAttachments(Account $account): Collection
653
    {
654
        return $account->attachments()->get();
655
    }
656
657
    /**
658
     * @inheritDoc
659
     */
660
    public function getUsedCurrencies(Account $account): Collection
661
    {
662
        $info        = $account->transactions()->get(['transaction_currency_id', 'foreign_currency_id'])->toArray();
663
        $currencyIds = [];
664
        foreach ($info as $entry) {
665
            $currencyIds[] = (int) $entry['transaction_currency_id'];
666
            $currencyIds[] = (int) $entry['foreign_currency_id'];
667
        }
668
        $currencyIds = array_unique($currencyIds);
669
670
        return TransactionCurrency::whereIn('id', $currencyIds)->get();
671
    }
672
}
673