Passed
Push — master ( 49de82...9f1fc0 )
by James
35:45 queued 23:44
created

AccountRepository::getInactiveAccountsByType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
rs 9.9666
cc 2
nc 2
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\TransactionCurrency;
32
use FireflyIII\Models\TransactionGroup;
33
use FireflyIII\Models\TransactionJournal;
34
use FireflyIII\Models\TransactionType;
35
use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
36
use FireflyIII\Services\Internal\Update\AccountUpdateService;
37
use FireflyIII\User;
38
use Illuminate\Database\Eloquent\Relations\HasMany;
39
use Illuminate\Support\Collection;
40
use Log;
41
42
/**
43
 * Class AccountRepository.
44
 *
45
 */
46
class AccountRepository implements AccountRepositoryInterface
47
{
48
49
    /** @var User */
50
    private $user;
51
52
    /**
53
     * Constructor.
54
     */
55
    public function __construct()
56
    {
57
        if ('testing' === config('app.env')) {
58
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
59
        }
60
    }
61
62
    /**
63
     * @param array $types
64
     *
65
     * @return int
66
     */
67
    public function count(array $types): int
68
    {
69
        return $this->user->accounts()->accountTypeIn($types)->count();
70
    }
71
72
    /**
73
     * Moved here from account CRUD.
74
     *
75
     * @param Account      $account
76
     * @param Account|null $moveTo
77
     *
78
     * @return bool
79
     *
80
81
     */
82
    public function destroy(Account $account, ?Account $moveTo): bool
83
    {
84
        /** @var AccountDestroyService $service */
85
        $service = app(AccountDestroyService::class);
86
        $service->destroy($account, $moveTo);
87
88
        return true;
89
    }
90
91
    /**
92
     * Find account with same name OR same IBAN or both, but not the same type or ID.
93
     *
94
     * @param Collection $accounts
95
     *
96
     * @return Collection
97
     */
98
    public function expandWithDoubles(Collection $accounts): Collection
99
    {
100
        $result = new Collection;
101
        /** @var Account $account */
102
        foreach ($accounts as $account) {
103
            $byName = $this->user->accounts()->where('name', $account->name)
104
                                 ->where('id', '!=', $account->id)->first();
105
            if (null !== $byName) {
106
                $result->push($account);
107
                $result->push($byName);
108
                continue;
109
            }
110
            if (null !== $account->iban) {
111
                $byIban = $this->user->accounts()->where('iban', $account->iban)
112
                                     ->where('id', '!=', $account->id)->first();
113
                if (null !== $byIban) {
114
                    $result->push($account);
115
                    $result->push($byIban);
116
                    continue;
117
                }
118
            }
119
            $result->push($account);
120
        }
121
122
        return $result;
123
124
    }
125
126
    /**
127
     * @param string $number
128
     * @param array  $types
129
     *
130
     * @return Account|null
131
     */
132
    public function findByAccountNumber(string $number, array $types): ?Account
133
    {
134
        $query = $this->user->accounts()
135
                            ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
136
                            ->where('account_meta.name', 'account_number')
137
                            ->where('account_meta.data', json_encode($number));
138
139
        if (count($types) > 0) {
140
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
141
            $query->whereIn('account_types.type', $types);
142
        }
143
144
        /** @var Collection $accounts */
145
        $accounts = $query->get(['accounts.*']);
146
        if ($accounts->count() > 0) {
147
            return $accounts->first();
148
        }
149
150
        return null;
151
    }
152
153
    /**
154
     * @param string $iban
155
     * @param array  $types
156
     *
157
     * @return Account|null
158
     */
159
    public function findByIbanNull(string $iban, array $types): ?Account
160
    {
161
        $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
162
163
        if (count($types) > 0) {
164
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
165
            $query->whereIn('account_types.type', $types);
166
        }
167
168
        // TODO a loop like this is no longer necessary
169
170
        $accounts = $query->get(['accounts.*']);
171
        /** @var Account $account */
172
        foreach ($accounts as $account) {
173
            if ($account->iban === $iban) {
174
                return $account;
175
            }
176
        }
177
178
        return null;
179
    }
180
181
    /**
182
     * @param string $name
183
     * @param array  $types
184
     *
185
     * @return Account|null
186
     */
187
    public function findByName(string $name, array $types): ?Account
188
    {
189
        $query = $this->user->accounts();
190
191
        if (count($types) > 0) {
192
            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
193
            $query->whereIn('account_types.type', $types);
194
        }
195
        Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]);
