1 | <?php |
||||||
2 | declare(strict_types=1); |
||||||
3 | /** |
||||||
4 | * VerifyDatabase.php |
||||||
5 | * Copyright (c) 2017 [email protected] |
||||||
6 | * |
||||||
7 | * This file is part of Firefly III. |
||||||
8 | * |
||||||
9 | * Firefly III is free software: you can redistribute it and/or modify |
||||||
10 | * it under the terms of the GNU General Public License as published by |
||||||
11 | * the Free Software Foundation, either version 3 of the License, or |
||||||
12 | * (at your option) any later version. |
||||||
13 | * |
||||||
14 | * Firefly III is distributed in the hope that it will be useful, |
||||||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
17 | * GNU General Public License for more details. |
||||||
18 | * |
||||||
19 | * You should have received a copy of the GNU General Public License |
||||||
20 | * along with Firefly III. If not, see <http://www.gnu.org/licenses/>. |
||||||
21 | */ |
||||||
22 | |||||||
23 | namespace FireflyIII\Console\Commands; |
||||||
24 | |||||||
25 | use Crypt; |
||||||
26 | use DB; |
||||||
27 | use FireflyIII\Models\Account; |
||||||
28 | use FireflyIII\Models\AccountType; |
||||||
29 | use FireflyIII\Models\Budget; |
||||||
30 | use FireflyIII\Models\LinkType; |
||||||
31 | use FireflyIII\Models\PiggyBankEvent; |
||||||
32 | use FireflyIII\Models\Transaction; |
||||||
33 | use FireflyIII\Models\TransactionJournal; |
||||||
34 | use FireflyIII\Models\TransactionType; |
||||||
35 | use FireflyIII\Repositories\User\UserRepositoryInterface; |
||||||
36 | use FireflyIII\User; |
||||||
37 | use Illuminate\Console\Command; |
||||||
38 | use Illuminate\Contracts\Encryption\DecryptException; |
||||||
39 | use Illuminate\Database\Eloquent\Builder; |
||||||
40 | use Preferences; |
||||||
41 | use Schema; |
||||||
42 | use stdClass; |
||||||
43 | |||||||
44 | /** |
||||||
45 | * Class VerifyDatabase. |
||||||
46 | * |
||||||
47 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||||||
48 | */ |
||||||
49 | class VerifyDatabase extends Command |
||||||
50 | { |
||||||
51 | /** |
||||||
52 | * The console command description. |
||||||
53 | * |
||||||
54 | * @var string |
||||||
55 | */ |
||||||
56 | protected $description = 'Will verify your database.'; |
||||||
57 | /** |
||||||
58 | * The name and signature of the console command. |
||||||
59 | * |
||||||
60 | * @var string |
||||||
61 | */ |
||||||
62 | protected $signature = 'firefly:verify'; |
||||||
63 | |||||||
64 | /** |
||||||
65 | * Execute the console command. |
||||||
66 | */ |
||||||
67 | public function handle() |
||||||
68 | { |
||||||
69 | // if table does not exist, return false |
||||||
70 | if (!Schema::hasTable('users')) { |
||||||
71 | return; |
||||||
72 | } |
||||||
73 | |||||||
74 | $this->reportObject('budget'); |
||||||
75 | $this->reportObject('category'); |
||||||
76 | $this->reportObject('tag'); |
||||||
77 | $this->reportAccounts(); |
||||||
78 | $this->reportBudgetLimits(); |
||||||
79 | $this->reportSum(); |
||||||
80 | $this->reportJournals(); |
||||||
81 | $this->reportTransactions(); |
||||||
82 | $this->reportDeletedAccounts(); |
||||||
83 | $this->reportNoTransactions(); |
||||||
84 | $this->reportTransfersBudgets(); |
||||||
85 | $this->reportIncorrectJournals(); |
||||||
86 | $this->repairPiggyBanks(); |
||||||
87 | $this->createLinkTypes(); |
||||||
88 | $this->createAccessTokens(); |
||||||
89 | $this->fixDoubleAmounts(); |
||||||
90 | } |
||||||
91 | |||||||
92 | /** |
||||||
93 | * Create user access tokens, if not present already. |
||||||
94 | */ |
||||||
95 | private function createAccessTokens() |
||||||
96 | { |
||||||
97 | $count = 0; |
||||||
98 | $users = User::get(); |
||||||
99 | /** @var User $user */ |
||||||
100 | foreach ($users as $user) { |
||||||
101 | $pref = Preferences::getForUser($user, 'access_token', null); |
||||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
102 | if (null === $pref) { |
||||||
103 | $token = $user->generateAccessToken(); |
||||||
104 | Preferences::setForUser($user, 'access_token', $token); |
||||||
0 ignored issues
–
show
The method
setForUser() does not exist on FireflyIII\Support\Facades\Preferences . Since you implemented __callStatic , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
105 | $this->line(sprintf('Generated access token for user %s', $user->email)); |
||||||
106 | ++$count; |
||||||
107 | } |
||||||
108 | } |
||||||
109 | if (0 === $count) { |
||||||
110 | $this->info('All access tokens OK!'); |
||||||
111 | } |
||||||
112 | } |
||||||
113 | |||||||
114 | /** |
||||||
115 | * Create default link types if necessary. |
||||||
116 | */ |
||||||
117 | private function createLinkTypes() |
||||||
118 | { |
||||||
119 | $count = 0; |
||||||
120 | $set = [ |
||||||
121 | 'Related' => ['relates to', 'relates to'], |
||||||
122 | 'Refund' => ['(partially) refunds', 'is (partially) refunded by'], |
||||||
123 | 'Paid' => ['(partially) pays for', 'is (partially) paid for by'], |
||||||
124 | 'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'], |
||||||
125 | ]; |
||||||
126 | foreach ($set as $name => $values) { |
||||||
127 | $link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first(); |
||||||
128 | if (null === $link) { |
||||||
129 | $link = new LinkType; |
||||||
130 | $link->name = $name; |
||||||
131 | $link->outward = $values[0]; |
||||||
132 | $link->inward = $values[1]; |
||||||
133 | ++$count; |
||||||
134 | } |
||||||
135 | $link->editable = false; |
||||||
136 | $link->save(); |
||||||
137 | } |
||||||
138 | if (0 === $count) { |
||||||
139 | $this->info('All link types OK!'); |
||||||
140 | } |
||||||
141 | } |
||||||
142 | |||||||
143 | private function fixDoubleAmounts() |
||||||
144 | { |
||||||
145 | $count = 0; |
||||||
146 | // get invalid journals |
||||||
147 | $errored = []; |
||||||
148 | $journals = DB::table('transactions') |
||||||
149 | ->groupBy('transaction_journal_id') |
||||||
0 ignored issues
–
show
'transaction_journal_id' of type string is incompatible with the type array expected by parameter $groups of Illuminate\Database\Query\Builder::groupBy() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
150 | ->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]); |
||||||
151 | /** @var stdClass $entry */ |
||||||
152 | foreach ($journals as $entry) { |
||||||
153 | if (0 !== bccomp((string)$entry->the_sum, '0')) { |
||||||
154 | $errored[] = $entry->transaction_journal_id; |
||||||
155 | } |
||||||
156 | } |
||||||
157 | foreach ($errored as $journalId) { |
||||||
158 | // select and update: |
||||||
159 | $res = Transaction::whereNull('deleted_at')->where('transaction_journal_id', $journalId)->groupBy('amount')->get([DB::raw('MIN(id) as first_id')]); |
||||||
160 | $ids = $res->pluck('first_id')->toArray(); |
||||||
161 | DB::table('transactions')->whereIn('id', $ids)->update(['amount' => DB::raw('amount * -1')]); |
||||||
162 | ++$count; |
||||||
163 | // report about it |
||||||
164 | /** @var TransactionJournal $journal */ |
||||||
165 | $journal = TransactionJournal::find($journalId); |
||||||
166 | if (null === $journal) { |
||||||
167 | continue; |
||||||
168 | } |
||||||
169 | if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) { |
||||||
0 ignored issues
–
show
|
|||||||
170 | $this->error( |
||||||
171 | sprintf( |
||||||
172 | 'Transaction #%d was stored incorrectly. One of your asset accounts may show the wrong balance. Please visit /transactions/show/%d to verify the opening balance.', |
||||||
173 | $journalId, $journalId |
||||||
174 | ) |
||||||
175 | ); |
||||||
176 | } |
||||||
177 | if (TransactionType::OPENING_BALANCE !== $journal->transactionType->type) { |
||||||
178 | $this->error( |
||||||
179 | sprintf( |
||||||
180 | 'Transaction #%d was stored incorrectly. Could be that the transaction shows the wrong amount. Please visit /transactions/show/%d to verify the opening balance.', |
||||||
181 | $journalId, $journalId |
||||||
182 | ) |
||||||
183 | ); |
||||||
184 | } |
||||||
185 | } |
||||||
186 | if (0 === $count) { |
||||||
187 | $this->info('Amount integrity OK!'); |
||||||
188 | } |
||||||
189 | |||||||
190 | return; |
||||||
191 | } |
||||||
192 | |||||||
193 | /** |
||||||
194 | * Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events. |
||||||
195 | */ |
||||||
196 | private function repairPiggyBanks(): void |
||||||
197 | { |
||||||
198 | $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); |
||||||
199 | $set->each( |
||||||
200 | function (PiggyBankEvent $event) { |
||||||
201 | if (null === $event->transaction_journal_id) { |
||||||
0 ignored issues
–
show
|
|||||||
202 | return true; |
||||||
203 | } |
||||||
204 | /** @var TransactionJournal $journal */ |
||||||
205 | $journal = $event->transactionJournal()->first(); |
||||||
206 | if (null === $journal) { |
||||||
207 | return true; |
||||||
208 | } |
||||||
209 | |||||||
210 | $type = $journal->transactionType->type; |
||||||
0 ignored issues
–
show
|
|||||||
211 | if (TransactionType::TRANSFER !== $type) { |
||||||
212 | $event->transaction_journal_id = null; |
||||||
213 | $event->save(); |
||||||
214 | $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); |
||||||
0 ignored issues
–
show
|
|||||||
215 | } |
||||||
216 | |||||||
217 | return true; |
||||||
218 | } |
||||||
219 | ); |
||||||
220 | |||||||
221 | return; |
||||||
222 | } |
||||||
223 | |||||||
224 | /** |
||||||
225 | * Reports on accounts with no transactions. |
||||||
226 | */ |
||||||
227 | private function reportAccounts() |
||||||
228 | { |
||||||
229 | $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') |
||||||
230 | ->leftJoin('users', 'accounts.user_id', '=', 'users.id') |
||||||
231 | ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']) |
||||||
232 | ->whereNull('transactions.account_id') |
||||||
233 | ->get( |
||||||
234 | ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'] |
||||||
235 | ); |
||||||
236 | |||||||
237 | /** @var stdClass $entry */ |
||||||
238 | foreach ($set as $entry) { |
||||||
239 | $name = $entry->name; |
||||||
240 | $line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; |
||||||
241 | $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name); |
||||||
242 | $this->line($line); |
||||||
243 | } |
||||||
244 | } |
||||||
245 | |||||||
246 | /** |
||||||
247 | * Reports on budgets with no budget limits (which makes them pointless). |
||||||
248 | */ |
||||||
249 | private function reportBudgetLimits() |
||||||
250 | { |
||||||
251 | $set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') |
||||||
252 | ->leftJoin('users', 'budgets.user_id', '=', 'users.id') |
||||||
253 | ->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email']) |
||||||
254 | ->whereNull('budget_limits.id') |
||||||
255 | ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']); |
||||||
256 | |||||||
257 | /** @var Budget $entry */ |
||||||
258 | foreach ($set as $entry) { |
||||||
259 | $line = sprintf( |
||||||
260 | 'User #%d (%s) has budget #%d ("%s") which has no budget limits.', |
||||||
261 | $entry->user_id, |
||||||
262 | $entry->email, |
||||||
263 | $entry->id, |
||||||
264 | $entry->name |
||||||
265 | ); |
||||||
266 | $this->line($line); |
||||||
267 | } |
||||||
268 | } |
||||||
269 | |||||||
270 | /** |
||||||
271 | * Reports on deleted accounts that still have not deleted transactions or journals attached to them. |
||||||
272 | */ |
||||||
273 | private function reportDeletedAccounts() |
||||||
274 | { |
||||||
275 | $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') |
||||||
276 | ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') |
||||||
277 | ->whereNotNull('accounts.deleted_at') |
||||||
278 | ->whereNotNull('transactions.id') |
||||||
279 | ->where( |
||||||
280 | function (Builder $q) { |
||||||
281 | $q->whereNull('transactions.deleted_at'); |
||||||
282 | $q->orWhereNull('transaction_journals.deleted_at'); |
||||||
283 | } |
||||||
284 | ) |
||||||
285 | ->get( |
||||||
286 | ['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id', |
||||||
287 | 'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id', |
||||||
288 | 'transaction_journals.deleted_at as journal_deleted_at',] |
||||||
289 | ); |
||||||
290 | /** @var stdClass $entry */ |
||||||
291 | foreach ($set as $entry) { |
||||||
292 | $date = $entry->transaction_deleted_at ?? $entry->journal_deleted_at; |
||||||
293 | $this->error( |
||||||
294 | 'Error: Account #' . $entry->account_id . ' should have been deleted, but has not.' . |
||||||
295 | ' Find it in the table called "accounts" and change the "deleted_at" field to: "' . $date . '"' |
||||||
296 | ); |
||||||
297 | } |
||||||
298 | } |
||||||
299 | |||||||
300 | /** |
||||||
301 | * Report on journals with bad account types linked to them. |
||||||
302 | */ |
||||||
303 | private function reportIncorrectJournals() |
||||||
304 | { |
||||||
305 | $configuration = [ |
||||||
306 | // a withdrawal can not have revenue account: |
||||||
307 | TransactionType::WITHDRAWAL => [AccountType::REVENUE], |
||||||
308 | // deposit cannot have an expense account: |
||||||
309 | TransactionType::DEPOSIT => [AccountType::EXPENSE], |
||||||
310 | // transfer cannot have either: |
||||||
311 | TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE], |
||||||
312 | ]; |
||||||
313 | foreach ($configuration as $transactionType => $accountTypes) { |
||||||
314 | $set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') |
||||||
315 | ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') |
||||||
316 | ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') |
||||||
317 | ->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id') |
||||||
318 | ->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id') |
||||||
319 | ->where('transaction_types.type', $transactionType) |
||||||
320 | ->whereIn('account_types.type', $accountTypes) |
||||||
321 | ->whereNull('transaction_journals.deleted_at') |
||||||
322 | ->get( |
||||||
323 | ['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', |
||||||
324 | 'transaction_types.type',] |
||||||
325 | ); |
||||||
326 | foreach ($set as $entry) { |
||||||
327 | $this->error( |
||||||
328 | sprintf( |
||||||
329 | 'Transaction journal #%d (user #%d, %s) is of type "%s" but ' . |
||||||
330 | 'is linked to a "%s". The transaction journal should be recreated.', |
||||||
331 | $entry->id, |
||||||
332 | $entry->user_id, |
||||||
333 | $entry->email, |
||||||
334 | $entry->type, |
||||||
335 | $entry->a_type |
||||||
336 | ) |
||||||
337 | ); |
||||||
338 | } |
||||||
339 | } |
||||||
340 | } |
||||||
341 | |||||||
342 | /** |
||||||
343 | * Any deleted transaction journals that have transactions that are NOT deleted:. |
||||||
344 | */ |
||||||
345 | private function reportJournals() |
||||||
346 | { |
||||||
347 | $count = 0; |
||||||
348 | $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') |
||||||
349 | ->whereNotNull('transaction_journals.deleted_at')// USE THIS |
||||||
350 | ->whereNull('transactions.deleted_at') |
||||||
351 | ->whereNotNull('transactions.id') |
||||||
352 | ->get( |
||||||
353 | [ |
||||||
354 | 'transaction_journals.id as journal_id', |
||||||
355 | 'transaction_journals.description', |
||||||
356 | 'transaction_journals.deleted_at as journal_deleted', |
||||||
357 | 'transactions.id as transaction_id', |
||||||
358 | 'transactions.deleted_at as transaction_deleted_at',] |
||||||
359 | ); |
||||||
360 | /** @var stdClass $entry */ |
||||||
361 | foreach ($set as $entry) { |
||||||
362 | $this->error( |
||||||
363 | 'Error: Transaction #' . $entry->transaction_id . ' should have been deleted, but has not.' . |
||||||
364 | ' Find it in the table called "transactions" and change the "deleted_at" field to: "' . $entry->journal_deleted . '"' |
||||||
365 | ); |
||||||
366 | ++$count; |
||||||
367 | } |
||||||
368 | if (0 === $count) { |
||||||
369 | $this->info('No orphaned transactions!'); |
||||||
370 | } |
||||||
371 | } |
||||||
372 | |||||||
373 | /** |
||||||
374 | * Report on journals without transactions. |
||||||
375 | */ |
||||||
376 | private function reportNoTransactions() |
||||||
377 | { |
||||||
378 | $count = 0; |
||||||
379 | $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') |
||||||
380 | ->groupBy('transaction_journals.id') |
||||||
0 ignored issues
–
show
'transaction_journals.id' of type string is incompatible with the type array expected by parameter $groups of Illuminate\Database\Query\Builder::groupBy() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
381 | ->whereNull('transactions.transaction_journal_id') |
||||||
382 | ->get(['transaction_journals.id']); |
||||||
383 | |||||||
384 | foreach ($set as $entry) { |
||||||
385 | $this->error( |
||||||
386 | 'Error: Journal #' . $entry->id . ' has zero transactions. Open table "transaction_journals" and delete the entry with id #' . $entry->id |
||||||
387 | ); |
||||||
388 | ++$count; |
||||||
389 | } |
||||||
390 | if (0 === $count) { |
||||||
391 | $this->info('No orphaned journals!'); |
||||||
392 | } |
||||||
393 | } |
||||||
394 | |||||||
395 | /** |
||||||
396 | * Report on things with no linked journals. |
||||||
397 | * |
||||||
398 | * @param string $name |
||||||
399 | */ |
||||||
400 | private function reportObject(string $name) |
||||||
401 | { |
||||||
402 | $plural = str_plural($name); |
||||||
403 | $class = sprintf('FireflyIII\Models\%s', ucfirst($name)); |
||||||
404 | $field = 'tag' === $name ? 'tag' : 'name'; |
||||||
405 | $set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id') |
||||||
406 | ->leftJoin('users', $plural . '.user_id', '=', 'users.id') |
||||||
407 | ->distinct() |
||||||
408 | ->whereNull($name . '_transaction_journal.' . $name . '_id') |
||||||
409 | ->whereNull($plural . '.deleted_at') |
||||||
410 | ->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']); |
||||||
411 | |||||||
412 | /** @var stdClass $entry */ |
||||||
413 | foreach ($set as $entry) { |
||||||
414 | $objName = $entry->name; |
||||||
415 | try { |
||||||
416 | $objName = Crypt::decrypt($objName); |
||||||
417 | } catch (DecryptException $e) { |
||||||
418 | // it probably was not encrypted. |
||||||
419 | } |
||||||
420 | |||||||
421 | $line = sprintf( |
||||||
422 | 'User #%d (%s) has %s #%d ("%s") which has no transactions.', |
||||||
423 | $entry->user_id, |
||||||
424 | $entry->email, |
||||||
425 | $name, |
||||||
426 | $entry->id, |
||||||
427 | $objName |
||||||
428 | ); |
||||||
429 | $this->line($line); |
||||||
430 | } |
||||||
431 | } |
||||||
432 | |||||||
433 | /** |
||||||
434 | * Reports for each user when the sum of their transactions is not zero. |
||||||
435 | */ |
||||||
436 | private function reportSum() |
||||||
437 | { |
||||||
438 | /** @var UserRepositoryInterface $userRepository */ |
||||||
439 | $userRepository = app(UserRepositoryInterface::class); |
||||||
440 | |||||||
441 | /** @var User $user */ |
||||||
442 | foreach ($userRepository->all() as $user) { |
||||||
443 | $sum = (string)$user->transactions()->sum('amount'); |
||||||
444 | if (0 !== bccomp($sum, '0')) { |
||||||
445 | $this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!'); |
||||||
446 | } else { |
||||||
447 | $this->info(sprintf('Amount integrity OK for user #%d', $user->id)); |
||||||
448 | } |
||||||
449 | } |
||||||
450 | } |
||||||
451 | |||||||
452 | /** |
||||||
453 | * Reports on deleted transactions that are connected to a not deleted journal. |
||||||
454 | */ |
||||||
455 | private function reportTransactions() |
||||||
456 | { |
||||||
457 | $set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') |
||||||
458 | ->whereNotNull('transactions.deleted_at') |
||||||
459 | ->whereNull('transaction_journals.deleted_at') |
||||||
460 | ->get( |
||||||
461 | ['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id', |
||||||
462 | 'transaction_journals.deleted_at',] |
||||||
463 | ); |
||||||
464 | /** @var stdClass $entry */ |
||||||
465 | foreach ($set as $entry) { |
||||||
466 | $this->error( |
||||||
467 | 'Error: Transaction journal #' . $entry->journal_id . ' should have been deleted, but has not.' . |
||||||
468 | ' Find it in the table called "transaction_journals" and change the "deleted_at" field to: "' . $entry->transaction_deleted . '"' |
||||||
469 | ); |
||||||
470 | } |
||||||
471 | } |
||||||
472 | |||||||
473 | /** |
||||||
474 | * Report on transfers that have budgets. |
||||||
475 | */ |
||||||
476 | private function reportTransfersBudgets() |
||||||
477 | { |
||||||
478 | $set = TransactionJournal::distinct() |
||||||
479 | ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') |
||||||
480 | ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') |
||||||
481 | ->whereIn('transaction_types.type', [TransactionType::TRANSFER, TransactionType::DEPOSIT]) |
||||||
482 | ->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.*']); |
||||||
483 | |||||||
484 | /** @var TransactionJournal $entry */ |
||||||
485 | foreach ($set as $entry) { |
||||||
486 | $this->error( |
||||||
487 | sprintf( |
||||||
488 | 'Error: Transaction journal #%d is a %s, but has a budget. Edit it without changing anything, so the budget will be removed.', |
||||||
489 | $entry->id, |
||||||
490 | $entry->transactionType->type |
||||||
491 | ) |
||||||
492 | ); |
||||||
493 | } |
||||||
494 | } |
||||||
495 | } |
||||||
496 |