Passed
Push — master ( 37b02e...ebbbe1 )
by James
08:59
created

AccountServiceTrait   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 35
dl 0
loc 346
rs 9
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
B storeIBJournal() 0 79 3
A deleteIB() 0 16 2
A getIBJournal() 0 14 2
A filterIban() 0 16 3
A storeOpposingAccount() 0 9 1
A updateIB() 0 22 3
A updateNote() 0 19 4
B updateIBJournal() 0 42 5
D updateMetaData() 0 28 9
A validIBData() 0 11 3
1
<?php
2
/**
3
 * AccountServiceTrait.php
4
 * Copyright (c) 2018 [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\Services\Internal\Support;
25
26
use FireflyIII\Factory\AccountFactory;
27
use FireflyIII\Factory\AccountMetaFactory;
28
use FireflyIII\Factory\TransactionFactory;
29
use FireflyIII\Factory\TransactionJournalFactory;
30
use FireflyIII\Models\Account;
31
use FireflyIII\Models\AccountMeta;
32
use FireflyIII\Models\AccountType;
33
use FireflyIII\Models\Note;
34
use FireflyIII\Models\Transaction;
35
use FireflyIII\Models\TransactionJournal;
36
use FireflyIII\Models\TransactionType;
37
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
38
use FireflyIII\User;
39
use Log;
40
use Validator;
41
42
/**
43
 * Trait AccountServiceTrait
44
 *
45
 * @package FireflyIII\Services\Internal\Support
46
 */
47
trait AccountServiceTrait
48
{
49
    /** @var array */
50
    public $validAssetFields = ['accountRole', 'accountNumber', 'currency_id', 'BIC'];
51
    /** @var array */
52
    public $validCCFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC'];
53
    /** @var array */
54
    public $validFields = ['accountNumber', 'currency_id', 'BIC'];
55
56
    /**
57
     * @param Account $account
58
     *
59
     * @return bool
60
     */
61
    public function deleteIB(Account $account): bool
62
    {
63
        Log::debug(sprintf('deleteIB() for account #%d', $account->id));
64
        $openingBalance = $this->getIBJournal($account);
65
66
        // opening balance data? update it!
67
        if (null !== $openingBalance) {
68
            Log::debug('Opening balance journal found, delete journal.');
69
            /** @var JournalDestroyService $service */
70
            $service = app(JournalDestroyService::class);
71
            $service->destroy($openingBalance);
72
73
            return true;
74
        }
75
76
        return true;
77
    }
78
79
    /**
80
     * @param null|string $iban
81
     *
82
     * @return null|string
83
     */
84
    public function filterIban(?string $iban)
85
    {
86
        if (null === $iban) {
87
            return null;
88
        }
89
        $data      = ['iban' => $iban];
90
        $rules     = ['iban' => 'required|iban'];
91
        $validator = Validator::make($data, $rules);
92
        if ($validator->fails()) {
93
            Log::error(sprintf('Detected invalid IBAN ("%s"). Return NULL instead.', $iban));
94
95
            return null;
96
        }
97
98
99
        return $iban;
100
    }
101
102
    /**
103
     * Find existing opening balance.
104
     *
105
     * @param Account $account
106
     *
107
     * @return TransactionJournal|null
108
     */
109
    public function getIBJournal(Account $account): ?TransactionJournal
110
    {
111
        $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
112
                                     ->where('transactions.account_id', $account->id)
113
                                     ->transactionTypes([TransactionType::OPENING_BALANCE])
114
                                     ->first(['transaction_journals.*']);
115
        if (null === $journal) {
116
            Log::debug('Could not find a opening balance journal, return NULL.');
117
118
            return null;
119
        }
120
        Log::debug(sprintf('Found opening balance: journal #%d.', $journal->id));
121
122
        return $journal;
123
    }
124
125
    /**
126
     * @param Account $account
127
     * @param array   $data
128
     *
129
     * @return TransactionJournal|null
130
     */
131
    public function storeIBJournal(Account $account, array $data): ?TransactionJournal
132
    {
133
        $amount = (string)$data['openingBalance'];
134
        Log::debug(sprintf('Submitted amount is %s', $amount));
135
136
        if (0 === bccomp($amount, '0')) {
137
            return null;
138
        }
139
140
        // store journal, without transactions:
141
        $name        = $data['name'];
142
        $currencyId  = $data['currency_id'];
143
        $journalData = [
144
            'type'                    => TransactionType::OPENING_BALANCE,
145
            'user'                    => $account->user->id,
146
            'transaction_currency_id' => $currencyId,
147
            'description'             => (string)trans('firefly.initial_balance_description', ['account' => $account->name]),
148
            'completed'               => true,
149
            'date'                    => $data['openingBalanceDate'],
150
            'bill_id'                 => null,
151
            'bill_name'               => null,
152
            'piggy_bank_id'           => null,
153
            'piggy_bank_name'         => null,
154
            'tags'                    => null,
155
            'notes'                   => null,
156
            'transactions'            => [],
157
158
        ];
159
        /** @var TransactionJournalFactory $factory */
160
        $factory = app(TransactionJournalFactory::class);
161
        $factory->setUser($account->user);
162
        $journal  = $factory->create($journalData);
163
        $opposing = $this->storeOpposingAccount($account->user, $name);
164
        Log::notice(sprintf('Created new opening balance journal: #%d', $journal->id));
165
166
        $firstAccount  = $account;
167
        $secondAccount = $opposing;
168
        $firstAmount   = $amount;
169
        $secondAmount  = bcmul($amount, '-1');
170
        Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount));
