mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Refactor upgrade and verify commands.
This commit is contained in:
parent
1b0be2a47e
commit
ce30375341
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
namespace FireflyIII\Console\Commands\Upgrade;
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
use Artisan;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
],
|
||||
|
160
composer.lock
generated
160
composer.lock
generated
@ -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",
|
||||
|
@ -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!
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user