Completed
Push — master ( bae40e...a4cb2c )
by James
19:36 queued 09:47
created

AccountRepository::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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