171
172
        if (bccomp($amount, '0') === -1) {
173
            Log::debug(sprintf('%s is a negative number.', $amount));
174
            $firstAccount  = $opposing;
175
            $secondAccount = $account;
176
            $firstAmount   = bcmul($amount, '-1');
177
            $secondAmount  = $amount;
178
            Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount));
179
        }
180
        /** @var TransactionFactory $factory */
181
        $factory = app(TransactionFactory::class);
182
        $factory->setUser($account->user);
183
        $one = $factory->create(
184
            [
185
                'account'             => $firstAccount,
186
                'transaction_journal' => $journal,
187
                'amount'              => $firstAmount,
188
                'currency_id'         => $currencyId,
189
                'description'         => null,
190
                'identifier'          => 0,
191
                'foreign_amount'      => null,
192
                'reconciled'          => false,
193
            ]
194
        );
195
        $two = $factory->create(
196
            [
197
                'account'             => $secondAccount,
198
                'transaction_journal' => $journal,
199
                'amount'              => $secondAmount,
200
                'currency_id'         => $currencyId,
201
                'description'         => null,
202
                'identifier'          => 0,
203
                'foreign_amount'      => null,
204
                'reconciled'          => false,
205
            ]
206
        );
207
        Log::notice(sprintf('Stored two transactions for new account, #%d and #%d', $one->id, $two->id));
208
209
        return $journal;
210
    }
211
212
    /**
213
     * @param User   $user
214
     * @param string $name
215
     *
216
     * @return Account
217
     */
218
    public function storeOpposingAccount(User $user, string $name): Account
219
    {
220
        $name .= ' initial balance';
221
        Log::debug('Going to create an opening balance opposing account.');
222
        /** @var AccountFactory $factory */
223
        $factory = app(AccountFactory::class);
224
        $factory->setUser($user);
225
226
        return $factory->findOrCreate($name, AccountType::INITIAL_BALANCE);
227
    }
228
229
    /**
230
     * @param Account $account
231
     * @param array   $data
232
     *
233
     * @return bool
234
     */
235
    public function updateIB(Account $account, array $data): bool
236
    {
237
        Log::debug(sprintf('updateInitialBalance() for account #%d', $account->id));
238
        $openingBalance = $this->getIBJournal($account);
239
240
        // no opening balance journal? create it:
241
        if (null === $openingBalance) {
242
            Log::debug('No opening balance journal yet, create journal.');
243
            $this->storeIBJournal($account, $data);
244
245
            return true;
246
        }
247
248
        // opening balance data? update it!
249
        if (null !== $openingBalance->id) {
250
            Log::debug('Opening balance journal found, update journal.');
251
            $this->updateIBJournal($account, $openingBalance, $data);
252
253
            return true;
254
        }
255
256
        return true; // @codeCoverageIgnore
257
    }
258
259
    /**
260
     * @param Account            $account
261
     * @param TransactionJournal $journal
262
     * @param array              $data
263
     *
264
     * @return bool
265
     */
266
    public function updateIBJournal(Account $account, TransactionJournal $journal, array $data): bool
