Improve upgrade command structure.

This commit is contained in:
James Cole 2019-03-18 16:53:05 +01:00
parent 3545d894fd
commit e411d7a825
7 changed files with 680 additions and 254 deletions

View File

@ -0,0 +1,155 @@
<?php
/**
* AccountCurrencies.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Console\Command;
use Log;
use UnexpectedValueException;
/**
* Class AccountCurrencies
*/
class AccountCurrencies extends Command
{
public const CONFIG_NAME = '4780_account_currencies';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Give all accounts proper currency info.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}';
/**
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
*
* @return int
*/
public function handle(): int
{
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
Log::debug('Now in 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;
}
);
$this->markAsExecuted();
return 0;
}
/**
* @return bool
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false; // @codeCoverageIgnore
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -0,0 +1,168 @@
<?php
/**
* BackToJournals.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
/**
* Class BackToJournals
*/
class BackToJournals extends Command
{
public const CONFIG_NAME = '4780_back_to_journals';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Move meta data back to journals, not individual transactions.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:back-to-journals {--F|force : Force the execution of this command.}';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
if (!$this->isMigrated()) {
$this->error('Please run firefly-iii:migrate-to-groups first.');
}
if ($this->isExecuted() && true !== $this->option('force')) {
$this->info('This command has been executed already.');
return 0;
}
if (true === $this->option('force')) {
$this->warn('Forcing the command.');
}
$this->migrateAll();
$this->info('Updated category and budget info for all journals.');
$this->markAsExecuted();
return 0;
}
/**
* @return bool
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false; // @codeCoverageIgnore
}
/**
* @return bool
*/
private function isMigrated(): bool
{
$configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false; // @codeCoverageIgnore
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
*
*/
private function migrateAll(): void
{
$journals = TransactionJournal::get();
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$this->migrateCategoriesForJournal($journal);
$this->migrateBudgetsForJournal($journal);
}
}
/**
* @param TransactionJournal $journal
*/
private function migrateBudgetsForJournal(TransactionJournal $journal): void
{
// grab category from first transaction
/** @var Transaction $transaction */
$transaction = $journal->transactions()->first();
/** @var Budget $budget */
$budget = $transaction->budgets()->first();
if (null !== $budget) {
// sync to journal:
$journal->budgets()->sync([(int)$budget->id]);
// remove from transactions:
$journal->transactions()->each(
function (Transaction $transaction) {
$transaction->budgets()->sync([]);
}
);
}
}
/**
* @param TransactionJournal $journal
*/
private function migrateCategoriesForJournal(TransactionJournal $journal): void
{
// grab category from first transaction
/** @var Transaction $transaction */
$transaction = $journal->transactions()->first();
/** @var Category $category */
$category = $transaction->categories()->first();
if (null !== $category) {
// sync to journal:
$journal->categories()->sync([(int)$category->id]);
// remove from transactions:
$journal->transactions()->each(
function (Transaction $transaction) {
$transaction->categories()->sync([]);
}
);
}
}
}

View File

