mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-11-19 16:10:00 +00:00
Improve test coverage.
This commit is contained in:
@@ -30,6 +30,7 @@ use Schema;
|
||||
|
||||
/**
|
||||
* Class CorrectDatabase
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class CorrectDatabase extends Command
|
||||
{
|
||||
@@ -38,7 +39,7 @@ class CorrectDatabase extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Will correct the integrity of your database, of necessary.';
|
||||
protected $description = 'Will correct the integrity of your database, if necessary.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -35,7 +36,7 @@ class CreateAccessTokens extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Creates user access tokens.';
|
||||
protected $description = 'Creates user access tokens which are used for command line access to personal data.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
@@ -51,9 +52,13 @@ class CreateAccessTokens extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// make repository:
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
$start = microtime(true);
|
||||
$count = 0;
|
||||
$users = User::get();
|
||||
$users= $repository->all();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$pref = app('preferences')->getForUser($user, 'access_token', null);
|
||||
|
||||
@@ -58,12 +58,13 @@ class CreateLinkTypes extends Command
|
||||
'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'],
|
||||
];
|
||||
foreach ($set as $name => $values) {
|
||||
$link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first();
|
||||
$link = LinkType::where('name', $name)
|
||||
->first();
|
||||
if (null === $link) {
|
||||
$link = new LinkType;
|
||||
$link->name = $name;
|
||||
$link->outward = $values[0];
|
||||
$link->inward = $values[1];
|
||||
$link->outward = $values[0];
|
||||
++$count;
|
||||
$this->line(sprintf('Created missing link type "%s"', $name));
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class DeleteEmptyGroups extends Command
|
||||
$this->info('No empty transaction groups.');
|
||||
}
|
||||
if ($count > 0) {
|
||||
$this->info(sprintf('Deleted %d empty transaction groups.', $count));
|
||||
$this->info(sprintf('Deleted %d empty transaction group(s).', $count));
|
||||
TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->delete();
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
|
||||
@@ -72,9 +72,12 @@ class DeleteEmptyJournals extends Command
|
||||
foreach ($set as $entry) {
|
||||
try {
|
||||
TransactionJournal::find($entry->id)->delete();
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Exception $e) {
|
||||
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
|
||||
++$count;
|
||||
}
|
||||
@@ -90,15 +93,6 @@ class DeleteEmptyJournals extends Command
|
||||
*/
|
||||
private function deleteUnevenJournals(): void
|
||||
{
|
||||
/**
|
||||
* select count(transactions.transaction_journal_id) as the_count, transactions.transaction_journal_id from transactions
|
||||
*
|
||||
* where transactions.deleted_at is null
|
||||
*
|
||||
* group by transactions.transaction_journal_id
|
||||
* having the_count in ()
|
||||
*/
|
||||
|
||||
$set = Transaction
|
||||
::whereNull('deleted_at')
|
||||
->having('the_count', '!=', '2')
|
||||
@@ -111,9 +105,12 @@ class DeleteEmptyJournals extends Command
|
||||
// uneven number, delete journal and transactions:
|
||||
try {
|
||||
TransactionJournal::find((int)$row->transaction_journal_id)->delete();
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch(Exception $e) {
|
||||
Log::info(sprintf('Could not delete journal: %s', $e->getMessage()));
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
Transaction::where('transaction_journal_id', (int)$row->transaction_journal_id)->delete();
|
||||
$this->info(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $row->transaction_journal_id));
|
||||
$total++;
|
||||
|
||||
@@ -25,8 +25,8 @@ use Exception;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
use stdClass;
|
||||
use Log;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Deletes transactions where the journal has been deleted.
|
||||
@@ -81,17 +81,20 @@ class DeleteOrphanedTransactions extends Command
|
||||
if ($journal) {
|
||||
try {
|
||||
$journal->delete();
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Exception $e) {
|
||||
Log::info(sprintf('Could not delete transaction %s', $e->getMessage()));
|
||||
Log::info(sprintf('Could not delete journal %s', $e->getMessage()));
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete();
|
||||
$this->line(
|
||||
sprintf('Deleted transaction journal #%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++;
|
||||
}
|
||||
if(0===$count) {
|
||||
if (0 === $count) {
|
||||
$this->info('No orphaned accounts.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ class DeleteZeroAmount extends Command
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @throws Exception
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
@@ -60,7 +59,13 @@ class DeleteZeroAmount extends Command
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id));
|
||||
$journal->delete();
|
||||
try {
|
||||
$journal->delete();
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (Exception $e) {
|
||||
$this->line($e->getMessage());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
Transaction::where('transaction_journal_id', $journal->id)->delete();
|
||||
}
|
||||
if (0 === $journals->count()) {
|
||||
|
||||
@@ -86,9 +86,13 @@ class EnableCurrencies extends Command
|
||||
|
||||
$found = array_unique($found);
|
||||
$this->info(sprintf('%d different currencies are currently in use.', count($found)));
|
||||
|
||||
$disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count();
|
||||
if ($disabled > 0) {
|
||||
$this->info(sprintf('%d were still disabled. This has been corrected.', $disabled));
|
||||
$this->info(sprintf('%d were (was) still disabled. This has been corrected.', $disabled));
|
||||
}
|
||||
if (0 === $disabled) {
|
||||
$this->info('All currencies are correctly enabled or disabled.');
|
||||
}
|
||||
TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]);
|
||||
|
||||
|
||||
@@ -52,82 +52,16 @@ class FixAccountTypes extends Command
|
||||
private $factory;
|
||||
/** @var array */
|
||||
private $fixable;
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $type
|
||||
* @param Transaction $source
|
||||
* @param Transaction $dest
|
||||
* @throws FireflyException
|
||||
* FixAccountTypes constructor.
|
||||
*/
|
||||
public function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void
|
||||
public function __construct()
|
||||
{
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,19 +93,103 @@ class FixAccountTypes extends Command
|
||||
|
||||
|
||||
$this->expected = config('firefly.source_dests');
|
||||
$journals = TransactionJournal
|
||||
|
||||
::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
|
||||
$journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
|
||||
foreach ($journals as $journal) {
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
if (0 === $this->count) {
|
||||
$this->info('All account types are OK!');
|
||||
}
|
||||
if (0 !== $this->count) {
|
||||
$this->info(sprintf('Acted on %d transaction(s)!', $this->count));
|
||||
}
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified account types in %s seconds', $end));
|
||||
$this->info(sprintf('Verifying account types took %s seconds', $end));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $type
|
||||
* @param Transaction $source
|
||||
* @param Transaction $dest
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void
|
||||
{
|
||||
$this->count++;
|
||||
// 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);
|
||||
$oldDest = $dest->account;
|
||||
$result = $this->factory->findOrCreate($dest->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,
|
||||
$oldDest->id, $oldDest->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);
|
||||
$oldSource = $dest->account;
|
||||
$source->account()->associate($result);
|
||||
$source->save();
|
||||
$this->info(
|
||||
sprintf(
|
||||
'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id,
|
||||
$oldSource->id, $oldSource->name,
|
||||
$result->id, $result->name
|
||||
)
|
||||
);
|
||||
$this->inspectJournal($journal);
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
@@ -201,7 +219,7 @@ class FixAccountTypes extends Command
|
||||
{
|
||||
$count = $journal->transactions()->count();
|
||||
if (2 !== $count) {
|
||||
$this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transactions instead of 2.', $journal->id, $count));
|
||||
$this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $count));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -213,9 +231,11 @@ class FixAccountTypes extends Command
|
||||
$destAccount = $destTransaction->account;
|
||||
$destAccountType = $destAccount->accountType->type;
|
||||
if (!isset($this->expected[$type])) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->info(sprintf('No source/destination info for transaction type %s.', $type));
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
if (!isset($this->expected[$type][$sourceAccountType])) {
|
||||
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
|
||||
|
||||
@@ -46,6 +46,9 @@ class FixPiggies extends Command
|
||||
*/
|
||||
protected $signature = 'firefly-iii:fix-piggies';
|
||||
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -53,29 +56,43 @@ class FixPiggies extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
$set->each(
|
||||
function (PiggyBankEvent $event) {
|
||||
if (null === $event->transaction_journal_id) {
|
||||
return true;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (null === $journal) {
|
||||
return true;
|
||||
}
|
||||
$this->count = 0;
|
||||
$start = microtime(true);
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if (TransactionType::TRANSFER !== $type) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
}
|
||||
/** @var PiggyBankEvent $event */
|
||||
foreach ($set as $event) {
|
||||
|
||||
return true;
|
||||
if (null === $event->transaction_journal_id) {
|
||||
continue;
|
||||
}
|
||||
);
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal;
|
||||
// @codeCoverageIgnoreStart
|
||||
if (null === $journal) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->count++;
|
||||
continue;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if (TransactionType::TRANSFER !== $type) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
$this->count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (0 === $this->count) {
|
||||
$this->line('All piggy bank events are correct.');
|
||||
}
|
||||
if (0 !== $this->count) {
|
||||
$this->line(sprintf('Fixed %d piggy bank event(s).', $this->count));
|
||||
}
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end));
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -84,7 +85,7 @@ class FixUnevenAmount extends Command
|
||||
// one of the transactions is bad.
|
||||
$journal = TransactionJournal::find($param);
|
||||
if (!$journal) {
|
||||
return;
|
||||
return; // @codeCoverageIgnore
|
||||
}
|
||||
/** @var Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
@@ -92,11 +93,11 @@ class FixUnevenAmount extends Command
|
||||
|
||||
// fix amount of destination:
|
||||
/** @var Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$destination->amount = $amount;
|
||||
$destination->save();
|
||||
|
||||
$this->line(sprintf('Corrected amount in transaction journal #%d', $param));
|
||||
|
||||
$message = sprintf('Corrected amount in transaction journal #%d', $param);
|
||||
$this->line($message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ class RenameMetaFields extends Command
|
||||
*/
|
||||
protected $signature = 'firefly-iii:rename-meta-fields';
|
||||
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -49,7 +52,8 @@ class RenameMetaFields extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
$this->count = 0;
|
||||
$start = microtime(true);
|
||||
|
||||
$changes = [
|
||||
'original-source' => 'original_source',
|
||||
@@ -67,6 +71,12 @@ class RenameMetaFields extends Command
|
||||
foreach ($changes as $original => $update) {
|
||||
$this->rename($original, $update);
|
||||
}
|
||||
if (0 === $this->count) {
|
||||
$this->line('All meta fields are correct.');
|
||||
}
|
||||
if (0 !== $this->count) {
|
||||
$this->line(sprintf('Renamed %d meta field(s).', $this->count));
|
||||
}
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Renamed meta fields in %s seconds', $end));
|
||||
@@ -80,8 +90,9 @@ class RenameMetaFields extends Command
|
||||
*/
|
||||
private function rename(string $original, string $update): void
|
||||
{
|
||||
DB::table('journal_meta')
|
||||
->where('name', '=', $original)
|
||||
->update(['name' => $update]);
|
||||
$count = DB::table('journal_meta')
|
||||
->where('name', '=', $original)
|
||||
->update(['name' => $update]);
|
||||
$this->count += $count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ class TransferBudgets extends Command
|
||||
if (0 === $count) {
|
||||
$this->info('No invalid budget/journal entries.');
|
||||
}
|
||||
if(0 !== $count) {
|
||||
$this->line(sprintf('Corrected %d invalid budget/journal entries (entry).', $count));
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified budget/journals in %s seconds.', $end));
|
||||
|
||||
|
||||
@@ -49,15 +49,15 @@ class DecryptDatabase extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly:decrypt-all';
|
||||
protected $signature = 'firefly-iii:decrypt-all';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): int
|
||||
{
|
||||
$this->line('Going to decrypt the database.');
|
||||
$tables = [
|
||||
@@ -151,7 +151,7 @@ class DecryptDatabase extends Command
|
||||
$value = Crypt::decrypt($value);
|
||||
} catch (DecryptException $e) {
|
||||
if ('The MAC is invalid.' === $e->getMessage()) {
|
||||
throw new FireflyException($e->getMessage());
|
||||
throw new FireflyException($e->getMessage()); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Could not decrypt. %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
@@ -28,18 +28,17 @@ namespace FireflyIII\Console\Commands\Import;
|
||||
use Exception;
|
||||
use FireflyIII\Console\Commands\VerifiesAccessToken;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Prerequisites\PrerequisitesInterface;
|
||||
use FireflyIII\Import\Routine\RoutineInterface;
|
||||
use FireflyIII\Import\Storage\ImportArrayStorage;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class CreateCSVImport.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class CreateCSVImport extends Command
|
||||
{
|
||||
@@ -50,7 +49,6 @@ class CreateCSVImport extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Use this command to create a new CSV file import.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
@@ -62,169 +60,96 @@ class CreateCSVImport extends Command
|
||||
{configuration? : The configuration file to use for the import.}
|
||||
{--user=1 : The user ID that the import should import for.}
|
||||
{--token= : The user\'s access token.}';
|
||||
/** @var UserRepositoryInterface */
|
||||
private $userRepository;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $importRepository;
|
||||
/** @var ImportJob */
|
||||
private $importJob;
|
||||
|
||||
/**
|
||||
* CreateCSVImport constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->userRepository = app(UserRepositoryInterface::class);
|
||||
$this->importRepository = app(ImportJobRepositoryInterface::class);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!$this->verifyAccessToken()) {
|
||||
$this->errorLine('Invalid access token.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
/** @var UserRepositoryInterface $userRepository */
|
||||
$userRepository = app(UserRepositoryInterface::class);
|
||||
$file = (string)$this->argument('file');
|
||||
$configuration = (string)$this->argument('configuration');
|
||||
$user = $userRepository->findNull((int)$this->option('user'));
|
||||
$cwd = getcwd();
|
||||
$configurationData = [];
|
||||
|
||||
if (null === $user) {
|
||||
$this->errorLine('User is NULL.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!$this->validArguments()) {
|
||||
$this->errorLine('Invalid arguments.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
if ('' !== $configuration) {
|
||||
$configurationData = json_decode(file_get_contents($configuration), true);
|
||||
if (null === $configurationData) {
|
||||
$this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
// @codeCoverageIgnoreEnd
|
||||
/** @var User $user */
|
||||
$user = $this->userRepository->findNull((int)$this->option('user'));
|
||||
$file = (string)$this->argument('file');
|
||||
$configuration = (string)$this->argument('configuration');
|
||||
|
||||
return 1;
|
||||
}
|
||||
$this->importRepository->setUser($user);
|
||||
|
||||
$configurationData = json_decode(file_get_contents($configuration), true);
|
||||
$this->importJob = $this->importRepository->create('file');
|
||||
|
||||
|
||||
// inform user (and log it)
|
||||
$this->infoLine(sprintf('Import file : %s', $file));
|
||||
$this->infoLine(sprintf('Configuration file : %s', $configuration));
|
||||
$this->infoLine(sprintf('User : #%d (%s)', $user->id, $user->email));
|
||||
$this->infoLine(sprintf('Job : %s', $this->importJob->key));
|
||||
|
||||
try {
|
||||
$this->storeFile($file);
|
||||
} catch (FireflyException $e) {
|
||||
$this->errorLine($e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
$this->infoLine(sprintf('Going to create a job to import file: %s', $file));
|
||||
$this->infoLine(sprintf('Using configuration file: %s', $configuration));
|
||||
$this->infoLine(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
|
||||
|
||||
/** @var ImportJobRepositoryInterface $jobRepository */
|
||||
$jobRepository = app(ImportJobRepositoryInterface::class);
|
||||
$jobRepository->setUser($user);
|
||||
$importJob = $jobRepository->create('file');
|
||||
$this->infoLine(sprintf('Created job "%s"', $importJob->key));
|
||||
|
||||
// make sure that job has no prerequisites.
|
||||
if ((bool)config('import.has_prereq.csv')) {
|
||||
// make prerequisites thing.
|
||||
$class = (string)config('import.prerequisites.csv');
|
||||
if (!class_exists($class)) {
|
||||
throw new FireflyException('No class to handle prerequisites for CSV.'); // @codeCoverageIgnore
|
||||
}
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser($user);
|
||||
if (!$object->isComplete()) {
|
||||
$this->errorLine('CSV Import provider has prerequisites that can only be filled in using the browser.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// store file as attachment.
|
||||
if ('' !== $file) {
|
||||
$messages = $jobRepository->storeCLIUpload($importJob, 'import_file', $file);
|
||||
if ($messages->count() > 0) {
|
||||
$this->errorLine($messages->first());
|
||||
|
||||
return 1;
|
||||
}
|
||||
$this->infoLine('File content saved.');
|
||||
}
|
||||
|
||||
$this->infoLine('Job configuration saved.');
|
||||
$jobRepository->setConfiguration($importJob, $configurationData);
|
||||
$jobRepository->setStatus($importJob, 'ready_to_run');
|
||||
|
||||
// job is ready to go
|
||||
$this->importRepository->setConfiguration($this->importJob, $configurationData);
|
||||
$this->importRepository->setStatus($this->importJob, 'ready_to_run');
|
||||
|
||||
$this->infoLine('The import routine has started. The process is not visible. Please wait.');
|
||||
Log::debug('Go for import!');
|
||||
|
||||
// run it!
|
||||
$className = config('import.routine.file');
|
||||
if (null === $className || !class_exists($className)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->errorLine('No routine for file provider.');
|
||||
|
||||
return 1;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// keep repeating this call until job lands on "provider_finished"
|
||||
$valid = ['provider_finished'];
|
||||
$count = 0;
|
||||
while (!in_array($importJob->status, $valid, true) && $count < 6) {
|
||||
Log::debug(sprintf('Now in loop #%d.', $count + 1));
|
||||
/** @var RoutineInterface $routine */
|
||||
$routine = app($className);
|
||||
$routine->setImportJob($importJob);
|
||||
try {
|
||||
$routine->run();
|
||||
} catch (FireflyException|Exception $e) {
|
||||
$message = 'The import routine crashed: ' . $e->getMessage();
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
try {
|
||||
$this->processFile();
|
||||
} catch (FireflyException $e) {
|
||||
$this->errorLine($e->getMessage());
|
||||
|
||||
// set job errored out:
|
||||
$jobRepository->setStatus($importJob, 'error');
|
||||
$this->errorLine($message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
$count++;
|
||||
return 1;
|
||||
}
|
||||
if ('provider_finished' === $importJob->status) {
|
||||
$this->infoLine('Import has finished. Please wait for storage of data.');
|
||||
// set job to be storing data:
|
||||
$jobRepository->setStatus($importJob, 'storing_data');
|
||||
|
||||
/** @var ImportArrayStorage $storage */
|
||||
$storage = app(ImportArrayStorage::class);
|
||||
$storage->setImportJob($importJob);
|
||||
// then store data:
|
||||
try {
|
||||
$this->storeData();
|
||||
} catch (FireflyException $e) {
|
||||
$this->errorLine($e->getMessage());
|
||||
|
||||
try {
|
||||
$storage->store();
|
||||
} catch (FireflyException|Exception $e) {
|
||||
$message = 'The import routine crashed: ' . $e->getMessage();
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
// set job errored out:
|
||||
$jobRepository->setStatus($importJob, 'error');
|
||||
$this->errorLine($message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
// set storage to be finished:
|
||||
$jobRepository->setStatus($importJob, 'storage_finished');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// give feedback:
|
||||
$this->infoLine('Job has finished.');
|
||||
if (null !== $importJob->tag) {
|
||||
$this->infoLine(sprintf('%d transaction(s) have been imported.', $importJob->tag->transactionJournals->count()));
|
||||
$this->infoLine(sprintf('You can find your transactions under tag "%s"', $importJob->tag->tag));
|
||||
}
|
||||
$this->giveFeedback();
|
||||
|
||||
if (null === $importJob->tag) {
|
||||
$this->errorLine('No transactions have been imported :(.');
|
||||
}
|
||||
if (count($importJob->errors) > 0) {
|
||||
$this->infoLine(sprintf('%d error(s) occurred:', count($importJob->errors)));
|
||||
foreach ($importJob->errors as $err) {
|
||||
$this->errorLine('- ' . $err);
|
||||
}
|
||||
}
|
||||
// clear cache for user:
|
||||
app('preferences')->setForUser($user, 'lastActivity', microtime());
|
||||
|
||||
@@ -234,6 +159,7 @@ class CreateCSVImport extends Command
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array|null $data
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function errorLine(string $message, array $data = null): void
|
||||
{
|
||||
@@ -245,6 +171,7 @@ class CreateCSVImport extends Command
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $data
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function infoLine(string $message, array $data = null): void
|
||||
{
|
||||
@@ -257,6 +184,7 @@ class CreateCSVImport extends Command
|
||||
*
|
||||
* @noinspection MultipleReturnStatementsInspection
|
||||
* @return bool
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function validArguments(): bool
|
||||
{
|
||||
@@ -283,6 +211,119 @@ class CreateCSVImport extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
$configurationData = json_decode(file_get_contents($configuration), true);
|
||||
if (null === $configurationData) {
|
||||
$this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the supplied file as an attachment to this job.
|
||||
*
|
||||
* @param string $file
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function storeFile(string $file): void
|
||||
{
|
||||
// store file as attachment.
|
||||
if ('' !== $file) {
|
||||
$messages = $this->importRepository->storeCLIUpload($this->importJob, 'import_file', $file);
|
||||
if ($messages->count() > 0) {
|
||||
throw new FireflyException($messages->first());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep repeating import call until job lands on "provider_finished".
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processFile(): void
|
||||
{
|
||||
$className = config('import.routine.file');
|
||||
$valid = ['provider_finished'];
|
||||
$count = 0;
|
||||
|
||||
while (!in_array($this->importJob->status, $valid, true) && $count < 6) {
|
||||
Log::debug(sprintf('Now in loop #%d.', $count + 1));
|
||||
/** @var RoutineInterface $routine */
|
||||
$routine = app($className);
|
||||
$routine->setImportJob($this->importJob);
|
||||
try {
|
||||
$routine->run();
|
||||
} catch (FireflyException|Exception $e) {
|
||||
$message = 'The import routine crashed: ' . $e->getMessage();
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
// set job errored out:
|
||||
$this->importRepository->setStatus($this->importJob, 'error');
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
$this->importRepository->setStatus($this->importJob, 'provider_finished');
|
||||
$this->importJob->status = 'provider_finished';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function storeData(): void
|
||||
{
|
||||
if ('provider_finished' === $this->importJob->status) {
|
||||
$this->infoLine('Import has finished. Please wait for storage of data.');
|
||||
// set job to be storing data:
|
||||
$this->importRepository->setStatus($this->importJob, 'storing_data');
|
||||
|
||||
/** @var ImportArrayStorage $storage */
|
||||
$storage = app(ImportArrayStorage::class);
|
||||
$storage->setImportJob($this->importJob);
|
||||
|
||||
try {
|
||||
$storage->store();
|
||||
} catch (FireflyException|Exception $e) {
|
||||
$message = 'The import routine crashed: ' . $e->getMessage();
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
// set job errored out:
|
||||
$this->importRepository->setStatus($this->importJob, 'error');
|
||||
throw new FireflyException($message);
|
||||
|
||||
}
|
||||
// set storage to be finished:
|
||||
$this->importRepository->setStatus($this->importJob, 'storage_finished');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function giveFeedback(): void
|
||||
{
|
||||
$this->infoLine('Job has finished.');
|
||||
|
||||
|
||||
if (null !== $this->importJob->tag) {
|
||||
$this->infoLine(sprintf('%d transaction(s) have been imported.', $this->importJob->tag->transactionJournals->count()));
|
||||
$this->infoLine(sprintf('You can find your transactions under tag "%s"', $this->importJob->tag->tag));
|
||||
}
|
||||
|
||||
if (null === $this->importJob->tag) {
|
||||
$this->errorLine('No transactions have been imported :(.');
|
||||
}
|
||||
if (count($this->importJob->errors) > 0) {
|
||||
$this->infoLine(sprintf('%d error(s) occurred:', count($this->importJob->errors)));
|
||||
foreach ($this->importJob->errors as $err) {
|
||||
$this->errorLine('- ' . $err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,8 @@ class ReportEmptyObjects extends Command
|
||||
|
||||
/** @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);
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
@@ -125,13 +124,12 @@ class ReportEmptyObjects extends Command
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$objName = $entry->name;
|
||||
$line = sprintf(
|
||||
'User #%d (%s) has budget #%d ("%s") which has no transaction journals.',
|
||||
$entry->user_id,
|
||||
$entry->email,
|
||||
$entry->id,
|
||||
$objName
|
||||
$entry->name
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
@@ -151,14 +149,12 @@ class ReportEmptyObjects extends Command
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$objName = $entry->name;
|
||||
|
||||
$line = sprintf(
|
||||
'User #%d (%s) has category #%d ("%s") which has no transaction journals.',
|
||||
$entry->user_id,
|
||||
$entry->email,
|
||||
$entry->id,
|
||||
$objName
|
||||
$entry->name
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
@@ -178,14 +174,13 @@ class ReportEmptyObjects extends Command
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$objName = $entry->tag;
|
||||
|
||||
$line = sprintf(
|
||||
'User #%d (%s) has tag #%d ("%s") which has no transaction journals.',
|
||||
$entry->user_id,
|
||||
$entry->email,
|
||||
$entry->id,
|
||||
$objName
|
||||
$entry->tag
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ use Artisan;
|
||||
|
||||
/**
|
||||
* Class ReportIntegrity
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class ReportIntegrity extends Command
|
||||
{
|
||||
|
||||
@@ -75,8 +75,6 @@ class ApplyRules extends Command
|
||||
private $acceptedAccounts;
|
||||
/** @var Carbon */
|
||||
private $endDate;
|
||||
/** @var Collection */
|
||||
private $results;
|
||||
/** @var array */
|
||||
private $ruleGroupSelection;
|
||||
/** @var array */
|
||||
@@ -106,7 +104,6 @@ class ApplyRules extends Command
|
||||
$this->accounts = new Collection;
|
||||
$this->ruleSelection = [];
|
||||
$this->ruleGroupSelection = [];
|
||||
$this->results = new Collection;
|
||||
$this->ruleRepository = app(RuleRepositoryInterface::class);
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE];
|
||||
@@ -121,11 +118,14 @@ class ApplyRules extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!$this->verifyAccessToken()) {
|
||||
$this->error('Invalid access token.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// set user:
|
||||
$this->ruleRepository->setUser($this->getUser());
|
||||
$this->ruleGroupRepository->setUser($this->getUser());
|
||||
@@ -141,24 +141,8 @@ class ApplyRules extends Command
|
||||
$this->grabAllRules();
|
||||
|
||||
// loop all groups and rules and indicate if they're included:
|
||||
$count = 0;
|
||||
$rulesToApply = [];
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($this->groups as $group) {
|
||||
/** @var Rule $rule */
|
||||
foreach ($group->rules as $rule) {
|
||||
// if in rule selection, or group in selection or all rules, it's included.
|
||||
$test = $this->includeRule($rule, $group);
|
||||
if (true === $test) {
|
||||
Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
|
||||
$count++;
|
||||
$rulesToApply[] = $rule->id;
|
||||
}
|
||||
if (false === $test) {
|
||||
Log::debug(sprintf('Will not include rule #%d "%s"', $rule->id, $rule->title));
|
||||
}
|
||||
}
|
||||
}
|
||||
$rulesToApply = $this->getRulesToApply();
|
||||
$count = count($rulesToApply);
|
||||
if (0 === $count) {
|
||||
$this->error('No rules or rule groups have been included.');
|
||||
$this->warn('Make a selection using:');
|
||||
@@ -167,7 +151,6 @@ class ApplyRules extends Command
|
||||
$this->warn(' --all_rules');
|
||||
}
|
||||
|
||||
// get transactions from asset accounts.
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->getUser());
|
||||
@@ -176,7 +159,7 @@ class ApplyRules extends Command
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
// start running rules.
|
||||
$this->line(sprintf('Will apply %d rules to %d transactions.', $count, count($journals)));
|
||||
$this->line(sprintf('Will apply %d rule(s) to %d transaction(s).', $count, count($journals)));
|
||||
|
||||
// start looping.
|
||||
/** @var RuleEngine $ruleEngine */
|
||||
@@ -191,6 +174,7 @@ class ApplyRules extends Command
|
||||
Log::debug('Start of new journal.');
|
||||
$ruleEngine->processJournalArray($journal);
|
||||
Log::debug('Done with all rules for this group + done with journal.');
|
||||
/** @noinspection DisconnectedForeachInstructionInspection */
|
||||
$bar->advance();
|
||||
}
|
||||
$this->line('');
|
||||
@@ -212,16 +196,10 @@ class ApplyRules extends Command
|
||||
}
|
||||
|
||||
// verify rule groups.
|
||||
$result = $this->verifyInputRuleGroups();
|
||||
if (false === $result) {
|
||||
return $result;
|
||||
}
|
||||
$this->verifyInputRuleGroups();
|
||||
|
||||
// verify rules.
|
||||
$result = $this->verifyInputRules();
|
||||
if (false === $result) {
|
||||
return $result;
|
||||
}
|
||||
$this->verifyInputRules();
|
||||
|
||||
$this->verifyInputDates();
|
||||
|
||||
@@ -243,11 +221,13 @@ class ApplyRules extends Command
|
||||
$finalList = new Collection;
|
||||
$accountList = explode(',', $accountString);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (0 === count($accountList)) {
|
||||
$this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
|
||||
|
||||
return false;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
@@ -284,11 +264,12 @@ class ApplyRules extends Command
|
||||
return true;
|
||||
}
|
||||
$ruleGroupList = explode(',', $ruleGroupString);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (0 === count($ruleGroupList)) {
|
||||
// can be empty.
|
||||
return true;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
foreach ($ruleGroupList as $ruleGroupId) {
|
||||
$ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId);
|
||||
if ($ruleGroup->active) {
|
||||
@@ -314,11 +295,14 @@ class ApplyRules extends Command
|
||||
}
|
||||
$ruleList = explode(',', $ruleString);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (0 === count($ruleList)) {
|
||||
// can be empty.
|
||||
|
||||
return true;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
foreach ($ruleList as $ruleId) {
|
||||
$rule = $this->ruleRepository->find((int)$ruleId);
|
||||
if (null !== $rule && $rule->active) {
|
||||
@@ -383,4 +367,27 @@ class ApplyRules extends Command
|
||||
in_array($rule->id, $this->ruleSelection, true) ||
|
||||
$this->allRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getRulesToApply(): array
|
||||
{
|
||||
$rulesToApply = [];
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($this->groups as $group) {
|
||||
$rules = $this->ruleGroupRepository->getActiveStoreRules($group);
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
// if in rule selection, or group in selection or all rules, it's included.
|
||||
$test = $this->includeRule($rule, $group);
|
||||
if (true === $test) {
|
||||
Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
|
||||
$rulesToApply[] = $rule->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rulesToApply;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ namespace FireflyIII\Console\Commands\Upgrade;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Log;
|
||||
@@ -48,9 +50,23 @@ class AccountCurrencies extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}';
|
||||
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $repository;
|
||||
private $accountRepos;
|
||||
/** @var UserRepositoryInterface */
|
||||
private $userRepos;
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* AccountCurrencies constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
|
||||
@@ -59,8 +75,7 @@ class AccountCurrencies extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$start = microtime(true);
|
||||
$start = microtime(true);
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->warn('This command has already been executed.');
|
||||
|
||||
@@ -69,11 +84,17 @@ class AccountCurrencies extends Command
|
||||
Log::debug('Now in updateAccountCurrencies()');
|
||||
$this->updateAccountCurrencies();
|
||||
|
||||
if (0 === $this->count) {
|
||||
$this->line('All accounts are OK.');
|
||||
}
|
||||
if (0 !== $this->count) {
|
||||
$this->line(sprintf('Corrected %d account(s).', $this->count));
|
||||
}
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end));
|
||||
$this->markAsExecuted();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -100,41 +121,53 @@ class AccountCurrencies extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @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;
|
||||
$this->accountRepos->setUser($account->user);
|
||||
|
||||
$accountCurrency = (int)$this->accountRepos->getMetaValue($account, 'currency_id');
|
||||
$openingBalance = $this->accountRepos->getOpeningBalance($account);
|
||||
$obCurrency = 0;
|
||||
if (null !== $openingBalance) {
|
||||
$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));
|
||||
|
||||
$this->count++;
|
||||
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));
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (#%d).', $account->id, $account->name, $obCurrency));
|
||||
$this->count++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// do not match and opening balance id is not null.
|
||||
if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) {
|
||||
if ($accountCurrency !== $obCurrency && null !== $openingBalance) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$openingBalance->transactions->each(
|
||||
static function (Transaction $transaction) use ($accountCurrency) {
|
||||
$transaction->transaction_currency_id = $accountCurrency;
|
||||
$transaction->save();
|
||||
});
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
$this->count++;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,24 +176,21 @@ class AccountCurrencies extends Command
|
||||
*/
|
||||
private function updateAccountCurrencies(): void
|
||||
{
|
||||
|
||||
$users = $this->userRepos->all();
|
||||
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
|
||||
$users = User::get();
|
||||
foreach ($users as $user) {
|
||||
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @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.*']);
|
||||
$this->accountRepos->setUser($user);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
|
||||
// get user's currency preference:
|
||||
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data;
|
||||
|
||||
@@ -54,6 +54,7 @@ class BackToJournals extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$start = microtime(true);
|
||||
if (!$this->isMigrated()) {
|
||||
$this->error('Please run firefly-iii:migrate-to-groups first.');
|
||||
@@ -66,6 +67,7 @@ class BackToJournals extends Command
|
||||
if (true === $this->option('force')) {
|
||||
$this->warn('Forcing the command.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->migrateAll();
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
@@ -82,8 +84,9 @@ class BackToJournals extends Command
|
||||
{
|
||||
$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 DB::table('transactions')
|
||||
->whereIn('transactions.id', $transactions)
|
||||
->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,8 +96,9 @@ class BackToJournals extends Command
|
||||
{
|
||||
$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 DB::table('transactions')
|
||||
->whereIn('transactions.id', $transactions)
|
||||
->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,9 +153,10 @@ class BackToJournals extends Command
|
||||
*/
|
||||
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.',count($journals)));
|
||||
$this->line(sprintf('Check %d transaction journal(s) for budget info.', count($journals)));
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$this->migrateBudgetsForJournal($journal);
|
||||
@@ -163,26 +168,34 @@ class BackToJournals extends Command
|
||||
*/
|
||||
private function migrateBudgetsForJournal(TransactionJournal $journal): void
|
||||
{
|
||||
|
||||
// grab category from first transaction
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
if (null === $transaction) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
/** @var Budget $budget */
|
||||
$budget = $transaction->budgets->first();
|
||||
/** @var Budget $journalBudget */
|
||||
$journalBudget = $journal->budgets->first();
|
||||
|
||||
// both have a budget, but they don't match.
|
||||
if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) {
|
||||
// sync to journal:
|
||||
$journal->budgets()->sync([(int)$budget->id]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// budget in transaction overrules journal.
|
||||
if (null === $budget && null !== $journalBudget) {
|
||||
$journal->budgets()->sync([]);
|
||||
// transaction has a budget, but the journal doesn't.
|
||||
if (null !== $budget && null === $journalBudget) {
|
||||
// sync to journal:
|
||||
$journal->budgets()->sync([(int)$budget->id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +206,7 @@ class BackToJournals extends Command
|
||||
{
|
||||
$journalIds = $this->getIdsForCategories();
|
||||
$journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'categories', 'transactions.categories'])->get();
|
||||
$this->line(sprintf('Check %d transaction journals for category info.', count($journals)));
|
||||
$this->line(sprintf('Check %d transaction journal(s) for category info.', count($journals)));
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$this->migrateCategoriesForJournal($journal);
|
||||
@@ -209,22 +222,26 @@ class BackToJournals extends Command
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
if (null === $transaction) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
/** @var Category $category */
|
||||
$category = $transaction->categories->first();
|
||||
/** @var Category $journalCategory */
|
||||
$journalCategory = $journal->categories->first();
|
||||
|
||||
// both have a category, but they don't match.
|
||||
if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) {
|
||||
// sync to journal:
|
||||
$journal->categories()->sync([(int)$category->id]);
|
||||
}
|
||||
|
||||
// category in transaction overrules journal.
|
||||
if (null === $category && null !== $journalCategory) {
|
||||
$journal->categories()->sync([]);
|
||||
// transaction has a category, but the journal doesn't.
|
||||
if (null !== $category && null === $journalCategory) {
|
||||
$journal->categories()->sync([(int)$category->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,14 @@ class BudgetLimitCurrency extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->warn('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$count = 0;
|
||||
$budgetLimits = BudgetLimit::get();
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
|
||||
@@ -58,14 +58,19 @@ class CCLiabilities extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->warn('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$ccType = AccountType::where('type', AccountType::CREDITCARD)->first();
|
||||
$debtType = AccountType::where('type', AccountType::DEBT)->first();
|
||||
if (null === $ccType || null === $debtType) {
|
||||
$this->info('No incorrectly stored credit card liabilities.');
|
||||
return 0;
|
||||
}
|
||||
/** @var Collection $accounts */
|
||||
|
||||
@@ -62,6 +62,20 @@ class JournalCurrencies extends Command
|
||||
private $currencyRepos;
|
||||
/** @var JournalRepositoryInterface */
|
||||
private $journalRepos;
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/**
|
||||
* JournalCurrencies constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->count = 0;
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$this->journalRepos = app(JournalRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
@@ -71,20 +85,27 @@ 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);
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->warn('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->updateTransferCurrencies();
|
||||
$this->updateOtherJournalsCurrencies();
|
||||
$this->markAsExecuted();
|
||||
|
||||
if (0 === $this->count) {
|
||||
$this->line('All transactions are correct.');
|
||||
}
|
||||
if (0 !== $this->count) {
|
||||
$this->line(sprintf('Verified %d transaction(s) and journal(s).', $this->count));
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end));
|
||||
|
||||
@@ -137,7 +158,8 @@ class JournalCurrencies extends Command
|
||||
private function getFirstAssetTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
$result = $journal->transactions->first(
|
||||
function (Transaction $transaction) {
|
||||
static function (Transaction $transaction) {
|
||||
// type can also be liability.
|
||||
return AccountType::ASSET === $transaction->account->accountType->type;
|
||||
}
|
||||
);
|
||||
@@ -180,7 +202,7 @@ class JournalCurrencies extends Command
|
||||
* This method makes sure that the transaction journal uses the currency given in the transaction.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
* @param Transaction $transaction
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
private function updateJournalCurrency(TransactionJournal $journal, Transaction $transaction): void
|
||||
{
|
||||
@@ -192,6 +214,7 @@ class JournalCurrencies extends Command
|
||||
}
|
||||
|
||||
if (!((int)$currency->id === (int)$journal->transaction_currency_id)) {
|
||||
$this->count++;
|
||||
$this->line(
|
||||
sprintf(
|
||||
'Transfer #%d ("%s") has been updated to use %s instead of %s.',
|
||||
@@ -223,10 +246,11 @@ class JournalCurrencies extends Command
|
||||
}
|
||||
|
||||
$journal->transactions->each(
|
||||
function (Transaction $transaction) use ($currency) {
|
||||
static function (Transaction $transaction) use ($currency) {
|
||||
if (null === $transaction->transaction_currency_id) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
$this->count++;
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
@@ -235,11 +259,13 @@ class JournalCurrencies extends Command
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
$this->count++;
|
||||
}
|
||||
}
|
||||
);
|
||||
// also update the journal, of course:
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$this->count++;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
@@ -255,11 +281,15 @@ class JournalCurrencies extends Command
|
||||
*/
|
||||
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.*']);
|
||||
$set =
|
||||
$this->journalRepos->getAllJournals(
|
||||
[
|
||||
TransactionType::WITHDRAWAL,
|
||||
TransactionType::DEPOSIT,
|
||||
TransactionType::OPENING_BALANCE,
|
||||
TransactionType::RECONCILIATION,
|
||||
]
|
||||
);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
@@ -292,46 +322,54 @@ class JournalCurrencies extends Command
|
||||
$this->journalRepos->setUser($user);
|
||||
$this->currencyRepos->setUser($user);
|
||||
|
||||
$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));
|
||||
$sourceCurrency = $this->getCurrency($sourceAccount);
|
||||
$destCurrency = $this->getCurrency($destAccount);
|
||||
if (null === $sourceCurrency) {
|
||||
$message = sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name);
|
||||
Log::error($message);
|
||||
$this->error($message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// has no currency ID? Must have, so fill in using account preference:
|
||||
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->transaction_currency_id = (int)$sourceCurrency->id;
|
||||
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $source->id, $sourceCurrency->code);
|
||||
Log::debug($message);
|
||||
$this->line($message);
|
||||
$this->count++;
|
||||
$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)$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.',
|
||||
$source->id,
|
||||
$source->transaction_currency_id,
|
||||
$sourceAccountCurrency->id,
|
||||
$source->amount
|
||||
)
|
||||
if (!((int)$source->transaction_currency_id === (int)$sourceCurrency->id) && null === $source->foreign_amount) {
|
||||
$message = sprintf(
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$source->id,
|
||||
$source->transaction_currency_id,
|
||||
$sourceCurrency->id,
|
||||
$source->amount
|
||||
);
|
||||
$source->transaction_currency_id = (int)$sourceAccountCurrency->id;
|
||||
Log::debug($message);
|
||||
$this->line($message);
|
||||
$this->count++;
|
||||
$source->transaction_currency_id = (int)$sourceCurrency->id;
|
||||
$source->save();
|
||||
}
|
||||
|
||||
|
||||
if (null === $destAccountCurrency) {
|
||||
Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name));
|
||||
if (null === $destCurrency) {
|
||||
$message = sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name);
|
||||
Log::error($message);
|
||||
$this->line($message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
|
||||
if ((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id) {
|
||||
if ((int)$destCurrency->id === (int)$sourceCurrency->id) {
|
||||
// update both transactions to match:
|
||||
$source->foreign_amount = null;
|
||||
$source->foreign_currency_id = null;
|
||||
@@ -343,22 +381,24 @@ class JournalCurrencies extends Command
|
||||
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.',
|
||||
$destAccount->name, $destAccountCurrency->code,
|
||||
$sourceAccount->name, $sourceAccountCurrency->code,
|
||||
$destAccount->name, $destCurrency->code,
|
||||
$sourceAccount->name, $sourceCurrency->code,
|
||||
$journal->transactionType->type, $journal->id,
|
||||
$source->id, $destination->id, $sourceAccountCurrency->code
|
||||
$source->id, $destination->id, $sourceCurrency->code
|
||||
)
|
||||
);
|
||||
$this->count++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if destination account currency is different, both transactions must have this currency as foreign currency id.
|
||||
if (!((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id)) {
|
||||
$source->foreign_currency_id = $destAccountCurrency->id;
|
||||
$destination->foreign_currency_id = $destAccountCurrency->id;
|
||||
if (!((int)$destCurrency->id === (int)$sourceCurrency->id)) {
|
||||
$source->foreign_currency_id = $destCurrency->id;
|
||||
$destination->foreign_currency_id = $destCurrency->id;
|
||||
$source->save();
|
||||
$destination->save();
|
||||
$this->count++;
|
||||
Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $source->id, $destination->id));
|
||||
}
|
||||
|
||||
@@ -366,6 +406,7 @@ class JournalCurrencies extends Command
|
||||
if (null === $source->foreign_amount && null !== $destination->foreign_amount) {
|
||||
$source->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
|
||||
$source->save();
|
||||
$this->count++;
|
||||
Log::debug(sprintf('Restored foreign amount of source transaction (1) #%d to %s', $source->id, $source->foreign_amount));
|
||||
}
|
||||
|
||||
@@ -373,6 +414,7 @@ class JournalCurrencies extends Command
|
||||
if (null === $destination->foreign_amount && null !== $destination->foreign_amount) {
|
||||
$destination->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
|
||||
$destination->save();
|
||||
$this->count++;
|
||||
Log::debug(sprintf('Restored foreign amount of destination transaction (2) #%d to %s', $destination->id, $destination->foreign_amount));
|
||||
}
|
||||
|
||||
@@ -385,6 +427,7 @@ class JournalCurrencies extends Command
|
||||
$destination->foreign_amount = $destination->amount;
|
||||
$source->save();
|
||||
$destination->save();
|
||||
$this->count++;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -396,6 +439,7 @@ class JournalCurrencies extends Command
|
||||
$foreignAmount
|
||||
)
|
||||
);
|
||||
$this->count++;
|
||||
$source->foreign_amount = bcmul($foreignPositive, '-1');
|
||||
$destination->foreign_amount = $foreignPositive;
|
||||
$source->save();
|
||||
@@ -415,12 +459,7 @@ class JournalCurrencies extends Command
|
||||
*/
|
||||
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.*']);
|
||||
|
||||
$set = $this->journalRepos->getAllJournals([TransactionType::TRANSFER]);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$this->updateTransferCurrency($journal);
|
||||
@@ -435,6 +474,7 @@ class JournalCurrencies extends Command
|
||||
$sourceTransaction = $this->getSourceTransaction($transfer);
|
||||
$destTransaction = $this->getDestinationTransaction($transfer);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (null === $sourceTransaction) {
|
||||
$this->info(sprintf('Source transaction for journal #%d is null.', $transfer->id));
|
||||
|
||||
@@ -445,6 +485,7 @@ class JournalCurrencies extends Command
|
||||
|
||||
return;
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->updateTransactionCurrency($transfer, $sourceTransaction, $destTransaction);
|
||||
$this->updateJournalCurrency($transfer, $sourceTransaction);
|
||||
|
||||
Reference in New Issue
Block a user