From ce30375341dbb6047a7678eb296740e63d6ca1ab Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Mar 2019 18:58:06 +0100 Subject: [PATCH] Refactor upgrade and verify commands. --- .../Correction/CreateAccessTokens.php | 3 + .../Commands/Correction/CreateLinkTypes.php | 4 +- .../Commands/Correction/DeleteEmptyGroups.php | 8 +- .../Correction/DeleteEmptyJournals.php | 12 +- .../Correction/DeleteOrphanedTransactions.php | 9 +- .../Commands/Correction/DeleteZeroAmount.php | 7 +- .../Commands/Correction/EnableCurrencies.php | 4 + .../Commands/Correction/FixAccountTypes.php | 157 ++++++- .../Commands/Correction/FixPiggies.php | 6 +- .../Commands/Correction/FixUnevenAmount.php | 7 +- .../Commands/Correction/RemoveBills.php | 7 +- .../Commands/Correction/TransferBudgets.php | 5 +- .../Commands/Integrity/ReportEmptyObjects.php | 66 +-- .../Commands/Integrity/ReportIntegrity.php | 36 -- app/Console/Commands/Integrity/ReportSum.php | 7 +- .../Commands/Upgrade/AccountCurrencies.php | 157 ++++--- .../Commands/Upgrade/BackToJournals.php | 106 ++++- .../Commands/Upgrade/BudgetLimitCurrency.php | 9 +- .../Commands/Upgrade/CCLiabilities.php | 6 + .../Commands/Upgrade/JournalCurrencies.php | 397 +++++++++++------- .../Commands/Upgrade/MigrateAttachments.php | 4 +- app/Console/Commands/Upgrade/MigrateNotes.php | 4 +- .../Commands/Upgrade/MigrateToGroups.php | 160 ++++--- .../Commands/Upgrade/MigrateToRules.php | 13 +- .../Upgrade/TransactionIdentifier.php | 4 +- .../Commands/Upgrade/UpgradeDatabase.php | 2 + .../Journal/JournalRepository.php | 25 +- .../Journal/JournalRepositoryInterface.php | 4 +- composer.json | 29 +- composer.lock | 160 +++---- readme.md | 4 +- 31 files changed, 909 insertions(+), 513 deletions(-) diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php index 44d92f39fb..f14632553f 100644 --- a/app/Console/Commands/Correction/CreateAccessTokens.php +++ b/app/Console/Commands/Correction/CreateAccessTokens.php @@ -51,6 +51,7 @@ class CreateAccessTokens extends Command */ public function handle(): int { + $start = microtime(true); $count = 0; $users = User::get(); /** @var User $user */ @@ -66,6 +67,8 @@ class CreateAccessTokens extends Command if (0 === $count) { $this->info('All access tokens OK!'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verify access tokens in %s seconds.', $end)); return 0; } diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php index 83799d7ef7..6cc158af3e 100644 --- a/app/Console/Commands/Correction/CreateLinkTypes.php +++ b/app/Console/Commands/Correction/CreateLinkTypes.php @@ -49,7 +49,7 @@ class CreateLinkTypes extends Command */ public function handle(): int { - // + $start = microtime(true); $count = 0; $set = [ 'Related' => ['relates to', 'relates to'], @@ -73,6 +73,8 @@ class CreateLinkTypes extends Command if (0 === $count) { $this->info('All link types OK!'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified link types in %s seconds', $end)); return 0; } diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php index c828bc2719..98887142e9 100644 --- a/app/Console/Commands/Correction/DeleteEmptyGroups.php +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -52,16 +52,18 @@ class DeleteEmptyGroups extends Command */ public function handle(): int { - // + $start = microtime(true); $groups = array_unique(TransactionJournal::get(['transaction_group_id'])->pluck('transaction_group_id')->toArray()); $count = TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->count(); if (0 === $count) { - $this->info('No empty groups.'); + $this->info('No empty transaction groups.'); } if ($count > 0) { - $this->info(sprintf('Deleted %d empty groups.', $count)); + $this->info(sprintf('Deleted %d empty transaction groups.', $count)); TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->delete(); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified empty groups in %s seconds', $end)); return 0; } diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php index ac60ae6775..10dd7c17e1 100644 --- a/app/Console/Commands/Correction/DeleteEmptyJournals.php +++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php @@ -60,7 +60,7 @@ class DeleteEmptyJournals extends Command private function deleteEmptyJournals(): void { - + $start = microtime(true); $count = 0; $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->groupBy('transaction_journals.id') @@ -69,12 +69,14 @@ class DeleteEmptyJournals extends Command foreach ($set as $entry) { TransactionJournal::find($entry->id)->delete(); - $this->info(sprintf('Deleted empty transaction #%d', $entry->id)); + $this->info(sprintf('Deleted empty transaction journal #%d', $entry->id)); ++$count; } if (0 === $count) { - $this->info('No empty transactions.'); + $this->info('No empty transaction journals.'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified empty journals in %s seconds', $end)); } /** @@ -103,12 +105,12 @@ class DeleteEmptyJournals extends Command // uneven number, delete journal and transactions: TransactionJournal::find((int)$row->transaction_journal_id)->delete(); Transaction::where('transaction_journal_id', (int)$row->transaction_journal_id)->delete(); - $this->info(sprintf('Deleted transaction #%d because it had an uneven number of transactions.', $row->transaction_journal_id)); + $this->info(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $row->transaction_journal_id)); $total++; } } if (0 === $total) { - $this->info('No uneven transactions.'); + $this->info('No uneven transaction journals.'); } } diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php index 52d02379b9..072d2f9a79 100644 --- a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php +++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php @@ -53,9 +53,11 @@ class DeleteOrphanedTransactions extends Command */ public function handle(): int { + $start = microtime(true); $this->deleteOrphanedTransactions(); $this->deleteFromOrphanedAccounts(); - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified orphans in %s seconds', $end)); return 0; } @@ -80,7 +82,7 @@ class DeleteOrphanedTransactions extends Command } Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete(); $this->line( - sprintf('Deleted transaction #%d because account #%d was already deleted.', $transaction->transaction_journal_id, $transaction->account_id) + sprintf('Deleted transaction journal #%d because account #%d was already deleted.', $transaction->transaction_journal_id, $transaction->account_id) ); $count++; } @@ -112,7 +114,7 @@ class DeleteOrphanedTransactions extends Command $transaction->delete(); $this->info( sprintf( - 'Transaction #%d (part of deleted journal #%d) has been deleted as well.', + 'Transaction #%d (part of deleted transaction journal #%d) has been deleted as well.', $entry->transaction_id, $entry->journal_id ) @@ -122,5 +124,6 @@ class DeleteOrphanedTransactions extends Command if (0 === $count) { $this->info('No orphaned transactions.'); } + } } diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php index a3e33554dc..6c062f74de 100644 --- a/app/Console/Commands/Correction/DeleteZeroAmount.php +++ b/app/Console/Commands/Correction/DeleteZeroAmount.php @@ -52,20 +52,23 @@ class DeleteZeroAmount extends Command */ public function handle(): int { + $start = microtime(true); $set = Transaction::where('amount', 0)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); $set = array_unique($set); /** @var Collection $journals */ $journals = TransactionJournal::whereIn('id', $set)->get(); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $this->info(sprintf('Deleted transaction #%d because the amount is zero (0.00).', $journal->id)); + $this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id)); $journal->delete(); Transaction::where('transaction_journal_id', $journal->id)->delete(); } if (0 === $journals->count()) { - $this->info('No zero-amount transactions.'); + $this->info('No zero-amount transaction journals.'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified zero-amount integrity in %s seconds', $end)); return 0; } } diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php index f338b949b2..e4f7bfd395 100644 --- a/app/Console/Commands/Correction/EnableCurrencies.php +++ b/app/Console/Commands/Correction/EnableCurrencies.php @@ -54,6 +54,7 @@ class EnableCurrencies extends Command */ public function handle(): int { + $start = microtime(true); $found = []; // get all meta entries /** @var Collection $meta */ @@ -91,6 +92,9 @@ class EnableCurrencies extends Command } TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified currencies in %s seconds.', $end)); + return 0; } } diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php index 958bbf3c1d..d8952811f7 100644 --- a/app/Console/Commands/Correction/FixAccountTypes.php +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -21,8 +21,11 @@ namespace FireflyIII\Console\Commands\Correction; -use FireflyIII\Models\Account; +use FireflyIII\Factory\AccountFactory; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; use Illuminate\Console\Command; /** @@ -44,70 +47,180 @@ class FixAccountTypes extends Command protected $signature = 'firefly-iii:fix-account-types'; /** @var array */ private $expected; + /** @var AccountFactory */ + private $factory; + /** @var array */ + private $fixable; + + /** + * @param TransactionJournal $journal + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void + { + // variables: + $combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type); + + switch ($combination) { + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE): + // from an asset to a liability should be a withdrawal: + $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first(); + $journal->transactionType()->associate($withdrawal); + $journal->save(); + $this->info(sprintf('Converted transaction #%d from a transfer to a withdrawal', $journal->id)); + // check it again: + $this->inspectJournal($journal); + break; + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET): + // from a liability to an asset should be a deposit. + $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first(); + $journal->transactionType()->associate($deposit); + $journal->save(); + $this->info(sprintf('Converted transaction #%d from a transfer to a deposit', $journal->id)); + // check it again: + $this->inspectJournal($journal); + + break; + case sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE): + // withdrawals with a revenue account as destination instead of an expense account. + $this->factory->setUser($journal->user); + $result = $this->factory->findOrCreate($source->account->name, AccountType::EXPENSE); + $dest->account()->associate($result); + $dest->save(); + $this->info( + sprintf( + 'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id, + $source->account->id, $source->account->name, + $result->id, $result->name + ) + ); + $this->inspectJournal($journal); + break; + case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET): + // deposits with an expense account as source instead of a revenue account. + // find revenue account. + $this->factory->setUser($journal->user); + $result = $this->factory->findOrCreate($source->account->name, AccountType::REVENUE); + $source->account()->associate($result); + $source->save(); + $this->info( + sprintf( + 'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id, + $source->account->id, $source->account->name, + $result->id, $result->name + ) + ); + $this->inspectJournal($journal); + break; + break; + default: + $this->info(sprintf('The source account of %s #%d cannot be of type "%s".', $type, $journal->id, $source->account->accountType->type)); + $this->info(sprintf('The destination account of %s #%d cannot be of type "%s".', $type, $journal->id, $dest->account->accountType->type)); + break; + + } + + } /** * Execute the console command. * * @return int + * @throws \FireflyIII\Exceptions\FireflyException */ public function handle(): int { + $start = microtime(true); + $this->factory = app(AccountFactory::class); + // some combinations can be fixed by this script: + $this->fixable = [ + // transfers from asset to liability and vice versa + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET), + + // withdrawals with a revenue account as destination instead of an expense account. + sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE), + + // deposits with an expense account as source instead of a revenue account. + sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET), + ]; + + $this->expected = config('firefly.source_dests'); - $journals = TransactionJournal::get(); + $journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get(); foreach ($journals as $journal) { $this->inspectJournal($journal); } - return 0; - } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified account types in %s seconds', $end)); - private function getDestinationAccount(TransactionJournal $journal): Account - { - return $journal->transactions()->where('amount', '>', 0)->first()->account; + return 0; } /** * @param TransactionJournal $journal * - * @return Account + * @return Transaction */ - private function getSourceAccount(TransactionJournal $journal): Account + private function getDestinationTransaction(TransactionJournal $journal): Transaction { - return $journal->transactions()->where('amount', '<', 0)->first()->account; + return $journal->transactions->firstWhere('amount', '>', 0); } /** * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + return $journal->transactions->firstWhere('amount', '<', 0); + } + + /** + * @param TransactionJournal $journal + * + * @throws \FireflyIII\Exceptions\FireflyException */ private function inspectJournal(TransactionJournal $journal): void { $count = $journal->transactions()->count(); if (2 !== $count) { - $this->info(sprintf('Cannot inspect journal #%d because it does not have 2 transactions, but %d', $journal->id, $count)); + $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transactions instead of 2.', $journal->id, $count)); return; } - $type = $journal->transactionType->type; - $sourceAccount = $this->getSourceAccount($journal); - $sourceAccountType = $sourceAccount->accountType->type; - $destinationAccount = $this->getDestinationAccount($journal); - $destinationAccountType = $destinationAccount->accountType->type; + $type = $journal->transactionType->type; + $sourceTransaction = $this->getSourceTransaction($journal); + $sourceAccount = $sourceTransaction->account; + $sourceAccountType = $sourceAccount->accountType->type; + $destTransaction = $this->getDestinationTransaction($journal); + $destAccount = $destTransaction->account; + $destAccountType = $destAccount->accountType->type; if (!isset($this->expected[$type])) { $this->info(sprintf('No source/destination info for transaction type %s.', $type)); return; } if (!isset($this->expected[$type][$sourceAccountType])) { - $this->info(sprintf('The source of %s #%d cannot be of type "%s".', $type, $journal->id, $sourceAccountType)); - $this->info(sprintf('The destination of %s #%d probably cannot be of type "%s".', $type, $journal->id, $destinationAccountType)); + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); - // TODO think of a way to fix the problem. return; } $expectedTypes = $this->expected[$type][$sourceAccountType]; - if (!\in_array($destinationAccountType, $expectedTypes, true)) { - $this->info(sprintf('The destination of %s #%d cannot be of type "%s".', $type, $journal->id, $destinationAccountType)); - // TODO think of a way to fix the problem. + if (!\in_array($destAccountType, $expectedTypes, true)) { + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); } } + } diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php index f07d6f77af..c7701747b5 100644 --- a/app/Console/Commands/Correction/FixPiggies.php +++ b/app/Console/Commands/Correction/FixPiggies.php @@ -53,7 +53,8 @@ class FixPiggies extends Command */ public function handle(): int { - $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); + $start = microtime(true); + $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); $set->each( function (PiggyBankEvent $event) { if (null === $event->transaction_journal_id) { @@ -75,7 +76,8 @@ class FixPiggies extends Command return true; } ); - $this->line(sprintf('Verified the content of %d piggy bank events.', $set->count())); + $end = round(microtime(true) - $start, 2); + $this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end)); return 0; } diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php index 16390769f6..5a915cc457 100644 --- a/app/Console/Commands/Correction/FixUnevenAmount.php +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -52,7 +52,7 @@ class FixUnevenAmount extends Command */ public function handle(): int { - + $start = microtime(true); $count = 0; // get invalid journals $journals = DB::table('transactions') @@ -70,6 +70,9 @@ class FixUnevenAmount extends Command $this->info('Amount integrity OK!'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified amount integrity in %s seconds', $end)); + return 0; } @@ -93,7 +96,7 @@ class FixUnevenAmount extends Command $destination->amount = $amount; $destination->save(); - $this->line(sprintf('Corrected amount in transaction #%d', $param)); + $this->line(sprintf('Corrected amount in transaction journal #%d', $param)); } } diff --git a/app/Console/Commands/Correction/RemoveBills.php b/app/Console/Commands/Correction/RemoveBills.php index 9df66bcbc9..2dfb2f8588 100644 --- a/app/Console/Commands/Correction/RemoveBills.php +++ b/app/Console/Commands/Correction/RemoveBills.php @@ -50,6 +50,7 @@ class RemoveBills extends Command */ public function handle(): int { + $start = microtime(true); /** @var TransactionType $withdrawal */ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); $journals = TransactionJournal::whereNotNull('bill_id')->where('transaction_type_id', '!=', $withdrawal->id)->get(); @@ -60,11 +61,13 @@ class RemoveBills extends Command $journal->save(); } if (0 === $journals->count()) { - $this->info('All transactions have correct bill information.'); + $this->info('All transaction journals have correct bill information.'); } if ($journals->count() > 0) { - $this->info('Fixed all transactions so they have correct bill information.'); + $this->info('Fixed all transaction journals so they have correct bill information.'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified bills / journals in %s seconds', $end)); return 0; } diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php index 4eea03ddf0..9b44827024 100644 --- a/app/Console/Commands/Correction/TransferBudgets.php +++ b/app/Console/Commands/Correction/TransferBudgets.php @@ -50,6 +50,7 @@ class TransferBudgets extends Command */ public function handle(): int { + $start = microtime(true); $set = TransactionJournal::distinct() ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') @@ -58,13 +59,15 @@ class TransferBudgets extends Command $count = 0; /** @var TransactionJournal $entry */ foreach ($set as $entry) { - $this->info(sprintf('Transaction #%d is a %s, so has no longer a budget.', $entry->id, $entry->transactionType->type)); + $this->info(sprintf('Transaction journal #%d is a %s, so has no longer a budget.', $entry->id, $entry->transactionType->type)); $entry->budgets()->sync([]); $count++; } if (0 === $count) { $this->info('No invalid budget/journal entries.'); } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified budget/journals in %s seconds.', $end)); return 0; } diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php index 1c24e3783f..ec5dc922ec 100644 --- a/app/Console/Commands/Integrity/ReportEmptyObjects.php +++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php @@ -21,10 +21,10 @@ namespace FireflyIII\Console\Commands\Integrity; +use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; -use FireflyIII\Models\Account; use Illuminate\Console\Command; use stdClass; @@ -53,15 +53,39 @@ class ReportEmptyObjects extends Command */ public function handle(): int { + $start = microtime(true); $this->reportEmptyBudgets(); $this->reportEmptyCategories(); $this->reportEmptyTags(); $this->reportAccounts(); $this->reportBudgetLimits(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Report on empty objects finished in %s seconds', $end)); + return 0; } + /** + * Reports on accounts with no transactions. + */ + private function reportAccounts(): void + { + $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('users', 'accounts.user_id', '=', 'users.id') + ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']) + ->whereNull('transactions.account_id') + ->get( + ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'] + ); + /** @var stdClass $entry */ + foreach ($set as $entry) { + $name = $entry->name; + $line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; + $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name); + $this->line($line); + } + } /** * Reports on budgets with no budget limits (which makes them pointless). @@ -87,28 +111,6 @@ class ReportEmptyObjects extends Command } } - /** - * Reports on accounts with no transactions. - */ - private function reportAccounts(): void - { - $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('users', 'accounts.user_id', '=', 'users.id') - ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']) - ->whereNull('transactions.account_id') - ->get( - ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'] - ); - - /** @var stdClass $entry */ - foreach ($set as $entry) { - $name = $entry->name; - $line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; - $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name); - $this->line($line); - } - } - /** * Report on budgets with no transactions or journals. */ @@ -125,7 +127,7 @@ class ReportEmptyObjects extends Command foreach ($set as $entry) { $objName = $entry->name; $line = sprintf( - 'User #%d (%s) has budget #%d ("%s") which has no transactions.', + 'User #%d (%s) has budget #%d ("%s") which has no transaction journals.', $entry->user_id, $entry->email, $entry->id, @@ -152,7 +154,7 @@ class ReportEmptyObjects extends Command $objName = $entry->name; $line = sprintf( - 'User #%d (%s) has category #%d ("%s") which has no transactions.', + 'User #%d (%s) has category #%d ("%s") which has no transaction journals.', $entry->user_id, $entry->email, $entry->id, @@ -167,19 +169,19 @@ class ReportEmptyObjects extends Command */ private function reportEmptyTags(): void { - $set = Tag::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id') - ->leftJoin('users', 'tags.user_id', '=', 'users.id') - ->distinct() - ->whereNull('tag_transaction_journal.tag_id') - ->whereNull('tags.deleted_at') - ->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']); + $set = Tag::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id') + ->leftJoin('users', 'tags.user_id', '=', 'users.id') + ->distinct() + ->whereNull('tag_transaction_journal.tag_id') + ->whereNull('tags.deleted_at') + ->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']); /** @var stdClass $entry */ foreach ($set as $entry) { $objName = $entry->tag; $line = sprintf( - 'User #%d (%s) has tag #%d ("%s") which has no transactions.', + 'User #%d (%s) has tag #%d ("%s") which has no transaction journals.', $entry->user_id, $entry->email, $entry->id, diff --git a/app/Console/Commands/Integrity/ReportIntegrity.php b/app/Console/Commands/Integrity/ReportIntegrity.php index 7ffac64eb3..83ca43a7c7 100644 --- a/app/Console/Commands/Integrity/ReportIntegrity.php +++ b/app/Console/Commands/Integrity/ReportIntegrity.php @@ -59,21 +59,6 @@ class ReportIntegrity extends Command $commands = [ 'firefly-iii:report-empty-objects', 'firefly-iii:report-sum', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', -// 'firefly-iii:', ]; foreach ($commands as $command) { $this->line(sprintf('Now executing %s', $command)); @@ -82,27 +67,6 @@ class ReportIntegrity extends Command echo $result; } -// $this->reportEmptyBudgets(); -// $this->reportEmptyCategories(); -// $this->reportObject('tag'); -// $this->reportAccounts(); -// $this->reportBudgetLimits(); -// $this->reportSum(); -// $this->reportJournals(); -// $this->reportTransactions(); -// $this->reportDeletedAccounts(); -// $this->reportNoTransactions(); -// $this->reportTransfersBudgets(); -// $this->reportIncorrectJournals(); -// $this->repairPiggyBanks(); -// $this->createLinkTypes(); -// $this->createAccessTokens(); -// $this->fixDoubleAmounts(); // is a report function! -// $this->fixBadMeta(); -// $this->removeBills(); -// $this->enableCurrencies(); -// $this->reportZeroAmount(); - return 0; } } \ No newline at end of file diff --git a/app/Console/Commands/Integrity/ReportSum.php b/app/Console/Commands/Integrity/ReportSum.php index bba9c46550..e4b3c2cb43 100644 --- a/app/Console/Commands/Integrity/ReportSum.php +++ b/app/Console/Commands/Integrity/ReportSum.php @@ -61,6 +61,7 @@ class ReportSum extends Command */ private function reportSum(): void { + $start = microtime(true); /** @var UserRepositoryInterface $userRepository */ $userRepository = app(UserRepositoryInterface::class); @@ -68,11 +69,15 @@ class ReportSum extends Command foreach ($userRepository->all() as $user) { $sum = (string)$user->transactions()->sum('amount'); if (0 !== bccomp($sum, '0')) { - $this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!'); + $message = sprintf('Error: Transactions for user #%d (%s) are off by %s!', $user->id, $user->email, $sum); + $this->error($message); } if (0 === bccomp($sum, '0')) { $this->info(sprintf('Amount integrity OK for user #%d', $user->id)); } } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Report on total sum finished in %s seconds', $end)); + } } diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php index d56d8e8702..4843162430 100644 --- a/app/Console/Commands/Upgrade/AccountCurrencies.php +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -26,9 +26,9 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\User; use Illuminate\Console\Command; use Log; -use UnexpectedValueException; /** * Class AccountCurrencies @@ -49,6 +49,9 @@ class AccountCurrencies extends Command */ protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}'; + /** @var AccountRepositoryInterface */ + private $repository; + /** * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. * @@ -56,78 +59,21 @@ class AccountCurrencies extends Command */ public function handle(): int { + $this->repository = app(AccountRepositoryInterface::class); + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); return 0; } - Log::debug('Now in updateAccountCurrencies()'); + $this->updateAccountCurrencies(); - $defaultConfig = (string)config('firefly.default_currency', 'EUR'); - Log::debug(sprintf('System default currency is "%s"', $defaultConfig)); - - $accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $accounts->each( - function (Account $account) use ($repository, $defaultConfig) { - $repository->setUser($account->user); - // get users preference, fall back to system pref. - - // expand and debug routine. - $defaultCurrencyCode = app('preferences')->getForUser($account->user, 'currencyPreference', $defaultConfig)->data; - Log::debug(sprintf('Default currency code is "%s"', var_export($defaultCurrencyCode, true))); - if (!is_string($defaultCurrencyCode)) { - $defaultCurrencyCode = $defaultConfig; - Log::debug(sprintf('Default currency code is not a string, now set to "%s"', $defaultCurrencyCode)); - } - $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); - $accountCurrency = (int)$repository->getMetaValue($account, 'currency_id'); - $openingBalance = $account->getOpeningBalance(); - $obCurrency = (int)$openingBalance->transaction_currency_id; - - if (null === $defaultCurrency) { - throw new UnexpectedValueException(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode)); - } - Log::debug( - sprintf('Found default currency #%d (%s) while searching for "%s"', $defaultCurrency->id, $defaultCurrency->code, $defaultCurrencyCode) - ); - - // both 0? set to default currency: - if (0 === $accountCurrency && 0 === $obCurrency) { - AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - - return true; - } - - // account is set to 0, opening balance is not? - if (0 === $accountCurrency && $obCurrency > 0) { - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - - return true; - } - - // do not match and opening balance id is not null. - if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) { - // update opening balance: - $openingBalance->transaction_currency_id = $accountCurrency; - $openingBalance->save(); - $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); - - return true; - } - - return true; - } - ); - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end)); $this->markAsExecuted(); + return 0; } @@ -152,4 +98,87 @@ class AccountCurrencies extends Command { app('fireflyconfig')->set(self::CONFIG_NAME, true); } + + /** + * @param Account $account + * @param TransactionCurrency $currency + */ + private function updateAccount(Account $account, TransactionCurrency $currency): void + { + $this->repository->setUser($account->user); + + $accountCurrency = (int)$this->repository->getMetaValue($account, 'currency_id'); + $openingBalance = $account->getOpeningBalance(); + $obCurrency = (int)$openingBalance->transaction_currency_id; + + + // both 0? set to default currency: + if (0 === $accountCurrency && 0 === $obCurrency) { + AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $currency->id]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code)); + + return; + } + + // account is set to 0, opening balance is not? + if (0 === $accountCurrency && $obCurrency > 0) { + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code)); + + return; + } + + // do not match and opening balance id is not null. + if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) { + // update opening balance: + $openingBalance->transaction_currency_id = $accountCurrency; + $openingBalance->save(); + $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); + } + } + + /** + * + */ + private function updateAccountCurrencies(): void + { + + $defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR'); + $users = User::get(); + foreach ($users as $user) { + $this->updateCurrenciesForUser($user, $defaultCurrencyCode); + } + } + + /** + * @param User $user + * @param string $systemCurrencyCode + */ + private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void + { + $accounts = $user->accounts() + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET]) + ->get(['accounts.*']); + + // get user's currency preference: + $defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data; + if (!is_string($defaultCurrencyCode)) { + $defaultCurrencyCode = $systemCurrencyCode; + } + /** @var TransactionCurrency $defaultCurrency */ + $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); + + if (null === $defaultCurrency) { + $this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode)); + + return; + } + + /** @var Account $account */ + foreach ($accounts as $account) { + $this->updateAccount($account, $defaultCurrency); + } + } } diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php index e13c0ef679..f61dc073b3 100644 --- a/app/Console/Commands/Upgrade/BackToJournals.php +++ b/app/Console/Commands/Upgrade/BackToJournals.php @@ -21,6 +21,7 @@ namespace FireflyIII\Console\Commands\Upgrade; +use DB; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Transaction; @@ -53,6 +54,7 @@ class BackToJournals extends Command */ public function handle(): int { + $start = microtime(true); if (!$this->isMigrated()) { $this->error('Please run firefly-iii:migrate-to-groups first.'); } @@ -66,13 +68,35 @@ class BackToJournals extends Command } $this->migrateAll(); - - $this->info('Updated category and budget info for all journals.'); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Updated category and budget info for all transaction journals in %s seconds.', $end)); $this->markAsExecuted(); return 0; } + /** + * @return array + */ + private function getIdsForBudgets(): array + { + $transactions = DB::table('budget_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray(); + + return DB::table('transactions')->whereIn('transactions.id', $transactions)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray( + ); + } + + /** + * @return array + */ + private function getIdsForCategories(): array + { + $transactions = DB::table('category_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray(); + + return DB::table('transactions')->whereIn('transactions.id', $transactions)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray( + ); + } + /** * @return bool */ @@ -112,10 +136,24 @@ class BackToJournals extends Command */ private function migrateAll(): void { - $journals = TransactionJournal::get(); + $this->migrateBudgets(); + $this->migrateCategories(); + + // empty tables + DB::table('budget_transaction')->delete(); + DB::table('categories_transaction')->delete(); + } + + /** + * + */ + private function migrateBudgets(): void + { + $journalIds = $this->getIdsForBudgets(); + $journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'budgets', 'transactions.budgets'])->get(); + $this->line(sprintf('Check %d transaction journals for budget info.', $journals->count())); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { - $this->migrateCategoriesForJournal($journal); $this->migrateBudgetsForJournal($journal); } } @@ -127,19 +165,38 @@ class BackToJournals extends Command { // grab category from first transaction /** @var Transaction $transaction */ - $transaction = $journal->transactions()->first(); + $transaction = $journal->transactions->first(); + if (null === $transaction) { + $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); + + return; + } /** @var Budget $budget */ - $budget = $transaction->budgets()->first(); - if (null !== $budget) { + $budget = $transaction->budgets->first(); + /** @var Budget $journalBudget */ + $journalBudget = $journal->budgets->first(); + if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) { // sync to journal: $journal->budgets()->sync([(int)$budget->id]); + } - // remove from transactions: - $journal->transactions()->each( - function (Transaction $transaction) { - $transaction->budgets()->sync([]); - } - ); + // budget in transaction overrules journal. + if (null === $budget && null !== $journalBudget) { + $journal->budgets()->sync([]); + } + } + + /** + * + */ + private function migrateCategories(): void + { + $journalIds = $this->getIdsForCategories(); + $journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'categories', 'transactions.categories'])->get(); + $this->line(sprintf('Check %d transaction journals for category info.', $journals->count())); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->migrateCategoriesForJournal($journal); } } @@ -150,19 +207,24 @@ class BackToJournals extends Command { // grab category from first transaction /** @var Transaction $transaction */ - $transaction = $journal->transactions()->first(); + $transaction = $journal->transactions->first(); + if (null === $transaction) { + $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); + + return; + } /** @var Category $category */ - $category = $transaction->categories()->first(); - if (null !== $category) { + $category = $transaction->categories->first(); + /** @var Category $journalCategory */ + $journalCategory = $journal->categories->first(); + if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) { // sync to journal: $journal->categories()->sync([(int)$category->id]); + } - // remove from transactions: - $journal->transactions()->each( - function (Transaction $transaction) { - $transaction->categories()->sync([]); - } - ); + // category in transaction overrules journal. + if (null === $category && null !== $journalCategory) { + $journal->categories()->sync([]); } } } diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php index db85f1cfb5..c3205fdea0 100644 --- a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php +++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php @@ -54,12 +54,13 @@ class BudgetLimitCurrency extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); return 0; } - + $count = 0; $budgetLimits = BudgetLimit::get(); /** @var BudgetLimit $budgetLimit */ foreach ($budgetLimits as $budgetLimit) { @@ -75,10 +76,16 @@ class BudgetLimitCurrency extends Command $this->line( sprintf('Budget limit #%d (part of budget "%s") now has a currency setting (%s).', $budgetLimit->id, $budget->name, $currency->name) ); + $count++; } } } } + if (0 === $count) { + $this->info('All budget limits are correct.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified budget limits in %s seconds.', $end)); $this->markAsExecuted(); diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php index 373bac1ce7..b6c29c266d 100644 --- a/app/Console/Commands/Upgrade/CCLiabilities.php +++ b/app/Console/Commands/Upgrade/CCLiabilities.php @@ -57,6 +57,7 @@ class CCLiabilities extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -77,6 +78,11 @@ class CCLiabilities extends Command if ($accounts->count() > 0) { $this->info('Credit card liability types are no longer supported and have been converted to generic debts. See: http://bit.ly/FF3-credit-cards'); } + if (0 === $accounts->count()) { + $this->info('No incorrectly stored credit card liabilities.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified credit card liabilities in %s seconds', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/JournalCurrencies.php b/app/Console/Commands/Upgrade/JournalCurrencies.php index aea1685cc3..eae5df4aad 100644 --- a/app/Console/Commands/Upgrade/JournalCurrencies.php +++ b/app/Console/Commands/Upgrade/JournalCurrencies.php @@ -26,13 +26,13 @@ namespace FireflyIII\Console\Commands\Upgrade; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Console\Command; -use Illuminate\Support\Collection; use Log; /** @@ -54,6 +54,14 @@ class JournalCurrencies extends Command * @var string */ protected $signature = 'firefly-iii:journal-currencies {--F|force : Force the execution of this command.}'; + /** @var array */ + private $accountCurrencies; + /** @var AccountRepositoryInterface */ + private $accountRepos; + /** @var CurrencyRepositoryInterface */ + private $currencyRepos; + /** @var JournalRepositoryInterface */ + private $journalRepos; /** * Execute the console command. @@ -62,6 +70,12 @@ class JournalCurrencies extends Command */ public function handle(): int { + $this->accountCurrencies = []; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->currencyRepos = app(CurrencyRepositoryInterface::class); + $this->journalRepos = app(JournalRepositoryInterface::class); + + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -69,106 +83,76 @@ class JournalCurrencies extends Command } $this->updateTransferCurrencies(); - $this->updateOtherCurrencies(); + $this->updateOtherJournalsCurrencies(); $this->markAsExecuted(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end)); return 0; } /** - * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for - * the accounts they are linked to. + * @param Account $account * - * Both source and destination must match the respective currency preference of the related asset account. - * So FF3 must verify all transactions. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return TransactionCurrency|null */ - public function updateOtherCurrencies(): void + private function getCurrency(Account $account): ?TransactionCurrency { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - $set = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->get(['transaction_journals.*']); + $accountId = $account->id; + if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { + return null; + } + if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + return $this->accountCurrencies[$accountId]; + } + $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); + $result = $this->currencyRepos->findNull($currencyId); + if (null === $result) { + $this->accountCurrencies[$accountId] = 0; - $set->each( - function (TransactionJournal $journal) use ($repository, $accountRepos) { - // get the transaction with the asset account in it: - /** @var Transaction $transaction */ - $transaction = $journal->transactions() - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']); - if (null === $transaction) { - return; - } - $accountRepos->setUser($journal->user); - /** @var Account $account */ - $account = $transaction->account; - $currency = $repository->findNull((int)$accountRepos->getMetaValue($account, 'currency_id')); - if (null === $currency) { - return; - } - $transactions = $journal->transactions()->get(); - $transactions->each( - function (Transaction $transaction) use ($currency) { - if (null === $transaction->transaction_currency_id) { - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } + return null; + } + $this->accountCurrencies[$accountId] = $result; + + return $result; - // when mismatch in transaction: - if (!((int)$transaction->transaction_currency_id === (int)$currency->id)) { - $transaction->foreign_currency_id = (int)$transaction->transaction_currency_id; - $transaction->foreign_amount = $transaction->amount; - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } - } - ); - // also update the journal, of course: - $journal->transaction_currency_id = $currency->id; - $journal->save(); - } - ); } /** - * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. - * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they - * like it or not. Previous routines MUST have set the currency setting for both accounts for this to work. + * @param TransactionJournal $transfer * - * A transfer always has the - * - * Both source and destination must match the respective currency preference. So FF3 must verify ALL - * transactions. + * @return Transaction|null */ - public function updateTransferCurrencies(): void + private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction { - $set = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_types.type', TransactionType::TRANSFER) - ->get(['transaction_journals.*']); + return $transfer->transactions->firstWhere('amount', '>', 0); + } - $set->each( - function (TransactionJournal $transfer) { - // select all "source" transactions: - /** @var Collection $transactions */ - $transactions = $transfer->transactions()->where('amount', '<', 0)->get(); - $transactions->each( - function (Transaction $transaction) { - $this->updateTransactionCurrency($transaction); - $this->updateJournalCurrency($transaction); - } - ); + /** + * @param TransactionJournal $journal + * + * @return Transaction|null + */ + private function getFirstAssetTransaction(TransactionJournal $journal): ?Transaction + { + $result = $journal->transactions->first( + function (Transaction $transaction) { + return AccountType::ASSET === $transaction->account->accountType->type; } ); + + return $result; + } + + /** + * @param TransactionJournal $transfer + * + * @return Transaction|null + */ + private function getSourceTransaction(TransactionJournal $transfer): ?Transaction + { + return $transfer->transactions->firstWhere('amount', '<', 0); } /** @@ -195,17 +179,12 @@ class JournalCurrencies extends Command /** * This method makes sure that the transaction journal uses the currency given in the transaction. * - * @param Transaction $transaction + * @param TransactionJournal $journal + * @param Transaction $transaction */ - private function updateJournalCurrency(Transaction $transaction): void + private function updateJournalCurrency(TransactionJournal $journal, Transaction $transaction): void { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - $accountRepos->setUser($transaction->account->user); - $currency = $repository->findNull((int)$accountRepos->getMetaValue($transaction->account, 'currency_id')); - $journal = $transaction->transactionJournal; + $currency = $this->getCurrency($transaction->account); $currencyCode = $journal->transactionCurrency->code ?? '(nothing)'; if (null === $currency) { @@ -225,11 +204,71 @@ class JournalCurrencies extends Command $journal->transaction_currency_id = $currency->id; $journal->save(); } - } /** - * This method makes sure that the tranaction uses the same currency as the source account does. + * @param TransactionJournal $journal + */ + private function updateOtherJournalCurrency(TransactionJournal $journal): void + { + $transaction = $this->getFirstAssetTransaction($journal); + if (null === $transaction) { + return; + } + /** @var Account $account */ + $account = $transaction->account; + $currency = $this->getCurrency($account); + if (null === $currency) { + return; + } + + $journal->transactions->each( + function (Transaction $transaction) use ($currency) { + if (null === $transaction->transaction_currency_id) { + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + + // when mismatch in transaction: + if (!((int)$transaction->transaction_currency_id === (int)$currency->id)) { + $transaction->foreign_currency_id = (int)$transaction->transaction_currency_id; + $transaction->foreign_amount = $transaction->amount; + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + } + ); + // also update the journal, of course: + $journal->transaction_currency_id = $currency->id; + $journal->save(); + } + + /** + * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for + * the accounts they are linked to. + * + * Both source and destination must match the respective currency preference of the related asset account. + * So FF3 must verify all transactions. + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function updateOtherJournalsCurrencies(): void + { + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereNotIn('transaction_types.type', [TransactionType::TRANSFER]) + ->with(['transactions', 'transactions.account', 'transactions.account.accountType']) + ->get(['transaction_journals.*']); + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $this->updateOtherJournalCurrency($journal); + } + } + + /** + * This method makes sure that the transaction uses the same currency as the source account does. * If not, the currency is updated to include a reference to its original currency as the "foreign" currency. * * The transaction that is sent to this function MUST be the source transaction (amount negative). @@ -242,116 +281,108 @@ class JournalCurrencies extends Command * * @param Transaction $transaction */ - private function updateTransactionCurrency(Transaction $transaction): void + private function updateTransactionCurrency(TransactionJournal $journal, Transaction $source, Transaction $destination): void { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - /** @var JournalRepositoryInterface $journalRepos */ - $journalRepos = app(JournalRepositoryInterface::class); + $user = $journal->user; + $sourceAccount = $source->account; + $destAccount = $destination->account; + $this->accountRepos->setUser($user); + $this->journalRepos->setUser($user); + $this->currencyRepos->setUser($user); - $accountRepos->setUser($transaction->account->user); - $journalRepos->setUser($transaction->account->user); - $currency = $repository->findNull((int)$accountRepos->getMetaValue($transaction->account, 'currency_id')); - - if (null === $currency) { - Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $transaction->account->id, $transaction->account->name)); + $sourceAccountCurrency = $this->getCurrency($sourceAccount); + $destAccountCurrency = $this->getCurrency($destAccount); + if (null === $sourceAccountCurrency) { + Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name)); return; } // has no currency ID? Must have, so fill in using account preference: - if (null === $transaction->transaction_currency_id) { - $transaction->transaction_currency_id = (int)$currency->id; - Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code)); - $transaction->save(); + if (null === $source->transaction_currency_id) { + $source->transaction_currency_id = (int)$sourceAccountCurrency->id; + Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $source->id, $sourceAccountCurrency->code)); + $source->save(); } // does not match the source account (see above)? Can be fixed // when mismatch in transaction and NO foreign amount is set: - if (!((int)$transaction->transaction_currency_id === (int)$currency->id) && null === $transaction->foreign_amount) { + if (!((int)$source->transaction_currency_id === (int)$sourceAccountCurrency->id) && null === $source->foreign_amount) { Log::debug( sprintf( 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', - $transaction->id, - $transaction->transaction_currency_id, - $currency->id, - $transaction->amount + $source->id, + $source->transaction_currency_id, + $sourceAccountCurrency->id, + $source->amount ) ); - $transaction->transaction_currency_id = (int)$currency->id; - $transaction->save(); + $source->transaction_currency_id = (int)$sourceAccountCurrency->id; + $source->save(); } - // grab opposing transaction: - /** @var TransactionJournal $journal */ - $journal = $transaction->transactionJournal; - /** @var Transaction $opposing */ - $opposing = $journal->transactions()->where('amount', '>', 0)->where('identifier', $transaction->identifier)->first(); - $opposingCurrency = $repository->findNull((int)$accountRepos->getMetaValue($opposing->account, 'currency_id')); - if (null === $opposingCurrency) { - Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $opposing->account->id, $opposing->account->name)); + if (null === $destAccountCurrency) { + Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name)); return; } // if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions: - if ((int)$opposingCurrency->id === (int)$currency->id) { + if ((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id) { // update both transactions to match: - $transaction->foreign_amount = null; - $transaction->foreign_currency_id = null; - $opposing->foreign_amount = null; - $opposing->foreign_currency_id = null; - $opposing->transaction_currency_id = $currency->id; - $transaction->save(); - $opposing->save(); + $source->foreign_amount = null; + $source->foreign_currency_id = null; + $destination->foreign_amount = null; + $destination->foreign_currency_id = null; + $source->save(); + $destination->save(); Log::debug( sprintf( 'Currency for account "%s" is %s, and currency for account "%s" is also %s, so %s #%d (#%d and #%d) has been verified to be to %s exclusively.', - $opposing->account->name, $opposingCurrency->code, - $transaction->account->name, $transaction->transactionCurrency->code, + $destAccount->name, $destAccountCurrency->code, + $sourceAccount->name, $sourceAccountCurrency->code, $journal->transactionType->type, $journal->id, - $transaction->id, $opposing->id, $currency->code + $source->id, $destination->id, $sourceAccountCurrency->code ) ); return; } + // if destination account currency is different, both transactions must have this currency as foreign currency id. - if (!((int)$opposingCurrency->id === (int)$currency->id)) { - $transaction->foreign_currency_id = $opposingCurrency->id; - $opposing->foreign_currency_id = $opposingCurrency->id; - $transaction->save(); - $opposing->save(); - Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $transaction->id, $opposing->id)); + if (!((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id)) { + $source->foreign_currency_id = $destAccountCurrency->id; + $destination->foreign_currency_id = $destAccountCurrency->id; + $source->save(); + $destination->save(); + Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $source->id, $destination->id)); } // if foreign amount of one is null and the other is not, use this to restore: - if (null === $transaction->foreign_amount && null !== $opposing->foreign_amount) { - $transaction->foreign_amount = bcmul((string)$opposing->foreign_amount, '-1'); - $transaction->save(); - Log::debug(sprintf('Restored foreign amount of transaction (1) #%d to %s', $transaction->id, $transaction->foreign_amount)); + if (null === $source->foreign_amount && null !== $destination->foreign_amount) { + $source->foreign_amount = bcmul((string)$destination->foreign_amount, '-1'); + $source->save(); + Log::debug(sprintf('Restored foreign amount of source transaction (1) #%d to %s', $source->id, $source->foreign_amount)); } // if foreign amount of one is null and the other is not, use this to restore (other way around) - if (null === $opposing->foreign_amount && null !== $transaction->foreign_amount) { - $opposing->foreign_amount = bcmul((string)$transaction->foreign_amount, '-1'); - $opposing->save(); - Log::debug(sprintf('Restored foreign amount of transaction (2) #%d to %s', $opposing->id, $opposing->foreign_amount)); + if (null === $destination->foreign_amount && null !== $destination->foreign_amount) { + $destination->foreign_amount = bcmul((string)$destination->foreign_amount, '-1'); + $destination->save(); + Log::debug(sprintf('Restored foreign amount of destination transaction (2) #%d to %s', $destination->id, $destination->foreign_amount)); } // when both are zero, try to grab it from journal: - if (null === $opposing->foreign_amount && null === $transaction->foreign_amount) { - $foreignAmount = $journalRepos->getMetaField($journal, 'foreign_amount'); + if (null === $source->foreign_amount && null === $destination->foreign_amount) { + $foreignAmount = $this->journalRepos->getMetaField($journal, 'foreign_amount'); if (null === $foreignAmount) { - Log::debug(sprintf('Journal #%d has missing foreign currency data, forced to do 1:1 conversion :(.', $transaction->transaction_journal_id)); - $transaction->foreign_amount = bcmul((string)$transaction->amount, '-1'); - $opposing->foreign_amount = bcmul((string)$opposing->amount, '-1'); - $transaction->save(); - $opposing->save(); + Log::debug(sprintf('Journal #%d has missing foreign currency data, forced to do 1:1 conversion :(.', $source->transaction_journal_id)); + $source->foreign_amount = $source->amount; + $destination->foreign_amount = $destination->amount; + $source->save(); + $destination->save(); return; } @@ -359,15 +390,61 @@ class JournalCurrencies extends Command Log::debug( sprintf( 'Journal #%d has missing foreign currency info, try to restore from meta-data ("%s").', - $transaction->transaction_journal_id, + $source->transaction_journal_id, $foreignAmount ) ); - $transaction->foreign_amount = bcmul($foreignPositive, '-1'); - $opposing->foreign_amount = $foreignPositive; - $transaction->save(); - $opposing->save(); + $source->foreign_amount = bcmul($foreignPositive, '-1'); + $destination->foreign_amount = $foreignPositive; + $source->save(); + $destination->save(); + } + } + + /** + * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. + * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they + * like it or not. Previous routines MUST have set the currency setting for both accounts for this to work. + * + * A transfer always has the + * + * Both source and destination must match the respective currency preference. So FF3 must verify ALL + * transactions. + */ + private function updateTransferCurrencies(): void + { + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->where('transaction_types.type', TransactionType::TRANSFER) + ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account']) + ->get(['transaction_journals.*']); + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $this->updateTransferCurrency($journal); + } + } + + /** + * @param TransactionJournal $transfer + */ + private function updateTransferCurrency(TransactionJournal $transfer): void + { + $sourceTransaction = $this->getSourceTransaction($transfer); + $destTransaction = $this->getDestinationTransaction($transfer); + + if (null === $sourceTransaction) { + $this->info(sprintf('Source transaction for journal #%d is null.', $transfer->id)); + + return; + } + if (null === $destTransaction) { + $this->info(sprintf('Destination transaction for journal #%d is null.', $transfer->id)); + + return; } + $this->updateTransactionCurrency($transfer, $sourceTransaction, $destTransaction); + $this->updateJournalCurrency($transfer, $sourceTransaction); } } \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/MigrateAttachments.php b/app/Console/Commands/Upgrade/MigrateAttachments.php index 39860d421b..ccf2991218 100644 --- a/app/Console/Commands/Upgrade/MigrateAttachments.php +++ b/app/Console/Commands/Upgrade/MigrateAttachments.php @@ -55,6 +55,7 @@ class MigrateAttachments extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -84,7 +85,8 @@ class MigrateAttachments extends Command Log::debug(sprintf('Migrated attachment #%s description to note #%d', $att->id, $note->id)); } } - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrated attachment notes in %s seconds.', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/MigrateNotes.php b/app/Console/Commands/Upgrade/MigrateNotes.php index 2c78de9a57..6eca5f18fb 100644 --- a/app/Console/Commands/Upgrade/MigrateNotes.php +++ b/app/Console/Commands/Upgrade/MigrateNotes.php @@ -56,6 +56,7 @@ class MigrateNotes extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -82,7 +83,8 @@ class MigrateNotes extends Command Log::error(sprintf('Could not delete old meta entry #%d: %s', $meta->id, $e->getMessage())); } } - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrated notes in %s seconds.', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php index b144d069ad..b9dca475d9 100644 --- a/app/Console/Commands/Upgrade/MigrateToGroups.php +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -21,14 +21,15 @@ namespace FireflyIII\Console\Commands\Upgrade; +use DB; use Exception; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Services\Internal\Destroy\JournalDestroyService; use Illuminate\Console\Command; +use Illuminate\Support\Collection; use Log; /** @@ -84,6 +85,7 @@ class MigrateToGroups extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isMigrated() && true !== $this->option('force')) { $this->info('Database already seems to be migrated.'); @@ -95,9 +97,14 @@ class MigrateToGroups extends Command Log::debug('---- start group migration ----'); $this->makeGroupsFromSplitJournals(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrate split journals to groups in %s seconds.', $end)); + + $start = microtime(true); $this->makeGroupsFromAll(); Log::debug('---- end group migration ----'); - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrate all journals to groups in %s seconds.', $end)); $this->markAsMigrated(); return 0; @@ -105,15 +112,42 @@ class MigrateToGroups extends Command /** * @param TransactionJournal $journal + * @param Transaction $transaction + * + * @return Transaction|null */ - private function giveGroup(TransactionJournal $journal): void + private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction { - $group = new TransactionGroup; - $group->title = null; - $group->user_id = $journal->user_id; - $group->save(); - $journal->transaction_group_id = $group->id; - $journal->save(); + $set = $journal->transactions->filter( + function (Transaction $subject) use ($transaction) { + return $transaction->amount * -1 === (float)$subject->amount && $transaction->identifier === $subject->identifier; + } + ); + + return $set->first(); + } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + private function getDestinationTransactions(TransactionJournal $journal): Collection + { + return $journal->transactions->filter( + function (Transaction $transaction) { + return $transaction->amount > 0; + } + ); + } + + /** + * @param array $array + */ + private function giveGroup(array $array): void + { + $groupId = DB::table('transaction_groups')->insertGetId(['title' => null, 'user_id' => $array['user_id']]); + DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]); } /** @@ -135,26 +169,26 @@ class MigrateToGroups extends Command private function makeGroupsFromAll(): void { $orphanedJournals = $this->journalRepository->getJournalsWithoutGroup(); - if ($orphanedJournals->count() > 0) { - Log::debug(sprintf('Going to convert %d transactions. Please hold..', $orphanedJournals->count())); - /** @var TransactionJournal $journal */ - foreach ($orphanedJournals as $journal) { - $this->giveGroup($journal); + $count = count($orphanedJournals); + if ($count > 0) { + Log::debug(sprintf('Going to convert %d transaction journals. Please hold..', $count)); + $this->line(sprintf('Going to convert %d transaction journals. Please hold..', $count)); + /** @var array $journal */ + foreach ($orphanedJournals as $array) { + $this->giveGroup($array); } } - if (0 === $orphanedJournals->count()) { - $this->info('No need to convert transactions.'); + if (0 === $count) { + $this->info('No need to convert transaction journals.'); } } /** - * * @throws Exception */ private function makeGroupsFromSplitJournals(): void { $splitJournals = $this->journalRepository->getSplitJournals(); - if ($splitJournals->count() > 0) { $this->info(sprintf('Going to convert %d split transaction(s). Please hold..', $splitJournals->count())); /** @var TransactionJournal $journal */ @@ -163,7 +197,7 @@ class MigrateToGroups extends Command } } if (0 === $splitJournals->count()) { - $this->info('Found no split transactions. Nothing to do.'); + $this->info('Found no split transaction journals. Nothing to do.'); } } @@ -185,7 +219,7 @@ class MigrateToGroups extends Command $this->journalRepository->setUser($journal->user); $this->journalFactory->setUser($journal->user); - $data = [ + $data = [ // mandatory fields. 'type' => strtolower($journal->transactionType->type), 'date' => $journal->date, @@ -193,16 +227,40 @@ class MigrateToGroups extends Command 'group_title' => $journal->description, 'transactions' => [], ]; + $destTransactions = $this->getDestinationTransactions($journal); + $budgetId = $this->journalRepository->getJournalBudgetId($journal); + $categoryId = $this->journalRepository->getJournalCategoryId($journal); + $notes = $this->journalRepository->getNoteText($journal); + $tags = $this->journalRepository->getTags($journal); + $internalRef = $this->journalRepository->getMetaField($journal, 'internal-reference'); + $sepaCC = $this->journalRepository->getMetaField($journal, 'sepa-cc'); + $sepaCtOp = $this->journalRepository->getMetaField($journal, 'sepa-ct-op'); + $sepaCtId = $this->journalRepository->getMetaField($journal, 'sepa-ct-id'); + $sepaDb = $this->journalRepository->getMetaField($journal, 'sepa-db'); + $sepaCountry = $this->journalRepository->getMetaField($journal, 'sepa-country'); + $sepaEp = $this->journalRepository->getMetaField($journal, 'sepa-ep'); + $sepaCi = $this->journalRepository->getMetaField($journal, 'sepa-ci'); + $sepaBatchId = $this->journalRepository->getMetaField($journal, 'sepa-batch-id'); + $externalId = $this->journalRepository->getMetaField($journal, 'external-id'); + $originalSource = $this->journalRepository->getMetaField($journal, 'original-source'); + $recurrenceId = $this->journalRepository->getMetaField($journal, 'recurrence_id'); + $bunq = $this->journalRepository->getMetaField($journal, 'bunq_payment_id'); + $hash = $this->journalRepository->getMetaField($journal, 'importHash'); + $hashTwo = $this->journalRepository->getMetaField($journal, 'importHashV2'); + $interestDate = $this->journalRepository->getMetaDate($journal, 'interest_date'); + $bookDate = $this->journalRepository->getMetaDate($journal, 'book_date'); + $processDate = $this->journalRepository->getMetaDate($journal, 'process_date'); + $dueDate = $this->journalRepository->getMetaDate($journal, 'due_date'); + $paymentDate = $this->journalRepository->getMetaDate($journal, 'payment_date'); + $invoiceDate = $this->journalRepository->getMetaDate($journal, 'invoice_date'); - $transactions = $journal->transactions()->where('amount', '>', 0)->get(); - Log::debug(sprintf('Will use %d positive transactions to create a new group.', $transactions->count())); + + Log::debug(sprintf('Will use %d positive transactions to create a new group.', $destTransactions->count())); /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { + foreach ($destTransactions as $transaction) { Log::debug(sprintf('Now going to add transaction #%d to the array.', $transaction->id)); - $budgetId = $this->journalRepository->getJournalBudgetId($journal); - $categoryId = $this->journalRepository->getJournalCategoryId($journal); - $opposingTr = $this->journalRepository->findOpposingTransaction($transaction); + $opposingTr = $this->findOpposingTransaction($journal, $transaction); if (null === $opposingTr) { $this->error( @@ -225,29 +283,29 @@ class MigrateToGroups extends Command 'budget_id' => $budgetId, 'category_id' => $categoryId, 'bill_id' => $journal->bill_id, - 'notes' => $this->journalRepository->getNoteText($journal), - 'tags' => $this->journalRepository->getTags($journal), - 'internal_reference' => $this->journalRepository->getMetaField($journal, 'internal-reference'), - 'sepa-cc' => $this->journalRepository->getMetaField($journal, 'sepa-cc'), - 'sepa-ct-op' => $this->journalRepository->getMetaField($journal, 'sepa-ct-op'), - 'sepa-ct-id' => $this->journalRepository->getMetaField($journal, 'sepa-ct-id'), - 'sepa-db' => $this->journalRepository->getMetaField($journal, 'sepa-db'), - 'sepa-country' => $this->journalRepository->getMetaField($journal, 'sepa-country'), - 'sepa-ep' => $this->journalRepository->getMetaField($journal, 'sepa-ep'), - 'sepa-ci' => $this->journalRepository->getMetaField($journal, 'sepa-ci'), - 'sepa-batch-id' => $this->journalRepository->getMetaField($journal, 'sepa-batch-id'), - 'external_id' => $this->journalRepository->getMetaField($journal, 'external-id'), - 'original-source' => $this->journalRepository->getMetaField($journal, 'original-source'), - 'recurrence_id' => $this->journalRepository->getMetaField($journal, 'recurrence_id'), - 'bunq_payment_id' => $this->journalRepository->getMetaField($journal, 'bunq_payment_id'), - 'importHash' => $this->journalRepository->getMetaField($journal, 'importHash'), - 'importHashV2' => $this->journalRepository->getMetaField($journal, 'importHashV2'), - 'interest_date' => $this->journalRepository->getMetaDate($journal, 'interest_date'), - 'book_date' => $this->journalRepository->getMetaDate($journal, 'book_date'), - 'process_date' => $this->journalRepository->getMetaDate($journal, 'process_date'), - 'due_date' => $this->journalRepository->getMetaDate($journal, 'due_date'), - 'payment_date' => $this->journalRepository->getMetaDate($journal, 'payment_date'), - 'invoice_date' => $this->journalRepository->getMetaDate($journal, 'invoice_date'), + 'notes' => $notes, + 'tags' => $tags, + 'internal_reference' => $internalRef, + 'sepa-cc' => $sepaCC, + 'sepa-ct-op' => $sepaCtOp, + 'sepa-ct-id' => $sepaCtId, + 'sepa-db' => $sepaDb, + 'sepa-country' => $sepaCountry, + 'sepa-ep' => $sepaEp, + 'sepa-ci' => $sepaCi, + 'sepa-batch-id' => $sepaBatchId, + 'external_id' => $externalId, + 'original-source' => $originalSource, + 'recurrence_id' => $recurrenceId, + 'bunq_payment_id' => $bunq, + 'importHash' => $hash, + 'importHashV2' => $hashTwo, + 'interest_date' => $interestDate, + 'book_date' => $bookDate, + 'process_date' => $processDate, + 'due_date' => $dueDate, + 'payment_date' => $paymentDate, + 'invoice_date' => $invoiceDate, ]; $data['transactions'][] = $tArray; @@ -260,8 +318,8 @@ class MigrateToGroups extends Command $this->service->destroy($journal); // report on result: - Log::debug(sprintf('Migrated journal #%d into these journals: %s', $journal->id, implode(', ', $result->pluck('id')->toArray()))); - $this->line(sprintf('Migrated journal #%d into these journals: %s', $journal->id, implode(', ', $result->pluck('id')->toArray()))); + Log::debug(sprintf('Migrated journal #%d into these journals: #%s', $journal->id, implode(', #', $result->pluck('id')->toArray()))); + $this->line(sprintf('Migrated journal #%d into these journals: #%s', $journal->id, implode(', #', $result->pluck('id')->toArray()))); } /** diff --git a/app/Console/Commands/Upgrade/MigrateToRules.php b/app/Console/Commands/Upgrade/MigrateToRules.php index 8756d268fe..9baca93974 100644 --- a/app/Console/Commands/Upgrade/MigrateToRules.php +++ b/app/Console/Commands/Upgrade/MigrateToRules.php @@ -64,6 +64,8 @@ class MigrateToRules extends Command */ public function handle(): int { + $start = microtime(true); + if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -111,11 +113,13 @@ class MigrateToRules extends Command // loop bills. $order = 1; + $count = 0; /** @var Collection $collection */ $collection = $user->bills()->get(); /** @var Bill $bill */ foreach ($collection as $bill) { if ('MIGRATED_TO_RULES' !== $bill->match) { + $count++; $rule = Rule::create( [ 'user_id' => $user->id, @@ -211,8 +215,15 @@ class MigrateToRules extends Command $bill->save(); } } + if ($count > 0) { + $this->info(sprintf('Migrated %d bills for user %s', $count, $user->email)); + } + if (0 === $count) { + $this->info(sprintf('Bills are correct for user %s.', $user->email)); + } } - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed bills in %s seconds.', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index 3bcd0927d7..212a6dafa3 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -61,6 +61,7 @@ class TransactionIdentifier extends Command */ public function handle(): int { + $start = microtime(true); if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -86,7 +87,8 @@ class TransactionIdentifier extends Command foreach ($journalIds as $journalId) { $this->updateJournalidentifiers((int)$journalId); } - + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end)); $this->markAsExecuted(); return 0; diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index 4b1675c279..737394dc67 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -21,6 +21,8 @@ namespace FireflyIII\Console\Commands\Upgrade; +set_time_limit(0); + use Artisan; use Illuminate\Console\Command; diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 982e1d9866..d4b574eda7 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -485,11 +485,11 @@ class JournalRepository implements JournalRepositoryInterface /** * Return all journals without a group, used in an upgrade routine. * - * @return Collection + * @return array */ - public function getJournalsWithoutGroup(): Collection + public function getJournalsWithoutGroup(): array { - return TransactionJournal::whereNull('transaction_group_id')->get(); + return TransactionJournal::whereNotNull('transaction_group_id')->get(['id', 'user_id'])->toArray(); } /** @@ -656,17 +656,16 @@ class JournalRepository implements JournalRepositoryInterface */ public function getSplitJournals(): Collection { - // grab all split transactions: - $all = Transaction::groupBy('transaction_journal_id')->get(['transaction_journal_id', DB::raw('COUNT(transaction_journal_id) as result')]); - /** @var Collection $filtered */ - $filtered = $all->filter( - function (Transaction $transaction) { - return (int)$transaction->result > 2; - } - ); - $journalIds = array_unique($filtered->pluck('transaction_journal_id')->toArray()); + $query = TransactionJournal + ::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->groupBy('transaction_journals.id') + ->having('tid', '>', 2) + ->get(['transaction_journals.id as jid', DB::raw('count(transactions.id) as tid')]); + $journalIds = array_unique($query->pluck('jid')->toArray()); - return TransactionJournal::whereIn('id', $journalIds)->get(); + return TransactionJournal + ::with(['transactions']) + ->whereIn('id', $journalIds)->get(); } /** diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 60cdb8f3d7..1819c16678 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -207,9 +207,9 @@ interface JournalRepositoryInterface /** * Return all journals without a group, used in an upgrade routine. * - * @return Collection + * @return array */ - public function getJournalsWithoutGroup(): Collection; + public function getJournalsWithoutGroup(): array; /** * @param TransactionJournalLink $link diff --git a/composer.json b/composer.json index c9591688b2..1b9d52d462 100644 --- a/composer.json +++ b/composer.json @@ -131,9 +131,34 @@ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" ], "post-update-cmd": [ - "@php artisan firefly-iii:upgrade-database", "@php artisan firefly:decrypt-all", - "@php artisan firefly:verify", + "@php artisan firefly-iii:transaction-identifiers", + "@php artisan firefly-iii:account-currencies", + "@php artisan firefly-iii:journal-currencies", + "@php artisan firefly-iii:migrate-notes", + "@php artisan firefly-iii:migrate-attachments", + "@php artisan firefly-iii:bills-to-rules", + "@php artisan firefly-iii:bl-currency", + "@php artisan firefly-iii:cc-liabilities", + "@php artisan firefly-iii:migrate-to-groups", + "@php artisan firefly-iii:back-to-journals", + + "@php artisan firefly-iii:fix-piggies", + "@php artisan firefly-iii:create-link-types", + "@php artisan firefly-iii:create-access-tokens", + "@php artisan firefly-iii:remove-bills", + "@php artisan firefly-iii:enable-currencies", + "@php artisan firefly-iii:fix-transfer-budgets", + "@php artisan firefly-iii:fix-uneven-amount", + "@php artisan firefly-iii:delete-zero-amount", + "@php artisan firefly-iii:delete-orphaned-transactions", + "@php artisan firefly-iii:delete-empty-journals", + "@php artisan firefly-iii:delete-empty-groups", + "@php artisan firefly-iii:fix-account-types", + + "@php artisan firefly-iii:report-empty-objects", + "@php artisan firefly-iii:report-sum", + "@php artisan firefly:instructions update", "@php artisan passport:install" ], diff --git a/composer.lock b/composer.lock index 2d1fa511ce..7ddafef7ba 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "adldap2/adldap2", - "version": "v10.0.5", + "version": "v10.0.6", "source": { "type": "git", "url": "https://github.com/Adldap2/Adldap2.git", - "reference": "74b6cd016b4e606fd2e2197dae6955cc4bd9e28d" + "reference": "efbf25c80861e47a5443d176dfa1a640000e8a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/74b6cd016b4e606fd2e2197dae6955cc4bd9e28d", - "reference": "74b6cd016b4e606fd2e2197dae6955cc4bd9e28d", + "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/efbf25c80861e47a5443d176dfa1a640000e8a20", + "reference": "efbf25c80861e47a5443d176dfa1a640000e8a20", "shasum": "" }, "require": { @@ -61,7 +61,7 @@ "ldap", "windows" ], - "time": "2019-03-18T19:39:15+00:00" + "time": "2019-03-22T18:25:32+00:00" }, { "name": "adldap2/adldap2-laravel", @@ -1308,16 +1308,16 @@ }, { "name": "laravel/framework", - "version": "v5.8.5", + "version": "v5.8.7", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "791992e20efdf043ac3c2d989025d48d648821de" + "reference": "f12c7baf9ceee80b131e06a01d3221d9a2488670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/791992e20efdf043ac3c2d989025d48d648821de", - "reference": "791992e20efdf043ac3c2d989025d48d648821de", + "url": "https://api.github.com/repos/laravel/framework/zipball/f12c7baf9ceee80b131e06a01d3221d9a2488670", + "reference": "f12c7baf9ceee80b131e06a01d3221d9a2488670", "shasum": "" }, "require": { @@ -1451,7 +1451,7 @@ "framework", "laravel" ], - "time": "2019-03-19T14:20:36+00:00" + "time": "2019-03-21T16:54:38+00:00" }, { "name": "laravel/passport", @@ -1651,16 +1651,16 @@ }, { "name": "league/commonmark", - "version": "0.18.2", + "version": "0.18.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "ad51c7cafb90e0bbd9f34b71d18d05994547e352" + "reference": "b1ec41ce15c3bd6f7cbe86a645b3efc78d927446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ad51c7cafb90e0bbd9f34b71d18d05994547e352", - "reference": "ad51c7cafb90e0bbd9f34b71d18d05994547e352", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b1ec41ce15c3bd6f7cbe86a645b3efc78d927446", + "reference": "b1ec41ce15c3bd6f7cbe86a645b3efc78d927446", "shasum": "" }, "require": { @@ -1676,7 +1676,7 @@ "erusev/parsedown": "~1.0", "michelf/php-markdown": "~1.4", "mikehaertl/php-shellcommand": "^1.2", - "phpunit/phpunit": "^5.7|^6.5", + "phpunit/phpunit": "^5.7.27|^6.5.14", "scrutinizer/ocular": "^1.1", "symfony/finder": "^3.0|^4.0" }, @@ -1716,7 +1716,7 @@ "markdown", "parser" ], - "time": "2019-03-17T01:41:59+00:00" + "time": "2019-03-21T22:47:25+00:00" }, { "name": "league/csv", @@ -3808,16 +3808,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -3829,7 +3829,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3862,20 +3862,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "97001cfc283484c9691769f51cdf25259037eba2" + "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/97001cfc283484c9691769f51cdf25259037eba2", - "reference": "97001cfc283484c9691769f51cdf25259037eba2", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", + "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", "shasum": "" }, "require": { @@ -3887,7 +3887,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3921,20 +3921,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "89de1d44f2c059b266f22c9cc9124ddc4cd0987a" + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/89de1d44f2c059b266f22c9cc9124ddc4cd0987a", - "reference": "89de1d44f2c059b266f22c9cc9124ddc4cd0987a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af", "shasum": "" }, "require": { @@ -3983,20 +3983,20 @@ "portable", "shim" ], - "time": "2018-09-30T16:36:12+00:00" + "time": "2019-03-04T13:44:35+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -4008,7 +4008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -4042,20 +4042,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af" + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ff208829fe1aa48ab9af356992bb7199fed551af", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", "shasum": "" }, "require": { @@ -4065,7 +4065,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -4098,20 +4098,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -4120,7 +4120,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -4153,20 +4153,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c" + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/3b58903eae668d348a7126f999b0da0f2f93611c", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/b46c6cae28a3106735323f00a0c38eccf2328897", + "reference": "b46c6cae28a3106735323f00a0c38eccf2328897", "shasum": "" }, "require": { @@ -4175,7 +4175,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -4205,7 +4205,7 @@ "polyfill", "shim" ], - "time": "2018-09-30T16:36:12+00:00" + "time": "2019-02-08T14:16:39+00:00" }, { "name": "symfony/process", @@ -4549,7 +4549,7 @@ }, { "name": "tightenco/collect", - "version": "v5.8.5", + "version": "v5.8.7", "source": { "type": "git", "url": "https://github.com/tightenco/collect.git", @@ -4646,16 +4646,16 @@ }, { "name": "twig/twig", - "version": "v1.38.2", + "version": "v1.38.4", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "874adbd9222f928f6998732b25b01b41dff15b0c" + "reference": "7732e9e7017d751313811bd118de61302e9c8b35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/874adbd9222f928f6998732b25b01b41dff15b0c", - "reference": "874adbd9222f928f6998732b25b01b41dff15b0c", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7732e9e7017d751313811bd118de61302e9c8b35", + "reference": "7732e9e7017d751313811bd118de61302e9c8b35", "shasum": "" }, "require": { @@ -4708,7 +4708,7 @@ "keywords": [ "templating" ], - "time": "2019-03-12T18:45:24+00:00" + "time": "2019-03-23T14:27:19+00:00" }, { "name": "vlucas/phpdotenv", @@ -5258,27 +5258,29 @@ }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -5303,12 +5305,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "filp/whoops", @@ -6353,12 +6355,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "33bad66e16fa13d87493018fd339eff48d431ad1" + "reference": "018ec51b676a4d1efc971950d1d9619570b71676" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/33bad66e16fa13d87493018fd339eff48d431ad1", - "reference": "33bad66e16fa13d87493018fd339eff48d431ad1", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/018ec51b676a4d1efc971950d1d9619570b71676", + "reference": "018ec51b676a4d1efc971950d1d9619570b71676", "shasum": "" }, "conflict": { @@ -6393,8 +6395,8 @@ "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.62|>=8,<8.5.11|>=8.6,<8.6.10", - "drupal/drupal": ">=7,<7.62|>=8,<8.5.11|>=8.6,<8.6.10", + "drupal/core": ">=7,<7.64|>=8,<8.5.13|>=8.6,<8.6.12", + "drupal/drupal": ">=7,<7.64|>=8,<8.5.13|>=8.6,<8.6.12", "erusev/parsedown": "<1.7", "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3", @@ -6422,7 +6424,7 @@ "la-haute-societe/tcpdf": "<6.2.22", "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", - "league/commonmark": ">=0.15.6,<0.18.1", + "league/commonmark": "<0.18.3", "magento/magento1ce": "<1.9.4", "magento/magento1ee": ">=1.9,<1.14.4", "magento/product-community-edition": ">=2,<2.2.7", @@ -6549,7 +6551,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2019-03-19T18:31:23+00:00" + "time": "2019-03-22T05:18:50+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", diff --git a/readme.md b/readme.md index d86f85bb43..c864c2155a 100644 --- a/readme.md +++ b/readme.md @@ -136,9 +136,7 @@ This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/L ### Donate If you like Firefly III and if it helps you save lots of money, why not send me a dime for every dollar saved! -OK that was a joke. You can become a backer or a sponsor of the [Firefly III Open Collective](https://opencollective.com/firefly-iii). On that page, I transparently show you the expenses I make and the donations I receive. - -You can also donate using [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA). +OK that was a joke. You could donate using [PayPal](https://docs.firefly-iii.org/en/latest/contact/note-donations.html). Please find the button on the page. Thank you for considering donating to Firefly III!