@ -21,17 +21,13 @@
namespace FireflyIII\Console\Commands\Upgrade;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionJournalFactory;
use FireflyIII\Models\Transaction;
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;
/**
@ -43,6 +39,7 @@ use Log;
*/
class MigrateToGroups extends Command
{
public const CONFIG_NAME = '4780_migrated_to_groups';
/**
* The console command description.
*
@ -54,7 +51,7 @@ class MigrateToGroups extends Command
*
* @var string
*/
protected $signature = 'firefly:migrate-to-groups {--F|force : Force the migration, even if it fired before.}';
protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}';
/** @var TransactionJournalFactory */
private $journalFactory;
@ -62,6 +59,9 @@ class MigrateToGroups extends Command
/** @var JournalRepositoryInterface */
private $journalRepository;
/** @var JournalDestroyService */
private $service;
/**
* Create a new command instance.
*
@ -72,80 +72,7 @@ class MigrateToGroups extends Command
parent::__construct();
$this->journalFactory = app(TransactionJournalFactory::class);
$this->journalRepository = app(JournalRepositoryInterface::class);
}
/**
* @param TransactionJournal $journal
* @param Transaction $transaction
*
* @return int
*/
public function getBudgetId(TransactionJournal $journal, Transaction $transaction): int
{
$budgetId = 0;
if (null !== $transaction->budgets()->first()) {
$budgetId = (int)$transaction->budgets()->first()->id; // done!
Log::debug(sprintf('Transaction #%d has a reference to budget #%d so will use that one.', $transaction->id, $budgetId));
}
if (0 === $budgetId && $journal->budgets()->first()) {
$budgetId = (int)$journal->budgets()->first()->id; // also done!
Log::debug(
sprintf('Transaction #%d has NO budget, but journal #%d has budget #%d so will use that one.', $transaction->id, $journal->id, $budgetId)
);
}
Log::debug(sprintf('Final budget ID for journal #%d and transaction #%d is %d', $journal->id, $transaction->id, $budgetId));
return $budgetId;
}
/**
* @param TransactionJournal $journal
* @param Transaction $transaction
*
* @return int
*/
public function getCategoryId(TransactionJournal $journal, Transaction $transaction): int
{
$categoryId = 0;
if (null !== $transaction->categories()->first()) {
$categoryId = (int)$transaction->categories()->first()->id; // done!
Log::debug(sprintf('Transaction #%d has a reference to category #%d so will use that one.', $transaction->id, $categoryId));
}
if (0 === $categoryId && $journal->categories()->first()) {
$categoryId = (int)$journal->categories()->first()->id; // also done!
Log::debug(
sprintf('Transaction #%d has NO category, but journal #%d has category #%d so will use that one.', $transaction->id, $journal->id, $categoryId)
);
}
Log::debug(sprintf('Final category ID for journal #%d and transaction #%d is %d', $journal->id, $transaction->id, $categoryId));
return $categoryId;
}
/**
* @param TransactionJournal $journal
* @param Transaction $transaction
*
* @return Transaction
* @throws FireflyException
*/
public function getOpposingTransaction(TransactionJournal $journal, Transaction $transaction): Transaction
{
/** @var Transaction $opposing */
$opposing = $journal->transactions()->where('amount', $transaction->amount * -1)->where('identifier', $transaction->identifier)->first();
if (null === $opposing) {
$message = sprintf(
'Could not convert journal #%d ("%s") because transaction #%d has no opposite entry in the database. This requires manual intervention beyond the capabilities of this script. Please open an issue on GitHub.',
$journal->id, $journal->description, $transaction->id
);
$this->error($message);
throw new FireflyException($message);
}
Log::debug(sprintf('Found opposing transaction #%d for transaction #%d (both part of journal #%d)', $opposing->id, $transaction->id, $journal->id));
return $opposing;
$this->service = app(JournalDestroyService::class);
}
/**
@ -158,6 +85,8 @@ class MigrateToGroups extends Command
{
if ($this->isMigrated() && true !== $this->option('force')) {
$this->info('Database already seems to be migrated.');
return 0;
}
if (true === $this->option('force')) {
$this->warn('Forcing the migration.');
@ -177,13 +106,12 @@ class MigrateToGroups extends Command
*/
private function isMigrated(): bool
{
$configName = 'migrated_to_groups_4780';
$configVar = app('fireflyconfig')->get($configName, false);
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
return false; // @codeCoverageIgnore
}
/**
@ -204,9 +132,6 @@ class MigrateToGroups extends Command
$this->journalRepository->setUser($journal->user);
$this->journalFactory->setUser($journal->user);
/** @var JournalDestroyService $service */
$service = app(JournalDestroyService::class);
$data = [
// mandatory fields.
'type' => strtolower($journal->transactionType->type),
@ -222,12 +147,21 @@ class MigrateToGroups extends Command
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
Log::debug(sprintf('Now going to add transaction #%d to the array.', $transaction->id));
$budgetId = $this->getBudgetId($journal, $transaction);
$categoryId = $this->getCategoryId($journal, $transaction);
$opposingTr = $this->getOpposingTransaction($journal, $transaction);
$tArray = [
$budgetId = $this->journalRepository->getJournalBudgetId($journal);
$categoryId = $this->journalRepository->getJournalCategoryId($journal);
$opposingTr = $this->journalRepository->findOpposingTransaction($transaction);
// currency and foreign currency
if (null === $opposingTr) {
$this->error(
sprintf(
'Journal #%d has no opposing transaction for transaction #%d. Cannot upgrade this entry.',
$journal->id, $transaction->id
)
);
continue;
}
$tArray = [
'currency_id' => $transaction->transaction_currency_id,
'foreign_currency_id' => $transaction->foreign_currency_id,
'amount' => $transaction->amount,
@ -239,7 +173,7 @@ class MigrateToGroups extends Command
'category_id' => $categoryId,
'bill_id' => $journal->bill_id,
'notes' => $this->journalRepository->getNoteText($journal),
'tags' => $journal->tags->pluck('tag')->toArray(),
'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'),
@ -270,7 +204,7 @@ class MigrateToGroups extends Command
Log::debug('Done calling transaction journal factory');
// delete the old transaction journal.
//$service->destroy($journal);
//$this->service->destroy($journal);
// report on result:
Log::debug(sprintf('Migrated journal #%d into these journals: %s', $journal->id, implode(', ', $result->pluck('id')->toArray())));
@ -283,16 +217,8 @@ class MigrateToGroups extends Command
*/
private function makeGroups(): void
{
// 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());
$splitJournals = TransactionJournal::whereIn('id', $journalIds)->get();
$splitJournals = $this->journalRepository->getSplitJournals();
if ($splitJournals->count() > 0) {
$this->info(sprintf('Going to un-split %d transaction(s). This could take some time.', $splitJournals->count()));
/** @var TransactionJournal $journal */
@ -300,6 +226,9 @@ class MigrateToGroups extends Command
$this->makeGroup($journal);
}
}
if (0 === $splitJournals->count()) {
$this->info('Found no split journals. Nothing to do.');
}
}
/**
@ -307,7 +236,7 @@ class MigrateToGroups extends Command
*/
private function markAsMigrated(): void
{
app('fireflyconfig')->set('migrated_to_groups_4780', true);
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -0,0 +1,160 @@
<?php
/**
* TransactionIdentifier.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Upgrade;
use DB;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Log;
use Schema;
/**
* Class TransactionIdentifier
*/
class TransactionIdentifier extends Command
{
public const CONFIG_NAME = '4780_transaction_identifier';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fixes transaction identifiers.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:transaction-identifiers {--F|force : Force the execution of this command.}';
/**
* This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier
* to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example.
*
* In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5.
*
* When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would
* think. So each set gets a number (1,2,3) to keep them apart.
*
* @return int
*/
public function handle(): int
{
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
// if table does not exist, return false
if (!Schema::hasTable('transaction_journals')) {
return 0;
}
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->groupBy(['transaction_journals.id'])
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
/** @noinspection PhpStrictTypeCheckingInspection */
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
->mergeBindings($subQuery->getQuery())
->where('t_count', '>', 2)
->select(['id', 't_count']);
$journalIds = array_unique($result->pluck('id')->toArray());
foreach ($journalIds as $journalId) {
$this->updateJournalidentifiers((int)$journalId);
}
$this->markAsExecuted();
return 0;
}
/**
* @return bool
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false; // @codeCoverageIgnore
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
* which has 0 as an identifier and give it the same identifier.
*
* @param int $journalId
*/
private function updateJournalidentifiers(int $journalId): void
{
$identifier = 0;
$processed = [];
$transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
// find opposing:
$amount = bcmul((string)$transaction->amount, '-1');
try {
/** @var Transaction $opposing */
$opposing = Transaction::where('transaction_journal_id', $journalId)
->where('amount', $amount)->where('identifier', '=', 0)
->whereNotIn('id', $processed)
->first();
} catch (QueryException $e) {
Log::error($e->getMessage());
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
$this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
$this->error('Please run "php artisan migrate" to add this field to the table.');
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
return;
}
if (null !== $opposing) {
// give both a new identifier:
$transaction->identifier = $identifier;
$opposing->identifier = $identifier;
$transaction->save();
$opposing->save();
$processed[] = $transaction->id;
$processed[] = $opposing->id;
}
++$identifier;
}
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* UpgradeDatabase.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Upgrade;
use Artisan;
use Illuminate\Console\Command;
/**
* Class UpgradeDatabase
*/
class UpgradeDatabase extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Executes all upgrade commands.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:upgrade-database {--F|force : Force all upgrades.}';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$commands = [
'firefly-iii:transaction-identifiers',
'firefly-iii:account-currencies',
'firefly-iii:migrate-to-groups',
'firefly-iii:back-to-journals',
];
$args = [];
if ($this->option('force')) {
$args = ['--force' => true];
}
foreach ($commands as $command) {
$this->line(sprintf('Now executing %s', $command));
Artisan::call($command, $args);
$result = Artisan::output();
echo $result;
}
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* UpgradeSkeleton.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Upgrade;
use Illuminate\Console\Command;
/**
* Class UpgradeSkeleton
*/
class UpgradeSkeleton extends Command
{
public const CONFIG_NAME = '4780_some_name';
/**
* The console command description.
*
* @var string
*/
protected $description = 'SOME DESCRIPTION';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:SKELETON {--F|force : Force the execution of this command.}';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
//$this->markAsExecuted();
return 0;
}
/**
* @return bool
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false; // @codeCoverageIgnore
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* UpgradeDatabase.php
* UpgradeDatabaseX.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
@ -70,7 +70,7 @@ use UnexpectedValueException;
*
* @codeCoverageIgnore
*/
class UpgradeDatabase extends Command
class UpgradeDatabaseX extends Command
{
/**
* The console command description.
@ -83,14 +83,13 @@ class UpgradeDatabase extends Command
*
* @var string
*/
protected $signature = 'firefly:upgrade-database';
protected $signature = 'firefly:upgrade-databaseX';
/**
* Execute the console command.
*/
public function handle(): int
{
$this->setTransactionIdentifier();
$this->updateAccountCurrencies();
$this->createNewTypes();
$this->line('Updating currency information..');
@ -277,112 +276,8 @@ class UpgradeDatabase extends Command
}
}
/**
* This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier
* to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example.
*
* In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5.
*
* When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would
* think. So each set gets a number (1,2,3) to keep them apart.
*/
public function setTransactionIdentifier(): void
{
// if table does not exist, return false
if (!Schema::hasTable('transaction_journals')) {
return;
}
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->groupBy(['transaction_journals.id'])
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
/** @noinspection PhpStrictTypeCheckingInspection */
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
->mergeBindings($subQuery->getQuery())
->where('t_count', '>', 2)
->select(['id', 't_count']);
$journalIds = array_unique($result->pluck('id')->toArray());
foreach ($journalIds as $journalId) {
$this->updateJournalidentifiers((int)$journalId);
}
}
/**
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function updateAccountCurrencies(): void
{
Log::debug('Now in 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;
}
);
}
/**
* This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for
@ -638,50 +533,7 @@ class UpgradeDatabase extends Command
}
/**
* grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one
* which has 0 as an identifier and give it the same identifier.
*
* @param int $journalId
*/
private function updateJournalidentifiers(int $journalId): void
{
$identifier = 0;
$processed = [];
$transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
// find opposing:
$amount = bcmul((string)$transaction->amount, '-1');
try {
/** @var Transaction $opposing */
$opposing = Transaction::where('transaction_journal_id', $journalId)
->where('amount', $amount)->where('identifier', '=', 0)
->whereNotIn('id', $processed)
->first();
} catch (QueryException $e) {
Log::error($e->getMessage());
$this->error('Firefly III could not find the "identifier" field in the "transactions" table.');
$this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
$this->error('Please run "php artisan migrate" to add this field to the table.');
$this->info('Then, run "php artisan firefly:upgrade-database" to try again.');
return;
}
if (null !== $opposing) {
// give both a new identifier:
$transaction->identifier = $identifier;
$opposing->identifier = $identifier;
$transaction->save();
$opposing->save();
$processed[] = $transaction->id;
$processed[] = $opposing->id;
}
++$identifier;
}
}
/**
* This method makes sure that the tranaction uses the same currency as the source account does.