196
197
        $accounts = $query->get(['accounts.*']);
198
199
        // TODO no longer need to loop like this
200
201
        /** @var Account $account */
202
        foreach ($accounts as $account) {
203
            if ($account->name === $name) {
204
                Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
205
206
                return $account;
207
            }
208
        }
209
        Log::debug(sprintf('There is no account with name "%s" of types', $name), $types);
210
211
        return null;
212
    }
213
214
    /**
215
     * @param int $accountId
216
     *
217
     * @return Account|null
218
     */
219
    public function findNull(int $accountId): ?Account
220
    {
221
        return $this->user->accounts()->find($accountId);
222
    }
223
224
    /**
225
     * @param Account $account
226
     *
227
     * @return TransactionCurrency|null
228
     */
229
    public function getAccountCurrency(Account $account): ?TransactionCurrency
230
    {
231
        $currencyId = (int)$this->getMetaValue($account, 'currency_id');
232
        if ($currencyId > 0) {
233
            return TransactionCurrency::find($currencyId);
234
        }
235
236
        return null;
237
    }
238
239
    /**
240
     * @param Account $account
241
     *
242
     * @return string
243
     */
244
    public function getAccountType(Account $account): string
245
    {
246
        return $account->accountType->type;
247
    }
248
249
    /**
250
     * Return account type or null if not found.
251
     *
252
     * @param string $type
253
     *
254
     * @return AccountType|null
255
     */
256
    public function getAccountTypeByType(string $type): ?AccountType
257
    {
258
        return AccountType::whereType($type)->first();
259
    }
260
261
    /**
262
     * @param array $accountIds
263
     *
264
     * @return Collection
265
     */
266
    public function getAccountsById(array $accountIds): Collection
267
    {
268
        /** @var Collection $result */
269
        $query = $this->user->accounts();
270
271
        if (count($accountIds) > 0) {
272
            $query->whereIn('accounts.id', $accountIds);
273
        }
274
        $query->orderBy('accounts.active', 'DESC');
275
        $query->orderBy('accounts.name', 'ASC');
276
277
        $result = $query->get(['accounts.*']);
278
279
        return $result;
280
    }
281
282
    /**
283
     * @param array $types
284
     *
285
     * @return Collection
286
     */
287
    public function getAccountsByType(array $types): Collection
288
    {
289
        /** @var Collection $result */
290
        $query = $this->user->accounts();
291
        if (count($types) > 0) {
292
            $query->accountTypeIn($types);
293
        }
294
        $query->orderBy('accounts.active', 'DESC');
295
        $query->orderBy('accounts.name', 'ASC');
296
        $result = $query->get(['accounts.*']);
297
298
299
        return $result;
300
    }
301
302
    /**
303
     * @param array $types
304
     *
305
     * @return Collection
306
     */
307
    public function getActiveAccountsByType(array $types): Collection
308
    {
309
        /** @var Collection $result */
310
        $query = $this->user->accounts()->with(
311
            ['accountmeta' => function (HasMany $query) {
312
                $query->where('name', 'account_role');
313
            }]
314
        );
315
        if (count($types) > 0) {
316
            $query->accountTypeIn($types);
317
        }
318
        $query->where('active', 1);
319
        $query->orderBy('accounts.account_type_id', 'ASC');
320
        $query->orderBy('accounts.name', 'ASC');
321
322
        return $query->get(['accounts.*']);
323
    }
324
325
    /**
326
     * @return Account
327
     *
328
     * @throws FireflyException
329
     */
330
    public function getCashAccount(): Account
