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 ( f4b9f8...380f59 )
by James
45:22 queued 31:24
created

FixAccountTypes::inspectJournal()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 45
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 29
c 1
b 0
f 0
dl 0
loc 45
rs 8.5226
cc 7
nc 7
nop 1
1
<?php
2
/**
3
 * FixAccountTypes.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
22
declare(strict_types=1);
23
24
namespace FireflyIII\Console\Commands\Correction;
25
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Factory\AccountFactory;
28
use FireflyIII\Models\AccountType;
29
use FireflyIII\Models\Transaction;
30
use FireflyIII\Models\TransactionJournal;
31
use FireflyIII\Models\TransactionType;
32
use Illuminate\Console\Command;
33
use Log;
34
35
/**
36
 * Class FixAccountTypes
37
 */
38
class FixAccountTypes extends Command
39
{
40
    /**
41
     * The console command description.
42
     *
43
     * @var string
44
     */
45
    protected $description = 'Make sure all journals have the correct from/to account types.';
46
    /**
47
     * The name and signature of the console command.
48
     *
49
     * @var string
50
     */
51
    protected $signature = 'firefly-iii:fix-account-types';
52
    /** @var array */
53
    private $expected;
54
    /** @var AccountFactory */
55
    private $factory;
56
    /** @var array */
57
    private $fixable;
58
    /** @var int */
59
    private $count;
60
61
    /**
62
     * Execute the console command.
63
     *
64
     * @return int
65
     * @throws FireflyException
66
     */
67
    public function handle(): int
68
    {
69
        $this->stupidLaravel();
70
        Log::debug('Now in fix-account-types');
71
        $start         = microtime(true);
72
        $this->factory = app(AccountFactory::class);
73
        // some combinations can be fixed by this script:
74
        $this->fixable = [
75
            // transfers from asset to liability and vice versa
76
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN),
77
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT),
78
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE),
79
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET),
80
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET),
81
            sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET),
82
83
            // withdrawals with a revenue account as destination instead of an expense account.
84
            sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE),
85
86
            // deposits with an expense account as source instead of a revenue account.
87
            sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET),
88
        ];
89
90
91
        $this->expected = config('firefly.source_dests');
92
        $journals       = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
93
        Log::debug(sprintf('Found %d journals to fix.', $journals->count()));
94
        foreach ($journals as $journal) {
95
            $this->inspectJournal($journal);
96
        }
97
        if (0 === $this->count) {
98
            Log::debug('No journals had to be fixed.');
99
            $this->info('All account types are OK!');
100
        }
101
        if (0 !== $this->count) {
102
            Log::debug(sprintf('%d journals had to be fixed.', $this->count));
103
            $this->info(sprintf('Acted on %d transaction(s)!', $this->count));
104
        }
105
106
        $end = round(microtime(true) - $start, 2);
107
        $this->info(sprintf('Verifying account types took %s seconds', $end));
108
109
        return 0;
110
    }
111
112
    /**
113
     * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
114
     * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
115
     * be called from the handle method instead of using the constructor to initialize the command.
116
     *
117
     * @codeCoverageIgnore
118
     */
119
    private function stupidLaravel(): void
120
    {
121
        $this->count = 0;
122
    }
123
124
    /**
125
     * @param TransactionJournal $journal
126
     * @param string $type
127
     * @param Transaction $source
128
     * @param Transaction $dest
129
     * @throws FireflyException
130
     */
131
    private function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void
132
    {
133
        $this->count++;
134
        // variables:
135
        $combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type);
136
137
        switch ($combination) {
138
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN):
139
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT):
140
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE):
141
                // from an asset to a liability should be a withdrawal:
142
                $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
143
                $journal->transactionType()->associate($withdrawal);
144
                $journal->save();
145
                $this->info(sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id));
146
                // check it again:
147
                $this->inspectJournal($journal);
148
                break;
149
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET):
150
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET):
151
            case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET):
152
                // from a liability to an asset should be a deposit.
153
                $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first();
154
                $journal->transactionType()->associate($deposit);
155
                $journal->save();
156
                $this->info(sprintf('Converted transaction #%d from a transfer to a deposit.', $journal->id));