267
    {
268
        $date           = $data['openingBalanceDate'];
269
        $amount         = (string)$data['openingBalance'];
270
        $negativeAmount = bcmul($amount, '-1');
271
        $currencyId     = (int)$data['currency_id'];
272
273
        Log::debug(sprintf('Submitted amount for opening balance to update is "%s"', $amount));
274
        if (0 === bccomp($amount, '0')) {
275
            Log::notice(sprintf('Amount "%s" is zero, delete opening balance.', $amount));
276
            /** @var JournalDestroyService $service */
277
            $service = app(JournalDestroyService::class);
278
            $service->destroy($journal);
279
280
281
            return true;
282
        }
283
284
        // update date:
285
        $journal->date                    = $date;
286
        $journal->transaction_currency_id = $currencyId;
0 ignored issues
show
Bug introduced by James Cole
The property transaction_currency_id does not exist on FireflyIII\Models\TransactionJournal. Did you mean transactionCurrency?
Loading history...
287
        $journal->save();
288
289
        // update transactions:
290
        /** @var Transaction $transaction */
291
        foreach ($journal->transactions()->get() as $transaction) {
292
            if ((int)$account->id === (int)$transaction->account_id) {
293
                Log::debug(sprintf('Will (eq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $amount));
294
                $transaction->amount                  = $amount;
295
                $transaction->transaction_currency_id = $currencyId;
296
                $transaction->save();
297
            }
298
            if (!((int)$account->id === (int)$transaction->account_id)) {
299
                Log::debug(sprintf('Will (neq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $negativeAmount));
300
                $transaction->amount                  = $negativeAmount;
301
                $transaction->transaction_currency_id = $currencyId;
302
                $transaction->save();
303
            }
304
        }
305
        Log::debug('Updated opening balance journal.');
306
307
        return true;
308
    }
309
310
    /**
311
     * Update meta data for account. Depends on type which fields are valid.
312
     *
313
     * @param Account $account
314
     * @param array   $data
315
     */
316
    public function updateMetaData(Account $account, array $data)
317
    {
318
        $fields = $this->validFields;
319
320
        if ($account->accountType->type === AccountType::ASSET) {
0 ignored issues
show
Bug introduced by James Cole
The property type does not seem to exist on FireflyIII\Models\AccountType. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
321
            $fields = $this->validAssetFields;
322
        }
323
        if ($account->accountType->type === AccountType::ASSET && $data['accountRole'] === 'ccAsset') {
324
            $fields = $this->validCCFields;
325
        }
326
        /** @var AccountMetaFactory $factory */
327
        $factory = app(AccountMetaFactory::class);
328
        foreach ($fields as $field) {
329
            /** @var AccountMeta $entry */
330
            $entry = $account->accountMeta()->where('name', $field)->first();
331
332
            // if $data has field and $entry is null, create new one:
333
            if (isset($data[$field]) && null === $entry) {
334
                Log::debug(sprintf('Created meta-field "%s":"%s" for account #%d ("%s") ', $field, $data[$field], $account->id, $account->name));
335
                $factory->create(['account_id' => $account->id, 'name' => $field, 'data' => $data[$field],]);
336
            }
337
338
            // if $data has field and $entry is not null, update $entry:
339
            // let's not bother with a service.
340
            if (isset($data[$field]) && null !== $entry) {
341
                $entry->data = $data[$field];
0 ignored issues
show
Bug introduced by James Cole
The property data does not seem to exist on FireflyIII\Models\AccountMeta. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
342
                $entry->save();
343
                Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $data[$field], $account->id, $account->name));
344
            }
345
        }
346
    }
347
348
    /**
349
     * @param Account $account
350
     * @param string  $note
351
     *
352
     * @return bool
353
     */
354
    public function updateNote(Account $account, string $note): bool
355
    {
356
        if (0 === strlen($note)) {
357
            $dbNote = $account->notes()->first();
358
            if (null !== $dbNote) {
359
                $dbNote->delete();
360
            }
361
362
            return true;
363
        }
364
        $dbNote = $account->notes()->first();
365
        if (null === $dbNote) {
366
            $dbNote = new Note;
367
            $dbNote->noteable()->associate($account);
368
        }
369
        $dbNote->text = trim($note);
370
        $dbNote->save();
371
372
        return true;
373
    }
374
375
    /**
376
     * Verify if array contains valid data to possibly store or update the opening balance.
377
     *
378
     * @param array $data
379
     *
380
     * @return bool
381
     */
382
    public function validIBData(array $data): bool
383
    {
384
        $data['openingBalance'] = (string)($data['openingBalance'] ?? '');
385
        if (isset($data['openingBalance'], $data['openingBalanceDate']) && \strlen($data['openingBalance']) > 0) {
386
            Log::debug('Array has valid opening balance data.');
387
388
            return true;
389
        }
390
        Log::debug('Array does not have valid opening balance data.');
391
392
        return false;
393
    }
394
}
395