331
    {
332
        /** @var AccountType $type */
333
        $type = AccountType::where('type', AccountType::CASH)->first();
334
        /** @var AccountFactory $factory */
335
        $factory = app(AccountFactory::class);
336
        $factory->setUser($this->user);
337
338
        return $factory->findOrCreate('Cash account', $type->type);
339
    }
340
341
    /**
342
     * Return meta value for account. Null if not found.
343
     *
344
     * @param Account $account
345
     * @param string  $field
346
     *
347
     * @return null|string
348
     */
349
    public function getMetaValue(Account $account, string $field): ?string
350
    {
351
        /** @var AccountMeta $meta */
352
        $meta = $account->accountMeta()->where('name', $field)->first();
353
        if (null === $meta) {
354
            return null;
355
        }
356
357
        return (string)$meta->data;
358
    }
359
360
    /**
361
     * Get note text or null.
362
     *
363
     * @param Account $account
364
     *
365
     * @return null|string
366
     */
367
    public function getNoteText(Account $account): ?string
368
    {
369
        $note = $account->notes()->first();
370
371
        if (null === $note) {
372
            return null;
373
        }
374
375
        return $note->text;
376
    }
377
378
    /**
379
     * @param Account $account
380
     *
381
     * @return TransactionJournal|null
382
     */
383
    public function getOpeningBalance(Account $account): ?TransactionJournal
384
    {
385
        return TransactionJournal
386
            ::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
387
            ->where('transactions.account_id', $account->id)
388
            ->transactionTypes([TransactionType::OPENING_BALANCE])
389
            ->first(['transaction_journals.*']);
390
    }
391
392
    /**
393
     * Returns the amount of the opening balance for this account.
394
     *
395
     * @param Account $account
396
     *
397
     * @return string
398
     */
399
    public function getOpeningBalanceAmount(Account $account): ?string
400
    {
401
402
        $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
403
                                     ->where('transactions.account_id', $account->id)
404
                                     ->transactionTypes([TransactionType::OPENING_BALANCE])
405
                                     ->first(['transaction_journals.*']);
406
        if (null === $journal) {
407
            return null;
408
        }
409
        $transaction = $journal->transactions()->where('account_id', $account->id)->first();
410
        if (null === $transaction) {
411
            return null;
412
        }
413
414
        return (string)$transaction->amount;
415
    }
416
417
    /**
418
     * Return date of opening balance as string or null.
419
     *
420
     * @param Account $account
421
     *
422
     * @return null|string
423
     */
424
    public function getOpeningBalanceDate(Account $account): ?string
425
    {
426
        $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
427
                                     ->where('transactions.account_id', $account->id)
428
                                     ->transactionTypes([TransactionType::OPENING_BALANCE])
429
                                     ->first(['transaction_journals.*']);
430
        if (null === $journal) {
431
            return null;
432
        }
433
434
        return $journal->date->format('Y-m-d');
435
    }
436
437
    /**
438
     * @param Account $account
439
     *
440
     * @return TransactionGroup|null
441
     */
442
    public function getOpeningBalanceGroup(Account $account): ?TransactionGroup
443
    {
444
        $journal = $this->getOpeningBalance($account);
445
        $group   = null;
446
        if (null !== $journal) {
447
            $group = $journal->transactionGroup;
448
        }
449
450
        return $group;
451
    }
452
453
    /**
454
     * @param Account $account
455
     *
456
     * @return Collection
457
     */
458
    public function getPiggyBanks(Account $account): Collection
459
    {
460
        return $account->piggyBanks()->get();
461
    }
462
463
    /**
464
     * @param Account $account
465
     *
466
     * @return Account|null
467
     *
468
     * @throws FireflyException
469
     */
470
    public function getReconciliation(Account $account): ?Account
471
    {
472
        if (AccountType::ASSET !== $account->accountType->type) {
473
            throw new FireflyException(sprintf('%s is not an asset account.', $account->name));
474
        }
475
        $name = $account->name . ' reconciliation';
476
        /** @var AccountType $type */
477
        $type     = AccountType::where('type', AccountType::RECONCILIATION)->first();
478
        $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get();
479
480
        // TODO no longer need to loop like this
481
482
        /** @var Account $current */
483
        foreach ($accounts as $current) {
484
            if ($current->name === $name) {
485
                return $current;
486
            }
487
        }
488
        /** @var AccountFactory $factory */
489
        $factory = app(AccountFactory::class);
490
        $factory->setUser($account->user);
491
        $account = $factory->findOrCreate($name, $type->type);
492
493
        return $account;
494
    }