157
                // check it again:
158
                $this->inspectJournal($journal);
159
160
                break;
161
            case sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE):
162
                // withdrawals with a revenue account as destination instead of an expense account.
163
                $this->factory->setUser($journal->user);
164
                $oldDest = $dest->account;
165
                $result  = $this->factory->findOrCreate($dest->account->name, AccountType::EXPENSE);
166
                $dest->account()->associate($result);
167
                $dest->save();
168
                $this->info(
169
                    sprintf(
170
                        'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id,
171
                        $oldDest->id, $oldDest->name,
172
                        $result->id, $result->name
173
                    )
174
                );
175
                $this->inspectJournal($journal);
176
                break;
177
            case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET):
178
                // deposits with an expense account as source instead of a revenue account.
179
                // find revenue account.
180
                $this->factory->setUser($journal->user);
181
                $result    = $this->factory->findOrCreate($source->account->name, AccountType::REVENUE);
182
                $oldSource = $dest->account;
183
                $source->account()->associate($result);
184
                $source->save();
185
                $this->info(
186
                    sprintf(
187
                        'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id,
188
                        $oldSource->id, $oldSource->name,
189
                        $result->id, $result->name
190
                    )
191
                );
192
                $this->inspectJournal($journal);
193
                break;
194
            default:
195
                $this->info(sprintf('The source account of %s #%d cannot be of type "%s".', $type, $journal->id, $source->account->accountType->type));
196
                $this->info(sprintf('The destination account of %s #%d cannot be of type "%s".', $type, $journal->id, $dest->account->accountType->type));
197
198
                break;
199
200
        }
201
202
    }
203
204
    /**
205
     * @param TransactionJournal $journal
206
     *
207
     * @return Transaction
208
     */
209
    private function getDestinationTransaction(TransactionJournal $journal): Transaction
210
    {
211
        return $journal->transactions->firstWhere('amount', '>', 0);
212
    }
213
214
    /**
215
     * @param TransactionJournal $journal
216
     *
217
     * @return Transaction
218
     */
219
    private function getSourceTransaction(TransactionJournal $journal): Transaction
220
    {
221
        return $journal->transactions->firstWhere('amount', '<', 0);
222
    }
223
224
    /**
225
     * @param TransactionJournal $journal
226
     *
227
     * @throws FireflyException
228
     */
229
    private function inspectJournal(TransactionJournal $journal): void
230
    {
231
        Log::debug(sprintf('Now trying to fix journal #%d', $journal->id));
232
        $count = $journal->transactions()->count();
233
        if (2 !== $count) {
234
            Log::debug(sprintf('Journal has %d transactions, so cant fix.', $count));
235
            $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $count));
236
237
            return;
238
        }
239
        $type              = $journal->transactionType->type;
240
        $sourceTransaction = $this->getSourceTransaction($journal);
241
        $destTransaction   = $this->getDestinationTransaction($journal);
242
        if (null === $sourceTransaction) {
243
            Log::error('Source transaction is unexpectedly NULL. Wont fix this journal.');
244
245
            return;
246
        }
247
        if (null === $destTransaction) {
248
            Log::error('Destination transaction is unexpectedly NULL. Wont fix this journal.');
249
250
            return;
251
        }
252
253
        $sourceAccount     = $sourceTransaction->account;
254
        $sourceAccountType = $sourceAccount->accountType->type;
255
        $destAccount       = $destTransaction->account;
256
        $destAccountType   = $destAccount->accountType->type;
257
258
        if (!isset($this->expected[$type])) {
259
            // @codeCoverageIgnoreStart
260
            Log::info(sprintf('No source/destination info for transaction type %s.', $type));
261
            $this->info(sprintf('No source/destination info for transaction type %s.', $type));
262
263
            return;
264
            // @codeCoverageIgnoreEnd
265
        }
266
        if (!isset($this->expected[$type][$sourceAccountType])) {
267
            $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
268
269
            return;
270
        }
271
        $expectedTypes = $this->expected[$type][$sourceAccountType];
272
        if (!in_array($destAccountType, $expectedTypes, true)) {
273
            $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
274
        }
275
    }
276
277
}
278