495
496
    /**
497
     * @param Account $account
498
     *
499
     * @return bool
500
     */
501
    public function isLiability(Account $account): bool
502
    {
503
        return in_array($account->accountType->type, [AccountType::CREDITCARD, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true);
504
    }
505
506
    /**
507
     * Returns the date of the very first transaction in this account.
508
     *
509
     * @param Account $account
510
     *
511
     * @return TransactionJournal|null
512
     */
513
    public function oldestJournal(Account $account): ?TransactionJournal
514
    {
515
        $first = $account->transactions()
516
                         ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
517
                         ->orderBy('transaction_journals.date', 'ASC')
518
                         ->orderBy('transaction_journals.order', 'DESC')
519
                         ->where('transaction_journals.user_id', $this->user->id)
520
                         ->orderBy('transaction_journals.id', 'ASC')
521
                         ->first(['transaction_journals.id']);
522
        if (null !== $first) {
523
            return TransactionJournal::find((int)$first->id);
524
        }
525
526
        return null;
527
    }
528
529
    /**
530
     * Returns the date of the very first transaction in this account.
531
     *
532
     * @param Account $account
533
     *
534
     * @return Carbon|null
535
     */
536
    public function oldestJournalDate(Account $account): ?Carbon
537
    {
538
        $result  = null;
539
        $journal = $this->oldestJournal($account);
540
        if (null !== $journal) {
541
            $result = $journal->date;
542
        }
543
544
        return $result;
545
    }
546
547
    /**
548
     * @param string $query
549
     * @param array  $types
550
     *
551
     * @return Collection
552
     */
553
    public function searchAccount(string $query, array $types): Collection
554
    {
555
        $dbQuery = $this->user->accounts()
556
                              ->where('active', 1)
557
                              ->with(['accountType']);
558
        if ('' !== $query) {
559
            $search = sprintf('%%%s%%', $query);
560
            $dbQuery->where('name', 'LIKE', $search);
561
        }
562
        if (count($types) > 0) {
563
            $dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
564
            $dbQuery->whereIn('account_types.type', $types);
565
        }
566
567
        return $dbQuery->get(['accounts.*']);
568
    }
569
570
    /**
571
     * @param User $user
572
     */
573
    public function setUser(User $user): void
574
    {
575
        $this->user = $user;
576
    }
577
578
    /**
579
     * @param array $data
580
     *
581
     * @return Account
582
     * @throws FireflyException
583
     */
584
    public function store(array $data): Account
585
    {
586
        /** @var AccountFactory $factory */
587
        $factory = app(AccountFactory::class);
588
        $factory->setUser($this->user);
589
590
        return $factory->create($data);
591
    }
592
593
    /**
594
     * @param Account $account
595
     * @param array   $data
596
     *
597
     * @return Account
598
     * @throws FireflyException
599
     */
600
    public function update(Account $account, array $data): Account
601
    {
602
        /** @var AccountUpdateService $service */
603
        $service = app(AccountUpdateService::class);
604
        $account = $service->update($account, $data);
605
606
        return $account;
607
    }
608
609
    /**
610
     * @param array $types
611
     *
612
     * @return Collection
613
     */
614
    public function getInactiveAccountsByType(array $types): Collection
615
    {
616
        /** @var Collection $result */
617
        $query = $this->user->accounts()->with(
618
            ['accountmeta' => function (HasMany $query) {
619
                $query->where('name', 'account_role');
620
            }]
621
        );
622
        if (count($types) > 0) {
623
            $query->accountTypeIn($types);
624
        }
625
        $query->where('active', 0);
626
        $query->orderBy('accounts.account_type_id', 'ASC');
627
        $query->orderBy('accounts.name', 'ASC');
628
629
        return $query->get(['accounts.*']);
630
    }
631
}
632