Merge branch 'release/v6.0.10'

This commit is contained in:
James Cole 2023-05-13 06:34:37 +02:00
commit 6d0906c37b
93 changed files with 2429 additions and 1906 deletions

View File

@ -677,16 +677,16 @@
},
{
"name": "sebastian/diff",
"version": "5.0.1",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02"
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02",
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"shasum": ""
},
"require": {
@ -732,7 +732,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.1"
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.3"
},
"funding": [
{
@ -740,20 +740,20 @@
"type": "github"
}
],
"time": "2023-03-23T05:12:41+00:00"
"time": "2023-05-01T07:48:21+00:00"
},
{
"name": "symfony/console",
"version": "v6.2.8",
"version": "v6.2.10",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b"
"reference": "12288d9f4500f84a4d02254d4aa968b15488476f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b",
"reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b",
"url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f",
"reference": "12288d9f4500f84a4d02254d4aa968b15488476f",
"shasum": ""
},
"require": {
@ -820,7 +820,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.2.8"
"source": "https://github.com/symfony/console/tree/v6.2.10"
},
"funding": [
{
@ -836,7 +836,7 @@
"type": "tidelift"
}
],
"time": "2023-03-29T21:42:15+00:00"
"time": "2023-04-28T13:37:43+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -1069,16 +1069,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.2.7",
"version": "v6.2.10",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "82b6c62b959f642d000456f08c6d219d749215b3"
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3",
"reference": "82b6c62b959f642d000456f08c6d219d749215b3",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"shasum": ""
},
"require": {
@ -1112,7 +1112,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.7"
"source": "https://github.com/symfony/filesystem/tree/v6.2.10"
},
"funding": [
{
@ -1128,7 +1128,7 @@
"type": "tidelift"
}
],
"time": "2023-02-14T08:44:56+00:00"
"time": "2023-04-18T13:46:08+00:00"
},
{
"name": "symfony/finder",
@ -1755,16 +1755,16 @@
},
{
"name": "symfony/process",
"version": "v6.2.8",
"version": "v6.2.10",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "75ed64103df4f6615e15a7fe38b8111099f47416"
"reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416",
"reference": "75ed64103df4f6615e15a7fe38b8111099f47416",
"url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e",
"reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e",
"shasum": ""
},
"require": {
@ -1796,7 +1796,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v6.2.8"
"source": "https://github.com/symfony/process/tree/v6.2.10"
},
"funding": [
{
@ -1812,7 +1812,7 @@
"type": "tidelift"
}
],
"time": "2023-03-09T16:20:02+00:00"
"time": "2023-04-18T13:56:57+00:00"
},
{
"name": "symfony/service-contracts",

View File

@ -177,25 +177,6 @@ class CorrectAmounts extends Command
$this->line(sprintf('Corrected %d currency exchange rate(s).', $count));
}
/**
* @return void
*/
private function fixRepetitions(): void
{
$set = PiggyBankRepetition::where('currentamount', '<', 0)->get();
$count = $set->count();
if (0 === $count) {
$this->info('Correct: All piggy bank repetition amounts are positive.');
return;
}
/** @var PiggyBankRepetition $item */
foreach ($set as $item) {
$item->currentamount = app('steam')->positive((string)$item->currentamount);
$item->save();
}
$this->line(sprintf('Corrected %d piggy bank repetition amount(s).', $count));
}
/**
* @return void
*/
@ -237,6 +218,25 @@ class CorrectAmounts extends Command
$this->line(sprintf('Corrected %d recurring transaction amount(s).', $count));
}
/**
* @return void
*/
private function fixRepetitions(): void
{
$set = PiggyBankRepetition::where('currentamount', '<', 0)->get();
$count = $set->count();
if (0 === $count) {
$this->info('Correct: All piggy bank repetition amounts are positive.');
return;
}
/** @var PiggyBankRepetition $item */
foreach ($set as $item) {
$item->currentamount = app('steam')->positive((string)$item->currentamount);
$item->save();
}
$this->line(sprintf('Corrected %d piggy bank repetition amount(s).', $count));
}
/**
* @return void
*/

View File

@ -83,7 +83,7 @@ class CorrectDatabase extends Command
'firefly-iii:fix-frontpage-accounts',
// new!
'firefly-iii:unify-group-accounts',
'firefly-iii:trigger-credit-recalculation'
'firefly-iii:trigger-credit-recalculation',
];
foreach ($commands as $command) {
$this->line(sprintf('Now executing command "%s"', $command));

View File

@ -33,8 +33,8 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Console\Command;
use JsonException;
use Illuminate\Support\Facades\Log;
use JsonException;
/**
* Class CorrectOpeningBalanceCurrencies

View File

@ -61,6 +61,33 @@ class DeleteEmptyJournals extends Command
return 0;
}
private function deleteEmptyJournals(): void
{
$start = microtime(true);
$count = 0;
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->groupBy('transaction_journals.id')
->whereNull('transactions.transaction_journal_id')
->get(['transaction_journals.id']);
foreach ($set as $entry) {
try {
TransactionJournal::find($entry->id)->delete();
} catch (QueryException $e) {
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
}
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
++$count;
}
if (0 === $count) {
$this->info('No empty transaction journals.');
}
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified empty journals in %s seconds', $end));
}
/**
* Delete transactions and their journals if they have an uneven number of transactions.
*/
@ -91,31 +118,4 @@ class DeleteEmptyJournals extends Command
$this->info('No uneven transaction journals.');
}
}
private function deleteEmptyJournals(): void
{
$start = microtime(true);
$count = 0;
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->groupBy('transaction_journals.id')
->whereNull('transactions.transaction_journal_id')
->get(['transaction_journals.id']);
foreach ($set as $entry) {
try {
TransactionJournal::find($entry->id)->delete();
} catch (QueryException $e) {
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
}
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
++$count;
}
if (0 === $count) {
$this->info('No empty transaction journals.');
}
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified empty journals in %s seconds', $end));
}
}

View File

@ -27,7 +27,6 @@ use Exception;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use stdClass;
/**
@ -66,6 +65,38 @@ class DeleteOrphanedTransactions extends Command
return 0;
}
/**
*
*/
private function deleteFromOrphanedAccounts(): void
{
$set
= Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->whereNotNull('accounts.deleted_at')
->get(['transactions.*']);
$count = 0;
/** @var Transaction $transaction */
foreach ($set as $transaction) {
// delete journals
$journal = TransactionJournal::find((int)$transaction->transaction_journal_id);
if ($journal) {
$journal->delete();
}
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
)
);
$count++;
}
if (0 === $count) {
$this->info('No orphaned accounts.');
}
}
private function deleteOrphanedJournals(): void
{
$set = TransactionJournal::leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
@ -129,36 +160,4 @@ class DeleteOrphanedTransactions extends Command
$this->info('No orphaned transactions.');
}
}
/**
*
*/
private function deleteFromOrphanedAccounts(): void
{
$set
= Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->whereNotNull('accounts.deleted_at')
->get(['transactions.*']);
$count = 0;
/** @var Transaction $transaction */
foreach ($set as $transaction) {
// delete journals
$journal = TransactionJournal::find((int)$transaction->transaction_journal_id);
if ($journal) {
$journal->delete();
}
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
)
);
$count++;
}
if (0 === $count) {
$this->info('No orphaned accounts.');
}
}
}

View File

@ -69,27 +69,38 @@ class FixIbans extends Command
{
$set = [];
/** @var Account $account */
foreach($accounts as $account) {
foreach ($accounts as $account) {
$userId = (int)$account->user_id;
$set[$userId] = $set[$userId] ?? [];
$iban = (string)$account->iban;
if ('' === $iban) {
continue;
}
$type = $account->accountType->type;
if(in_array($type, [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)) {
if (in_array($type, [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)) {
$type = 'liabilities';
}
if(array_key_exists($iban, $set[$userId])) {
if (array_key_exists($iban, $set[$userId])) {
// iban already in use! two exceptions exist:
if(
if (
!(AccountType::EXPENSE === $set[$userId][$iban] && AccountType::REVENUE === $type) && // allowed combination
!(AccountType::REVENUE === $set[$userId][$iban] && AccountType::EXPENSE === $type) // also allowed combination.
) {
$this->line(sprintf('IBAN "%s" is used more than once and will be removed from %s #%d ("%s")', $iban, $account->accountType->type, $account->id, $account->name));
$this->line(
sprintf(
'IBAN "%s" is used more than once and will be removed from %s #%d ("%s")',
$iban,
$account->accountType->type,
$account->id,
$account->name
)
);
$account->iban = null;
$account->save();
}
}
if(!array_key_exists($iban, $set[$userId])) {
if (!array_key_exists($iban, $set[$userId])) {
$set[$userId][$iban] = $type;
}
}

View File

@ -70,19 +70,6 @@ class FixRecurringTransactions extends Command
return 0;
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->recurringRepos = app(RecurringRepositoryInterface::class);
$this->userRepos = app(UserRepositoryInterface::class);
}
/**
*
*/
@ -95,19 +82,6 @@ class FixRecurringTransactions extends Command
}
}
/**
* @param User $user
*/
private function processUser(User $user): void
{
$this->recurringRepos->setUser($user);
$recurrences = $this->recurringRepos->get();
/** @var Recurrence $recurrence */
foreach ($recurrences as $recurrence) {
$this->processRecurrence($recurrence);
}
}
/**
* @param Recurrence $recurrence
*/
@ -140,4 +114,30 @@ class FixRecurringTransactions extends Command
}
}
}
/**
* @param User $user
*/
private function processUser(User $user): void
{
$this->recurringRepos->setUser($user);
$recurrences = $this->recurringRepos->get();
/** @var Recurrence $recurrence */
foreach ($recurrences as $recurrence) {
$this->processRecurrence($recurrence);
}
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->recurringRepos = app(RecurringRepositoryInterface::class);
$this->userRepos = app(UserRepositoryInterface::class);
}
}

View File

@ -78,6 +78,19 @@ class FixTransactionTypes extends Command
return 0;
}
/**
* @param TransactionJournal $journal
* @param string $expectedType
*/
private function changeJournal(TransactionJournal $journal, string $expectedType): void
{
$type = TransactionType::whereType($expectedType)->first();
if (null !== $type) {
$journal->transaction_type_id = $type->id;
$journal->save();
}
}
/**
* Collect all transaction journals.
*
@ -125,36 +138,6 @@ class FixTransactionTypes extends Command
return false;
}
/**
* @param TransactionJournal $journal
*
* @return Account
* @throws FireflyException
*/
private function getSourceAccount(TransactionJournal $journal): Account
{
$collection = $journal->transactions->filter(
static function (Transaction $transaction) {
return $transaction->amount < 0;
}
);
if (0 === $collection->count()) {
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
}
if (1 !== $collection->count()) {
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
}
/** @var Transaction $transaction */
$transaction = $collection->first();
/** @var Account|null $account */
$account = $transaction->account;
if (null === $account) {
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
}
return $account;
}
/**
* @param TransactionJournal $journal
*
@ -187,14 +170,31 @@ class FixTransactionTypes extends Command
/**
* @param TransactionJournal $journal
* @param string $expectedType
*
* @return Account
* @throws FireflyException
*/
private function changeJournal(TransactionJournal $journal, string $expectedType): void
private function getSourceAccount(TransactionJournal $journal): Account
{
$type = TransactionType::whereType($expectedType)->first();
if (null !== $type) {
$journal->transaction_type_id = $type->id;
$journal->save();
$collection = $journal->transactions->filter(
static function (Transaction $transaction) {
return $transaction->amount < 0;
}
);
if (0 === $collection->count()) {
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
}
if (1 !== $collection->count()) {
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
}
/** @var Transaction $transaction */
$transaction = $collection->first();
/** @var Account|null $account */
$account = $transaction->account;
if (null === $account) {
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
}
return $account;
}
}

View File

@ -39,17 +39,6 @@ class TriggerCreditCalculation extends Command
return 0;
}
private function processAccounts(): void
{
$accounts = Account::leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
foreach ($accounts as $account) {
Log::debug(sprintf('Processing account #%d ("%s")', $account->id, $account->name));
$this->processAccount($account);
}
}
/**
* @param Account $account
* @return void
@ -61,4 +50,15 @@ class TriggerCreditCalculation extends Command
$object->setAccount($account);
$object->recalculate();
}
private function processAccounts(): void
{
$accounts = Account::leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
foreach ($accounts as $account) {
Log::debug(sprintf('Processing account #%d ("%s")', $account->id, $account->name));
$this->processAccount($account);
}
}
}

View File

@ -51,39 +51,6 @@ class CreateGroupMemberships extends Command
*/
protected $signature = 'firefly-iii:create-group-memberships';
/**
* Execute the console command.
*
* @return int
* @throws FireflyException
*/
public function handle(): int
{
$start = microtime(true);
$this->createGroupMemberships();
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Validated group memberships in %s seconds.', $end));
return 0;
}
/**
*
* @throws FireflyException
*/
private function createGroupMemberships(): void
{
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
Log::debug(sprintf('Manage group memberships for user #%d', $user->id));
self::createGroupMembership($user);
Log::debug(sprintf('Done with user #%d', $user->id));
}
}
/**
* TODO move to helper.
* @param User $user
@ -125,4 +92,37 @@ class CreateGroupMemberships extends Command
Log::debug(sprintf('User #%d now has main group.', $user->id));
}
/**
* Execute the console command.
*
* @return int
* @throws FireflyException
*/
public function handle(): int
{
$start = microtime(true);
$this->createGroupMemberships();
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Validated group memberships in %s seconds.', $end));
return 0;
}
/**
*
* @throws FireflyException
*/
private function createGroupMemberships(): void
{
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
Log::debug(sprintf('Manage group memberships for user #%d', $user->id));
self::createGroupMembership($user);
Log::debug(sprintf('Done with user #%d', $user->id));
}
}
}

View File

@ -67,6 +67,51 @@ class ReportEmptyObjects extends Command
return 0;
}
/**
* Reports on accounts with no transactions.
*/
private function reportAccounts(): void
{
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
->whereNull('transactions.account_id')
->get(
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
);
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
$this->line($line);
}
}
/**
* Reports on budgets with no budget limits (which makes them pointless).
*/
private function reportBudgetLimits(): void
{
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
->whereNull('budget_limits.id')
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
/** @var Budget $entry */
foreach ($set as $entry) {
$line = sprintf(
'User #%d (%s) has budget #%d ("%s") which has no budget limits.',
$entry->user_id,
$entry->email,
$entry->id,
$entry->name
);
$this->line($line);
}
}
/**
* Report on budgets with no transactions or journals.
*/
@ -141,49 +186,4 @@ class ReportEmptyObjects extends Command
$this->line($line);
}
}
/**
* Reports on accounts with no transactions.
*/
private function reportAccounts(): void
{
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
->whereNull('transactions.account_id')
->get(
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
);
/** @var stdClass $entry */
foreach ($set as $entry) {
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
$this->line($line);
}
}
/**
* Reports on budgets with no budget limits (which makes them pointless).
*/
private function reportBudgetLimits(): void
{
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
->whereNull('budget_limits.id')
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
/** @var Budget $entry */
foreach ($set as $entry) {
$line = sprintf(
'User #%d (%s) has budget #%d ("%s") which has no budget limits.',
$entry->user_id,
$entry->email,
$entry->id,
$entry->name
);
$this->line($line);
}
}
}

View File

@ -61,7 +61,7 @@ class ReportIntegrity extends Command
'firefly-iii:report-empty-objects',
'firefly-iii:report-sum',
'firefly-iii:restore-oauth-keys',
'firefly-iii:upgrade-group-information'
'firefly-iii:upgrade-group-information',
];
foreach ($commands as $command) {
$this->line(sprintf('Now executing %s', $command));

View File

@ -58,6 +58,38 @@ class RestoreOAuthKeys extends Command
return 0;
}
/**
*
*/
private function generateKeys(): void
{
OAuthKeys::generateKeys();
}
/**
* @return bool
*/
private function keysInDatabase(): bool
{
return OAuthKeys::keysInDatabase();
}
/**
* @return bool
*/
private function keysOnDrive(): bool
{
return OAuthKeys::hasKeyFiles();
}
/**
*
*/
private function restoreKeysFromDB(): bool
{
return OAuthKeys::restoreKeysFromDB();
}
/**
*
*/
@ -97,30 +129,6 @@ class RestoreOAuthKeys extends Command
$this->line('OAuth keys are OK');
}
/**
* @return bool
*/
private function keysInDatabase(): bool
{
return OAuthKeys::keysInDatabase();
}
/**
* @return bool
*/
private function keysOnDrive(): bool
{
return OAuthKeys::hasKeyFiles();
}
/**
*
*/
private function generateKeys(): void
{
OAuthKeys::generateKeys();
}
/**
*
*/
@ -128,12 +136,4 @@ class RestoreOAuthKeys extends Command
{
OAuthKeys::storeKeysInDB();
}
/**
*
*/
private function restoreKeysFromDB(): bool
{
return OAuthKeys::restoreKeysFromDB();
}
}

View File

@ -23,60 +23,53 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\System;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Class ForceDecimalSize
*
* This command was inspired by https://github.com/elliot-gh. It will check all amount fields
* and their values and correct them to the correct number of decimal places. This fixes issues where
* Firefly III would store 0.01 as 0.01000000000000000020816681711721685132943093776702880859375.
*/
class ForceDecimalSize extends Command
{
use VerifiesAccessToken;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:force-decimal-size
{--user=1 : The user ID.}
{--token= : The user\'s access token.}';
protected $description = 'This command resizes DECIMAL columns in MySQL or PostgreSQL and correct amounts (only MySQL).';
protected $signature = 'firefly-iii:force-decimal-size';
private string $cast;
private array $classes = [
'accounts' => Account::class,
'auto_budgets' => AutoBudget::class,
'available_budgets' => AvailableBudget::class,
'bills' => Bill::class,
'budget_limits' => BudgetLimit::class,
'piggy_bank_events' => PiggyBankEvent::class,
'piggy_bank_repetitions' => PiggyBankRepetition::class,
'piggy_banks' => PiggyBank::class,
'recurrences_transactions' => RecurrenceTransaction::class,
'transactions' => Transaction::class,
];
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command resizes DECIMAL columns in MySQL or PostgreSQL.';
/**
* Execute the console command.
* @throws FireflyException
*/
public function handle(): int
{
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return 1;
}
$this->error('Running this command is dangerous and can cause data loss.');
$this->error('Please do not continue.');
$question = $this->confirm('Do you want to continue?');
if (true === $question) {
$user = $this->getUser();
Log::channel('audit')->info(sprintf('User #%d ("%s") forced DECIMAL size.', $user->id, $user->email));
$this->updateDecimals();
return 0;
}
$this->line('Done!');
return 0;
}
private function updateDecimals(): void
{
$this->info('Going to force the size of DECIMAL columns. Please hold.');
$tables = [
private string $operator;
private string $regularExpression;
private array $tables = [
'accounts' => ['virtual_balance'],
'auto_budgets' => ['amount'],
'available_budgets' => ['amount'],
@ -90,15 +83,466 @@ class ForceDecimalSize extends Command
'recurrences_transactions' => ['amount', 'foreign_amount'],
'transactions' => ['amount', 'foreign_amount'],
];
/**
* Execute the console command.
*
* @throws FireflyException
*/
public function handle(): int
{
Log::debug('Now in ForceDecimalSize::handle()');
$this->determineDatabaseType();
$this->error('Running this command is dangerous and can cause data loss.');
$this->error('Please do not continue.');
$question = $this->confirm('Do you want to continue?');
if (true === $question) {
$this->correctAmounts();
$this->updateDecimals();
}
$this->line('Done!');
return 0;
}
/**
* This method loops over all accounts and validates the amounts.
*
* @param TransactionCurrency $currency
* @param array $fields
* @return void
*/
private function correctAccountAmounts(TransactionCurrency $currency, array $fields): void
{
$operator = $this->operator;
$cast = $this->cast;
$regularExpression = $this->regularExpression;
/** @var Builder $query */
$query = Account::leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('account_meta.name', 'currency_id')
->where('account_meta.data', json_encode((string)$currency->id));
$query->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
foreach ($fields as $field) {
$q->orWhere(
DB::raw(sprintf('CAST(accounts.%s AS %s)', $field, $cast)),
$operator,
DB::raw(sprintf($regularExpression, $currency->decimal_places))
);
}
});
$result = $query->get(['accounts.*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All accounts in %s', $currency->code));
return;
}
/** @var Account $account */
foreach ($result as $account) {
foreach ($fields as $field) {
$value = $account->$field;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct));
Account::find($account->id)->update([$field => $correct]);
}
}
}
/**
* This method checks if a basic check can be done or if it needs to be complicated.
*
* @return void
*/
private function correctAmounts(): void
{
// if sqlite, add function?
if ('sqlite' === (string)config('database.default')) {
DB::connection()->getPdo()->sqliteCreateFunction('REGEXP', function ($pattern, $value) {
mb_regex_encoding('UTF-8');
$pattern = trim($pattern, '"');
return (false !== mb_ereg($pattern, (string) $value)) ? 1 : 0;
});
}
if (!in_array((string)config('database.default'), ['mysql', 'pgsql', 'sqlite'], true)) {
$this->line(sprintf('Skip correcting amounts, does not support "%s"...', (string)config('database.default')));
return;
}
$this->correctAmountsByCurrency();
}
/**
* This method loops all enabled currencies and then calls the method that will fix all objects in this currency.
*
* @return void
*/
private function correctAmountsByCurrency(): void
{
$this->line('Going to correct amounts.');
/** @var Collection $enabled */
$enabled = TransactionCurrency::whereEnabled(1)->get();
/** @var TransactionCurrency $currency */
foreach ($enabled as $currency) {
$this->correctByCurrency($currency);
}
}
/**
* This method loops the available tables that may need fixing, and calls for the right method that can fix them.
*
* @param TransactionCurrency $currency
* @return void
* @throws FireflyException
*/
private function correctByCurrency(TransactionCurrency $currency): void
{
$this->line(sprintf('Going to correct amounts in currency %s ("%s").', $currency->code, $currency->name));
/**
* @var string $name
* @var array $fields
*/
foreach($tables as $name => $fields) {
foreach ($this->tables as $name => $fields) {
switch ($name) {
default:
$message = sprintf('Cannot handle table "%s"', $name);
$this->line($message);
throw new FireflyException($message);
case 'accounts':
$this->correctAccountAmounts($currency, $fields);
break;
case 'auto_budgets':
case 'available_budgets':
case 'bills':
case 'budget_limits':
case 'recurrences_transactions':
$this->correctGeneric($currency, $name);
break;
case 'currency_exchange_rates':
case 'limit_repetitions':
// do nothing
break;
case 'piggy_bank_events':
$this->correctPiggyEventAmounts($currency, $fields);
break;
case 'piggy_bank_repetitions':
$this->correctPiggyRepetitionAmounts($currency, $fields);
break;
case 'piggy_banks':
$this->correctPiggyAmounts($currency, $fields);
break;
case 'transactions':
$this->correctTransactionAmounts($currency);
break;
}
}
}
/**
* This method fixes all auto budgets in currency $currency.
* @param TransactionCurrency $currency
* @param string $table
* @return void
*/
private function correctGeneric(TransactionCurrency $currency, string $table): void
{
$class = $this->classes[$table];
$fields = $this->tables[$table];
$operator = $this->operator;
$cast = $this->cast;
$regularExpression = $this->regularExpression;
/** @var Builder $query */
$query = $class::where('transaction_currency_id', $currency->id)->where(
static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
foreach ($fields as $field) {
$q->orWhere(
DB::raw(sprintf('CAST(%s AS %s)', $field, $cast)),
$operator,
DB::raw(sprintf($regularExpression, $currency->decimal_places))
);
}
}
);
$result = $query->get(['*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All %s in %s', $table, $currency->code));
return;
}
/** @var Model $item */
foreach ($result as $item) {
foreach ($fields as $field) {
$value = $item->$field;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('%s #%d has %s with value "%s", this has been corrected to "%s".', $table, $item->id, $field, $value, $correct));
$class::find($item->id)->update([$field => $correct]);
}
}
}
/**
* This method fixes all piggy banks in currency $currency.
*
* @param TransactionCurrency $currency
* @param array $fields
* @return void
*/
private function correctPiggyAmounts(TransactionCurrency $currency, array $fields): void
{
$operator = $this->operator;
$cast = $this->cast;
$regularExpression = $this->regularExpression;
/** @var Builder $query */
$query = PiggyBank::leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('account_meta.name', 'currency_id')
->where('account_meta.data', json_encode((string)$currency->id))
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
foreach ($fields as $field) {
$q->orWhere(
DB::raw(sprintf('CAST(piggy_banks.%s AS %s)', $field, $cast)),
$operator,
DB::raw(sprintf($regularExpression, $currency->decimal_places))
);
}
});
$result = $query->get(['piggy_banks.*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All piggy banks in %s', $currency->code));
return;
}
/** @var PiggyBank $item */
foreach ($result as $item) {
foreach ($fields as $field) {
$value = $item->$field;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
PiggyBank::find($item->id)->update([$field => $correct]);
}
}
}
/**
* This method fixes all piggy bank events in currency $currency.
* @param TransactionCurrency $currency
* @param array $fields
* @return void
*/
private function correctPiggyEventAmounts(TransactionCurrency $currency, array $fields): void
{
$operator = $this->operator;
$cast = $this->cast;
$regularExpression = $this->regularExpression;
/** @var Builder $query */
$query = PiggyBankEvent::leftJoin('piggy_banks', 'piggy_bank_events.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('account_meta.name', 'currency_id')
->where('account_meta.data', json_encode((string)$currency->id))
->where(static function (Builder $q) use ($fields, $currency, $cast, $operator, $regularExpression) {
foreach ($fields as $field) {
$q->orWhere(
DB::raw(sprintf('CAST(piggy_bank_events.%s AS %s)', $field, $cast)),
$operator,
DB::raw(sprintf($regularExpression, $currency->decimal_places))
);
}
});
$result = $query->get(['piggy_bank_events.*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All piggy bank events in %s', $currency->code));
return;
}
/** @var PiggyBankEvent $item */
foreach ($result as $item) {
foreach ($fields as $field) {
$value = $item->$field;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Piggy bank event #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
PiggyBankEvent::find($item->id)->update([$field => $correct]);
}
}
}
/**
* This method fixes all piggy bank repetitions in currency $currency.
*
* @param TransactionCurrency $currency
* @param array $fields
* @return void
*/
private function correctPiggyRepetitionAmounts(TransactionCurrency $currency, array $fields)
{
$operator = $this->operator;
$cast = $this->cast;
$regularExpression = $this->regularExpression;
// select all piggy bank repetitions with this currency and issue.
/** @var Builder $query */
$query = PiggyBankRepetition::leftJoin('piggy_banks', 'piggy_bank_repetitions.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('account_meta.name', 'currency_id')
->where('account_meta.data', json_encode((string)$currency->id))
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
foreach ($fields as $field) {
$q->orWhere(
DB::raw(sprintf('CAST(piggy_bank_repetitions.%s AS %s)', $field, $cast)),
$operator,
DB::raw(sprintf($regularExpression, $currency->decimal_places))
);
}
});
$result = $query->get(['piggy_bank_repetitions.*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All piggy bank repetitions in %s', $currency->code));
return;
}
/** @var PiggyBankRepetition $item */
foreach ($result as $item) {
foreach ($fields as $field) {
$value = $item->$field;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Piggy bank repetition #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
PiggyBankRepetition::find($item->id)->update([$field => $correct]);
}
}
}
/**
* This method fixes all transactions in currency $currency.
*
* @param TransactionCurrency $currency
* @return void
*/
private function correctTransactionAmounts(TransactionCurrency $currency): void
{
// select all transactions with this currency and issue.
/** @var Builder $query */
$query = Transaction::where('transaction_currency_id', $currency->id)->where(
DB::raw(sprintf('CAST(amount as %s)', $this->cast)),
$this->operator,
DB::raw(sprintf($this->regularExpression, $currency->decimal_places))
);
$result = $query->get(['transactions.*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All transactions in %s', $currency->code));
}
/** @var Transaction $item */
foreach ($result as $item) {
$value = $item->amount;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Transaction #%d has amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct));
Transaction::find($item->id)->update(['amount' => $correct]);
}
// select all transactions with this FOREIGN currency and issue.
/** @var Builder $query */
$query = Transaction::where('foreign_currency_id', $currency->id)->where(
DB::raw(sprintf('CAST(foreign_amount as %s)', $this->cast)),
$this->operator,
DB::raw(sprintf($this->regularExpression, $currency->decimal_places))
);
$result = $query->get(['*']);
if (0 === $result->count()) {
$this->line(sprintf('Correct: All transactions in foreign currency %s', $currency->code));
return;
}
/** @var Transaction $item */
foreach ($result as $item) {
$value = $item->foreign_amount;
if (null === $value) {
continue;
}
// fix $field by rounding it down correctly.
$pow = pow(10, (int)$currency->decimal_places);
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->line(sprintf('Transaction #%d has foreign amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct));
Transaction::find($item->id)->update(['foreign_amount' => $correct]);
}
}
private function determineDatabaseType(): void
{
// switch stuff based on database connection:
$this->operator = 'REGEXP';
$this->regularExpression = '\'\\\\.[\\\\d]{%d}[1-9]+\'';
$this->cast = 'CHAR';
if ('pgsql' === config('database.default')) {
$this->operator = 'SIMILAR TO';
$this->regularExpression = '\'%%\.[\d]{%d}[1-9]+%%\'';
$this->cast = 'TEXT';
}
if ('sqlite' === config('database.default')) {
$this->regularExpression = '"\\.[\d]{%d}[1-9]+"';
}
}
/**
* @return void
*/
private function updateDecimals(): void
{
$this->info('Going to force the size of DECIMAL columns. Please hold.');
$type = (string)config('database.default');
/**
* @var string $name
* @var array $fields
*/
foreach ($this->tables as $name => $fields) {
/** @var string $field */
foreach($fields as $field) {
foreach ($fields as $field) {
$this->line(sprintf('Updating table "%s", field "%s"...', $name, $field));
switch ($type) {
default:
$this->error(sprintf('Cannot handle database type "%s".', $type));
return;
case 'pgsql':
$query = sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE DECIMAL(32,12);', $name, $field);
break;
case 'mysql':
$query = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s DECIMAL(32, 12);', $name, $field, $field);
break;
}
DB::select($query);
sleep(1);
}

View File

@ -35,6 +35,12 @@ class ForceMigration extends Command
{
use VerifiesAccessToken;
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command will force-run all database migrations.';
/**
* The name and signature of the console command.
*
@ -44,13 +50,6 @@ class ForceMigration extends Command
{--user=1 : The user ID.}
{--token= : The user\'s access token.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command will force-run all database migrations.';
/**
* Execute the console command.
* @throws FireflyException

View File

@ -66,52 +66,6 @@ class UpgradeFireflyInstructions extends Command
return 0;
}
/**
* Render upgrade instructions.
*/
private function updateInstructions(): void
{
/** @var string $version */
$version = config('firefly.version');
$config = config('upgrade.text.upgrade');
$text = '';
foreach (array_keys($config) as $compare) {
// if string starts with:
if (\str_starts_with($version, $compare)) {
$text = $config[$compare];
}
}
$this->showLine();
$this->boxed('');
if (null === $text || '' === $text) {
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
$this->boxedInfo('There are no extra upgrade instructions.');
$this->boxed('Firefly III should be ready for use.');
$this->boxed('');
$this->showLine();
return;
}
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
$this->boxedInfo($text);
$this->boxed('');
$this->showLine();
}
/**
* Show a line.
*/
private function showLine(): void
{
$line = '+';
$line .= str_repeat('-', 78);
$line .= '+';
$this->line($line);
}
/**
* Show a nice box.
*
@ -170,4 +124,49 @@ class UpgradeFireflyInstructions extends Command
$this->boxed('');
$this->showLine();
}
/**
* Show a line.
*/
private function showLine(): void
{
$line = '+';
$line .= str_repeat('-', 78);
$line .= '+';
$this->line($line);
}
/**
* Render upgrade instructions.
*/
private function updateInstructions(): void
{
/** @var string $version */
$version = config('firefly.version');
$config = config('upgrade.text.upgrade');
$text = '';
foreach (array_keys($config) as $compare) {
// if string starts with:
if (\str_starts_with($version, $compare)) {
$text = $config[$compare];
}
}
$this->showLine();
$this->boxed('');
if (null === $text || '' === $text) {
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
$this->boxedInfo('There are no extra upgrade instructions.');
$this->boxed('Firefly III should be ready for use.');
$this->boxed('');
$this->showLine();
return;
}
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
$this->boxedInfo($text);
$this->boxed('');
$this->showLine();
}
}

View File

@ -31,8 +31,8 @@ use FireflyIII\Support\Cronjobs\BillWarningCronjob;
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
use FireflyIII\Support\Cronjobs\RecurringCronjob;
use Illuminate\Console\Command;
use InvalidArgumentException;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@ -123,62 +123,6 @@ class Cron extends Command
return 0;
}
/**
* @param bool $force
* @param Carbon|null $date
*/
private function exchangeRatesCronJob(bool $force, ?Carbon $date): void
{
$exchangeRates = new ExchangeRatesCronjob();
$exchangeRates->setForce($force);
// set date in cron job:
if (null !== $date) {
$exchangeRates->setDate($date);
}
$exchangeRates->fire();
if ($exchangeRates->jobErrored) {
$this->error(sprintf('Error in "exchange rates" cron: %s', $exchangeRates->message));
}
if ($exchangeRates->jobFired) {
$this->line(sprintf('"Exchange rates" cron fired: %s', $exchangeRates->message));
}
if ($exchangeRates->jobSucceeded) {
$this->info(sprintf('"Exchange rates" cron ran with success: %s', $exchangeRates->message));
}
}
/**
* @param bool $force
* @param Carbon|null $date
*
* @throws ContainerExceptionInterface
* @throws FireflyException
* @throws NotFoundExceptionInterface
*/
private function recurringCronJob(bool $force, ?Carbon $date): void
{
$recurring = new RecurringCronjob();
$recurring->setForce($force);
// set date in cron job:
if (null !== $date) {
$recurring->setDate($date);
}
$recurring->fire();
if ($recurring->jobErrored) {
$this->error(sprintf('Error in "create recurring transactions" cron: %s', $recurring->message));
}
if ($recurring->jobFired) {
$this->line(sprintf('"Create recurring transactions" cron fired: %s', $recurring->message));
}
if ($recurring->jobSucceeded) {
$this->info(sprintf('"Create recurring transactions" cron ran with success: %s', $recurring->message));
}
}
/**
* @param bool $force
* @param Carbon|null $date
@ -234,4 +178,60 @@ class Cron extends Command
$this->info(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
}
}
/**
* @param bool $force
* @param Carbon|null $date
*/
private function exchangeRatesCronJob(bool $force, ?Carbon $date): void
{
$exchangeRates = new ExchangeRatesCronjob();
$exchangeRates->setForce($force);
// set date in cron job:
if (null !== $date) {
$exchangeRates->setDate($date);
}
$exchangeRates->fire();
if ($exchangeRates->jobErrored) {
$this->error(sprintf('Error in "exchange rates" cron: %s', $exchangeRates->message));
}
if ($exchangeRates->jobFired) {
$this->line(sprintf('"Exchange rates" cron fired: %s', $exchangeRates->message));
}
if ($exchangeRates->jobSucceeded) {
$this->info(sprintf('"Exchange rates" cron ran with success: %s', $exchangeRates->message));
}
}
/**
* @param bool $force
* @param Carbon|null $date
*
* @throws ContainerExceptionInterface
* @throws FireflyException
* @throws NotFoundExceptionInterface
*/
private function recurringCronJob(bool $force, ?Carbon $date): void
{
$recurring = new RecurringCronjob();
$recurring->setForce($force);
// set date in cron job:
if (null !== $date) {
$recurring->setDate($date);
}
$recurring->fire();
if ($recurring->jobErrored) {
$this->error(sprintf('Error in "create recurring transactions" cron: %s', $recurring->message));
}
if ($recurring->jobFired) {
$this->line(sprintf('"Create recurring transactions" cron fired: %s', $recurring->message));
}
if ($recurring->jobSucceeded) {
$this->info(sprintf('"Create recurring transactions" cron ran with success: %s', $recurring->message));
}
}
}

View File

@ -96,20 +96,6 @@ class AccountCurrencies extends Command
return 0;
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->userRepos = app(UserRepositoryInterface::class);
$this->count = 0;
}
/**
* @return bool
* @throws ContainerExceptionInterface
@ -128,50 +114,23 @@ class AccountCurrencies extends Command
/**
*
*/
private function updateAccountCurrencies(): void
private function markAsExecuted(): void
{
Log::debug('Now in updateAccountCurrencies()');
$users = $this->userRepos->all();
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode));
foreach ($users as $user) {
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
}
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
* @param User $user
* @param string $systemCurrencyCode
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
* @throws FireflyException
*/
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
private function stupidLaravel(): void
{
Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode));
$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;
if (!is_string($defaultCurrencyCode)) {
$defaultCurrencyCode = $systemCurrencyCode;
}
Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode));
/** @var TransactionCurrency|null $defaultCurrency */
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
if (null === $defaultCurrency) {
Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode));
$this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode));
return;
}
/** @var Account $account */
foreach ($accounts as $account) {
$this->updateAccount($account, $defaultCurrency);
}
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->userRepos = app(UserRepositoryInterface::class);
$this->count = 0;
}
/**
@ -237,8 +196,49 @@ class AccountCurrencies extends Command
/**
*
*/
private function markAsExecuted(): void
private function updateAccountCurrencies(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
Log::debug('Now in updateAccountCurrencies()');
$users = $this->userRepos->all();
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode));
foreach ($users as $user) {
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
}
}
/**
* @param User $user
* @param string $systemCurrencyCode
*
* @throws FireflyException
*/
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
{
Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode));
$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;
if (!is_string($defaultCurrencyCode)) {
$defaultCurrencyCode = $systemCurrencyCode;
}
Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode));
/** @var TransactionCurrency|null $defaultCurrency */
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
if (null === $defaultCurrency) {
Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode));
$this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode));
return;
}
/** @var Account $account */
foreach ($accounts as $account) {
$this->updateAccount($account, $defaultCurrency);
}
}
}

View File

@ -73,30 +73,6 @@ class AppendBudgetLimitPeriods extends Command
return 0;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
return (bool)$configVar->data;
}
/**
*
*/
private function theresNoLimit(): void
{
$limits = BudgetLimit::whereNull('period')->get();
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$this->fixLimit($limit);
}
}
/**
* @param BudgetLimit $limit
*/
@ -182,6 +158,18 @@ class AppendBudgetLimitPeriods extends Command
return null;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
return (bool)$configVar->data;
}
/**
*
*/
@ -189,4 +177,16 @@ class AppendBudgetLimitPeriods extends Command
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
*
*/
private function theresNoLimit(): void
{
$limits = BudgetLimit::whereNull('period')->get();
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$this->fixLimit($limit);
}
}
}

View File

@ -87,15 +87,39 @@ class BackToJournals extends Command
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @return array
*/
private function isMigrated(): bool
private function getIdsForBudgets(): array
{
$configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false);
$transactions = DB::table('budget_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
return (bool)$configVar->data;
foreach ($chunks as $chunk) {
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}
return $array;
}
/**
* @return array
*/
private function getIdsForCategories(): array
{
$transactions = DB::table('category_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
foreach ($chunks as $chunk) {
$set = DB::table('transactions')
->whereIn('transactions.id', $chunk)
->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}
return $array;
}
/**
@ -110,6 +134,26 @@ class BackToJournals extends Command
return (bool)$configVar->data;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isMigrated(): bool
{
$configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false);
return (bool)$configVar->data;
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
*
*/
@ -143,23 +187,6 @@ class BackToJournals extends Command
}
}
/**
* @return array
*/
private function getIdsForBudgets(): array
{
$transactions = DB::table('budget_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
foreach ($chunks as $chunk) {
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}
return $array;
}
/**
* @param TransactionJournal $journal
*/
@ -217,25 +244,6 @@ class BackToJournals extends Command
}
}
/**
* @return array
*/
private function getIdsForCategories(): array
{
$transactions = DB::table('category_transaction')->distinct()->pluck('transaction_id')->toArray();
$array = [];
$chunks = array_chunk($transactions, 500);
foreach ($chunks as $chunk) {
$set = DB::table('transactions')
->whereIn('transactions.id', $chunk)
->pluck('transaction_journal_id')->toArray();
$array = array_merge($array, $set);
}
return $array;
}
/**
* @param TransactionJournal $journal
*/
@ -265,12 +273,4 @@ class BackToJournals extends Command
$journal->categories()->sync([(int)$category->id]);
}
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -87,6 +87,81 @@ class DecryptDatabase extends Command
return 0;
}
/**
* @param string $table
* @param string $field
*/
private function decryptField(string $table, string $field): void
{
$rows = DB::table($table)->get(['id', $field]);
/** @var stdClass $row */
foreach ($rows as $row) {
$this->decryptRow($table, $field, $row);
}
}
/**
* @param int $id
* @param string $value
*/
private function decryptPreferencesRow(int $id, string $value): void
{
// try to json_decrypt the value.
try {
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
} catch (JsonException $e) {
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
$this->error($message);
app('log')->warning($message);
app('log')->warning($value);
app('log')->warning($e->getTraceAsString());
return;
}
/** @var Preference $object */
$object = Preference::find((int)$id);
if (null !== $object) {
$object->data = $newValue;
$object->save();
}
}
/**
* @param string $table
* @param string $field
* @param stdClass $row
*/
private function decryptRow(string $table, string $field, stdClass $row): void
{
$original = $row->$field;
if (null === $original) {
return;
}
$id = (int)$row->id;
$value = '';
try {
$value = $this->tryDecrypt($original);
} catch (FireflyException $e) {
$message = sprintf('Could not decrypt field "%s" in row #%d of table "%s": %s', $field, $id, $table, $e->getMessage());
$this->error($message);
Log::error($message);
Log::error($e->getTraceAsString());
}
// A separate routine for preferences table:
if ('preferences' === $table) {
$this->decryptPreferencesRow($id, $value);
return;
}
if ($value !== $original) {
DB::table($table)->where('id', $id)->update([$field => $value]);
}
}
/**
* @param string $table
* @param array $fields
@ -132,54 +207,6 @@ class DecryptDatabase extends Command
return false;
}
/**
* @param string $table
* @param string $field
*/
private function decryptField(string $table, string $field): void
{
$rows = DB::table($table)->get(['id', $field]);
/** @var stdClass $row */
foreach ($rows as $row) {
$this->decryptRow($table, $field, $row);
}
}
/**
* @param string $table
* @param string $field
* @param stdClass $row
*/
private function decryptRow(string $table, string $field, stdClass $row): void
{
$original = $row->$field;
if (null === $original) {
return;
}
$id = (int)$row->id;
$value = '';
try {
$value = $this->tryDecrypt($original);
} catch (FireflyException $e) {
$message = sprintf('Could not decrypt field "%s" in row #%d of table "%s": %s', $field, $id, $table, $e->getMessage());
$this->error($message);
Log::error($message);
Log::error($e->getTraceAsString());
}
// A separate routine for preferences table:
if ('preferences' === $table) {
$this->decryptPreferencesRow($id, $value);
return;
}
if ($value !== $original) {
DB::table($table)->where('id', $id)->update([$field => $value]);
}
}
/**
* Tries to decrypt data. Will only throw an exception when the MAC is invalid.
*
@ -200,31 +227,4 @@ class DecryptDatabase extends Command
return $value;
}
/**
* @param int $id
* @param string $value
*/
private function decryptPreferencesRow(int $id, string $value): void
{
// try to json_decrypt the value.
try {
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
} catch (JsonException $e) {
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
$this->error($message);
app('log')->warning($message);
app('log')->warning($value);
app('log')->warning($e->getTraceAsString());
return;
}
/** @var Preference $object */
$object = Preference::find((int)$id);
if (null !== $object) {
$object->data = $newValue;
$object->save();
}
}
}

View File

@ -102,20 +102,11 @@ class MigrateRecurrenceMeta extends Command
}
/**
* @return int
* @throws JsonException
*
*/
private function migrateMetaData(): int
private function markAsExecuted(): void
{
$count = 0;
// get all recurrence meta data:
$collection = RecurrenceMeta::with('recurrence')->get();
/** @var RecurrenceMeta $meta */
foreach ($collection as $meta) {
$count += $this->migrateEntry($meta);
}
return $count;
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
@ -155,10 +146,19 @@ class MigrateRecurrenceMeta extends Command
}
/**
*
* @return int
* @throws JsonException
*/
private function markAsExecuted(): void
private function migrateMetaData(): int
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
$count = 0;
// get all recurrence meta data:
$collection = RecurrenceMeta::with('recurrence')->get();
/** @var RecurrenceMeta $meta */
foreach ($collection as $meta) {
$count += $this->migrateEntry($meta);
}
return $count;
}
}

View File

@ -78,6 +78,14 @@ class MigrateRecurrenceType extends Command
return 0;
}
/**
*
*/
private function getInvalidType(): TransactionType
{
return TransactionType::whereType(TransactionType::INVALID)->firstOrCreate(['type' => TransactionType::INVALID]);
}
/**
* @return bool
* @throws ContainerExceptionInterface
@ -96,15 +104,9 @@ class MigrateRecurrenceType extends Command
/**
*
*/
private function migrateTypes(): void
private function markAsExecuted(): void
{
$set = Recurrence::get();
/** @var Recurrence $recurrence */
foreach ($set as $recurrence) {
if ($recurrence->transactionType->type !== TransactionType::INVALID) {
$this->migrateRecurrence($recurrence);
}
}
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
private function migrateRecurrence(Recurrence $recurrence): void
@ -124,16 +126,14 @@ class MigrateRecurrenceType extends Command
/**
*
*/
private function getInvalidType(): TransactionType
private function migrateTypes(): void
{
return TransactionType::whereType(TransactionType::INVALID)->firstOrCreate(['type' => TransactionType::INVALID]);
$set = Recurrence::get();
/** @var Recurrence $recurrence */
foreach ($set as $recurrence) {
if ($recurrence->transactionType->type !== TransactionType::INVALID) {
$this->migrateRecurrence($recurrence);
}
}
/**
*
*/
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@ -75,6 +75,16 @@ class MigrateTagLocations extends Command
return 0;
}
/**
* @param Tag $tag
*
* @return bool
*/
private function hasLocationDetails(Tag $tag): bool
{
return null !== $tag->latitude && null !== $tag->longitude && null !== $tag->zoomLevel;
}
/**
* @return bool
* @throws ContainerExceptionInterface
@ -90,25 +100,12 @@ class MigrateTagLocations extends Command
return false;
}
private function migrateTagLocations(): void
{
$tags = Tag::get();
/** @var Tag $tag */
foreach ($tags as $tag) {
if ($this->hasLocationDetails($tag)) {
$this->migrateLocationDetails($tag);
}
}
}
/**
* @param Tag $tag
*
* @return bool
*/
private function hasLocationDetails(Tag $tag): bool
private function markAsExecuted(): void
{
return null !== $tag->latitude && null !== $tag->longitude && null !== $tag->zoomLevel;
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
@ -129,11 +126,14 @@ class MigrateTagLocations extends Command
$tag->save();
}
/**
*
*/
private function markAsExecuted(): void
private function migrateTagLocations(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
$tags = Tag::get();
/** @var Tag $tag */
foreach ($tags as $tag) {
if ($this->hasLocationDetails($tag)) {
$this->migrateLocationDetails($tag);
}
}
}
}

View File

@ -105,22 +105,6 @@ class MigrateToRules extends Command
return 0;
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->count = 0;
$this->userRepository = app(UserRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$this->billRepository = app(BillRepositoryInterface::class);
$this->ruleRepository = app(RuleRepositoryInterface::class);
}
/**
* @return bool
* @throws ContainerExceptionInterface
@ -137,38 +121,11 @@ class MigrateToRules extends Command
}
/**
* Migrate bills to new rule structure for a specific user.
*
* @param User $user
*
* @throws FireflyException
*/
private function migrateUser(User $user): void
private function markAsExecuted(): void
{
$this->ruleGroupRepository->setUser($user);
$this->billRepository->setUser($user);
$this->ruleRepository->setUser($user);
/** @var Preference $lang */
$lang = app('preferences')->getForUser($user, 'language', 'en_US');
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data);
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
if (null === $ruleGroup) {
$ruleGroup = $this->ruleGroupRepository->store(
[
'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data),
'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data),
'active' => true,
]
);
}
$bills = $this->billRepository->getBills();
/** @var Bill $bill */
foreach ($bills as $bill) {
$this->migrateBill($ruleGroup, $bill, $lang);
}
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
@ -243,10 +200,53 @@ class MigrateToRules extends Command
}
/**
* Migrate bills to new rule structure for a specific user.
*
* @param User $user
*
* @throws FireflyException
*/
private function markAsExecuted(): void
private function migrateUser(User $user): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
$this->ruleGroupRepository->setUser($user);
$this->billRepository->setUser($user);
$this->ruleRepository->setUser($user);
/** @var Preference $lang */
$lang = app('preferences')->getForUser($user, 'language', 'en_US');
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data);
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
if (null === $ruleGroup) {
$ruleGroup = $this->ruleGroupRepository->store(
[
'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data),
'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data),
'active' => true,
]
);
}
$bills = $this->billRepository->getBills();
/** @var Bill $bill */
foreach ($bills as $bill) {
$this->migrateBill($ruleGroup, $bill, $lang);
}
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->count = 0;
$this->userRepository = app(UserRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$this->billRepository = app(BillRepositoryInterface::class);
$this->ruleRepository = app(RuleRepositoryInterface::class);
}
}

View File

@ -107,63 +107,6 @@ class TransactionIdentifier extends Command
return 0;
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
$this->count = 0;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
* Grab all positive transactions 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 TransactionJournal $transactionJournal
*/
private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void
{
$identifier = 0;
$exclude = []; // transactions already processed.
$transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$opposing = $this->findOpposing($transaction, $exclude);
if (null !== $opposing) {
// give both a new identifier:
$transaction->identifier = $identifier;
$opposing->identifier = $identifier;
$transaction->save();
$opposing->save();
$exclude[] = $transaction->id;
$exclude[] = $opposing->id;
$this->count++;
}
++$identifier;
}
}
/**
* @param Transaction $transaction
* @param array $exclude
@ -194,6 +137,21 @@ class TransactionIdentifier extends Command
return $opposing;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
*
*/
@ -201,4 +159,46 @@ class TransactionIdentifier extends Command
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
* be called from the handle method instead of using the constructor to initialize the command.
*
*/
private function stupidLaravel(): void
{
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
$this->count = 0;
}
/**
* Grab all positive transactions 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 TransactionJournal $transactionJournal
*/
private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void
{
$identifier = 0;
$exclude = []; // transactions already processed.
$transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$opposing = $this->findOpposing($transaction, $exclude);
if (null !== $opposing) {
// give both a new identifier:
$transaction->identifier = $identifier;
$opposing->identifier = $identifier;
$transaction->save();
$opposing->save();
$exclude[] = $transaction->id;
$exclude[] = $opposing->id;
$this->count++;
}
++$identifier;
}
}
}

View File

@ -98,7 +98,7 @@ class UpgradeDatabase extends Command
private function callInitialCommands(): void
{
$this->line('Now seeding the database...');
$this->call('migrate', ['--seed' => true, '--force' => true,'--no-interaction' => true]);
$this->call('migrate', ['--seed' => true, '--force' => true, '--no-interaction' => true]);
$this->line('Fix PostgreSQL sequences.');
$this->call('firefly-iii:fix-pgsql-sequences');

View File

@ -82,79 +82,6 @@ class UpgradeLiabilities extends Command
return 0;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
*
*/
private function upgradeLiabilities(): void
{
Log::debug('Upgrading liabilities.');
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$this->upgradeForUser($user);
}
}
/**
* @param User $user
*/
private function upgradeForUser(User $user): void
{
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
$accounts = $user->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
/** @var Account $account */
foreach ($accounts as $account) {
$this->upgradeLiability($account);
$service = app(CreditRecalculateService::class);
$service->setAccount($account);
$service->recalculate();
}
}
/**
* @param Account $account
*/
private function upgradeLiability(Account $account): void
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
Log::debug(sprintf('Upgrade liability #%d', $account->id));
// get opening balance, and correct if necessary.
$openingBalance = $repository->getOpeningBalance($account);
if (null !== $openingBalance) {
// correct if necessary
$this->correctOpeningBalance($account, $openingBalance);
}
// add liability direction property (if it does not yet exist!)
$value = $repository->getMetaValue($account, 'liability_direction');
if (null === $value) {
/** @var AccountMetaFactory $factory */
$factory = app(AccountMetaFactory::class);
$factory->crud($account, 'liability_direction', 'debit');
}
}
/**
* @param Account $account
* @param TransactionJournal $openingBalance
@ -185,9 +112,9 @@ class UpgradeLiabilities extends Command
*
* @return Transaction|null
*/
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
{
return $journal->transactions()->where('amount', '<', 0)->first();
return $journal->transactions()->where('amount', '>', 0)->first();
}
/**
@ -195,9 +122,24 @@ class UpgradeLiabilities extends Command
*
* @return Transaction|null
*/
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
{
return $journal->transactions()->where('amount', '>', 0)->first();
return $journal->transactions()->where('amount', '<', 0)->first();
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
@ -207,4 +149,62 @@ class UpgradeLiabilities extends Command
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
* @param User $user
*/
private function upgradeForUser(User $user): void
{
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
$accounts = $user->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
/** @var Account $account */
foreach ($accounts as $account) {
$this->upgradeLiability($account);
$service = app(CreditRecalculateService::class);
$service->setAccount($account);
$service->recalculate();
}
}
/**
*
*/
private function upgradeLiabilities(): void
{
Log::debug('Upgrading liabilities.');
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$this->upgradeForUser($user);
}
}
/**
* @param Account $account
*/
private function upgradeLiability(Account $account): void
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
Log::debug(sprintf('Upgrade liability #%d', $account->id));
// get opening balance, and correct if necessary.
$openingBalance = $repository->getOpeningBalance($account);
if (null !== $openingBalance) {
// correct if necessary
$this->correctOpeningBalance($account, $openingBalance);
}
// add liability direction property (if it does not yet exist!)
$value = $repository->getMetaValue($account, 'liability_direction');
if (null === $value) {
/** @var AccountMetaFactory $factory */
$factory = app(AccountMetaFactory::class);
$factory->crud($account, 'liability_direction', 'debit');
}
}
}

View File

@ -83,115 +83,6 @@ class UpgradeLiabilitiesEight extends Command
return 0;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
*
*/
private function upgradeLiabilities(): void
{
Log::debug('Upgrading liabilities.');
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$this->upgradeForUser($user);
}
}
/**
* @param User $user
*/
private function upgradeForUser(User $user): void
{
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
$accounts = $user->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
/** @var Account $account */
foreach ($accounts as $account) {
$this->upgradeLiability($account);
$service = app(CreditRecalculateService::class);
$service->setAccount($account);
$service->recalculate();
}
}
/**
* @param Account $account
*/
private function upgradeLiability(Account $account): void
{
Log::debug(sprintf('Upgrade liability #%d ("%s")', $account->id, $account->name));
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$direction = $repository->getMetaValue($account, 'liability_direction');
if ('debit' === $direction) {
Log::debug('Direction is debit ("I owe this amount"), so no need to upgrade.');
}
if ('credit' === $direction && $this->hasBadOpening($account)) {
$this->deleteCreditTransaction($account);
$this->reverseOpeningBalance($account);
$this->line(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
}
if ('credit' === $direction) {
$count = $this->deleteTransactions($account);
if ($count > 0) {
$this->line(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
}
}
Log::debug(sprintf('Done upgrading liability #%d ("%s")', $account->id, $account->name));
}
/**
* @param Account $account
* @return bool
*/
private function hasBadOpening(Account $account): bool
{
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
$liabilityType = TransactionType::whereType(TransactionType::LIABILITY_CREDIT)->first();
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
->first(['transaction_journals.*']);
if (null === $openingJournal) {
Log::debug('Account has no opening balance and can be skipped.');
return false;
}
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $liabilityType->id)
->first(['transaction_journals.*']);
if (null === $liabilityJournal) {
Log::debug('Account has no liability credit and can be skipped.');
return false;
}
if (!$openingJournal->date->isSameDay($liabilityJournal->date)) {
Log::debug('Account has opening/credit not on the same day.');
return false;
}
Log::debug('Account has bad opening balance data.');
return true;
}
/**
* @param Account $account
* @return void
@ -214,35 +105,6 @@ class UpgradeLiabilitiesEight extends Command
Log::debug('No liability credit journal found.');
}
/**
* @param Account $account
* @return void
*/
private function reverseOpeningBalance(Account $account): void
{
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
/** @var TransactionJournal $openingJournal */
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
->first(['transaction_journals.*']);
/** @var Transaction|null $source */
$source = $openingJournal->transactions()->where('amount', '<', 0)->first();
/** @var Transaction|null $dest */
$dest = $openingJournal->transactions()->where('amount', '>', 0)->first();
if ($source && $dest) {
$sourceId = $source->account_id;
$destId = $dest->account_id;
$dest->account_id = $sourceId;
$source->account_id = $destId;
$source->save();
$dest->save();
Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id));
return;
}
Log::warning('Did not find opening balance.');
}
/**
* @param $account
* @return int
@ -293,6 +155,54 @@ class UpgradeLiabilitiesEight extends Command
return $count;
}
/**
* @param Account $account
* @return bool
*/
private function hasBadOpening(Account $account): bool
{
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
$liabilityType = TransactionType::whereType(TransactionType::LIABILITY_CREDIT)->first();
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
->first(['transaction_journals.*']);
if (null === $openingJournal) {
Log::debug('Account has no opening balance and can be skipped.');
return false;
}
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $liabilityType->id)
->first(['transaction_journals.*']);
if (null === $liabilityJournal) {
Log::debug('Account has no liability credit and can be skipped.');
return false;
}
if (!$openingJournal->date->isSameDay($liabilityJournal->date)) {
Log::debug('Account has opening/credit not on the same day.');
return false;
}
Log::debug('Account has bad opening balance data.');
return true;
}
/**
* @return bool
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool)$configVar->data;
}
return false;
}
/**
*
*/
@ -300,4 +210,94 @@ class UpgradeLiabilitiesEight extends Command
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
/**
* @param Account $account
* @return void
*/
private function reverseOpeningBalance(Account $account): void
{
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
/** @var TransactionJournal $openingJournal */
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
->first(['transaction_journals.*']);
/** @var Transaction|null $source */
$source = $openingJournal->transactions()->where('amount', '<', 0)->first();
/** @var Transaction|null $dest */
$dest = $openingJournal->transactions()->where('amount', '>', 0)->first();
if ($source && $dest) {
$sourceId = $source->account_id;
$destId = $dest->account_id;
$dest->account_id = $sourceId;
$source->account_id = $destId;
$source->save();
$dest->save();
Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id));
return;
}
Log::warning('Did not find opening balance.');
}
/**
* @param User $user
*/
private function upgradeForUser(User $user): void
{
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
$accounts = $user->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereIn('account_types.type', config('firefly.valid_liabilities'))
->get(['accounts.*']);
/** @var Account $account */
foreach ($accounts as $account) {
$this->upgradeLiability($account);
$service = app(CreditRecalculateService::class);
$service->setAccount($account);
$service->recalculate();
}
}
/**
*
*/
private function upgradeLiabilities(): void
{
Log::debug('Upgrading liabilities.');
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$this->upgradeForUser($user);
}
}
/**
* @param Account $account
*/
private function upgradeLiability(Account $account): void
{
Log::debug(sprintf('Upgrade liability #%d ("%s")', $account->id, $account->name));
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$direction = $repository->getMetaValue($account, 'liability_direction');
if ('debit' === $direction) {
Log::debug('Direction is debit ("I owe this amount"), so no need to upgrade.');
}
if ('credit' === $direction && $this->hasBadOpening($account)) {
$this->deleteCreditTransaction($account);
$this->reverseOpeningBalance($account);
$this->line(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
}
if ('credit' === $direction) {
$count = $this->deleteTransactions($account);
if ($count > 0) {
$this->line(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
}
}
Log::debug(sprintf('Done upgrading liability #%d ("%s")', $account->id, $account->name));
}
}

View File

@ -27,9 +27,12 @@ use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use Illuminate\Support\Facades\Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Spatie\Period\Boundaries;
use Spatie\Period\Period;
use Spatie\Period\Precision;
@ -45,7 +48,7 @@ class BudgetLimitHandler
*/
public function created(Created $event): void
{
Log::debug(sprintf('BudgetLimitHandler::created(%s)', $event->budgetLimit->id));
Log::debug(sprintf('BudgetLimitHandler::created(#%s)', $event->budgetLimit->id));
$this->updateAvailableBudget($event->budgetLimit);
}
@ -55,7 +58,7 @@ class BudgetLimitHandler
*/
public function updated(Updated $event): void
{
Log::debug(sprintf('BudgetLimitHandler::updated(%s)', $event->budgetLimit->id));
Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id));
$this->updateAvailableBudget($event->budgetLimit);
}
@ -65,7 +68,9 @@ class BudgetLimitHandler
*/
public function deleted(Deleted $event): void
{
Log::debug(sprintf('BudgetLimitHandler::deleted(%s)', $event->budgetLimit->id));
Log::debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id));
$budgetLimit = $event->budgetLimit;
$budgetLimit->id = null;
$this->updateAvailableBudget($event->budgetLimit);
}
@ -124,6 +129,11 @@ class BudgetLimitHandler
}
}
}
if (0 === bccomp('0', $newAmount)) {
Log::debug('New amount is zero, deleting AB.');
$availableBudget->delete();
return;
}
Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount));
$availableBudget->amount = $newAmount;
$availableBudget->save();
@ -135,6 +145,9 @@ class BudgetLimitHandler
*/
private function getDailyAmount(BudgetLimit $budgetLimit): string
{
if (0 === (int)$budgetLimit->id) {
return '0';
}
$limitPeriod = Period::make(
$budgetLimit->start_date,
$budgetLimit->end_date,
@ -152,9 +165,6 @@ class BudgetLimitHandler
/**
* @param BudgetLimit $budgetLimit
* @return void
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function updateAvailableBudget(BudgetLimit $budgetLimit): void
{
@ -163,11 +173,23 @@ class BudgetLimitHandler
// based on the view range of the user (month week quarter etc) the budget limit could
// either overlap multiple available budget periods or be contained in a single one.
// all have to be created or updated.
try {
$viewRange = app('preferences')->get('viewRange', '1M')->data;
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
$viewRange = '1M';
}
$start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange);
$end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange);
$end = app('navigation')->endOfPeriod($end, $viewRange);
$user = $budgetLimit->budget->user;
$budget = Budget::withTrashed()->find($budgetLimit->budget_id);
$user = $budget->user;
// sanity check. It happens when the budget has been deleted so the original user is unknown.
if (null === $user) {
Log::warning('User is null, cannot continue.');
$budgetLimit->forceDelete();
return;
}
// limit period in total is:
$limitPeriod = Period::make($start, $end, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
@ -195,8 +217,12 @@ class BudgetLimitHandler
// no need to calculate if period is equal.
if ($currentPeriod->equals($limitPeriod)) {
$amount = $budgetLimit->amount;
$amount = 0 === (int)$budgetLimit->id ? '0' : $budgetLimit->amount;
}
if (0 === bccomp($amount, '0')) {
Log::debug('Amount is zero, will not create AB.');
}
if (0 !== bccomp($amount, '0')) {
Log::debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$availableBudget = new AvailableBudget(
[
@ -209,6 +235,7 @@ class BudgetLimitHandler
);
$availableBudget->save();
}
}
// prep for next loop
$current = app('navigation')->addPeriod($current, $viewRange, 0);

View File

@ -80,7 +80,10 @@ class IndexController extends Controller
$defaultCurrency = app('amount')->getDefaultCurrency();
$parameters = new ParameterBag();
$parameters->set('start', $start);
// sub one day from temp start so the last paid date is one day before it should be.
$tempStart = clone $start;
$tempStart->subDay();
$parameters->set('start', $tempStart);
$parameters->set('end', $end);
/** @var BillTransformer $transformer */
@ -108,18 +111,6 @@ class IndexController extends Controller
'object_group_title' => $array['object_group_title'],
'bills' => [],
];
// var_dump($array);exit;
// // expected today? default:
// $array['next_expected_match_diff'] = trans('firefly.not_expected_period');
// $nextExpectedMatch = new Carbon($array['next_expected_match']);
// if ($nextExpectedMatch->isToday()) {
// $array['next_expected_match_diff'] = trans('firefly.today');
// }
// $current = $array['pay_dates'][0] ?? null;
// if (null !== $current && !$nextExpectedMatch->isToday()) {
// $currentExpectedMatch = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current);
// $array['next_expected_match_diff'] = $currentExpectedMatch->diffForHumans(today(), Carbon::DIFF_RELATIVE_TO_NOW);
// }
$currency = $bill->transactionCurrency ?? $defaultCurrency;
$array['currency_id'] = $currency->id;

View File

@ -34,8 +34,8 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\View\View;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Monolog\Handler\RotatingFileHandler;
/**
@ -121,6 +121,8 @@ class DebugController extends Controller
$now = today(config('app.timezone'))->format('Y-m-d H:i:s e');
$buildNr = '(unknown)';
$buildDate = '(unknown)';
$baseBuildNr = '(unknown)';
$baseBuildDate = '(unknown)';
$expectedDBversion = config('firefly.db_version');
$foundDBversion = FireflyConfig::get('db_version', 1)->data;
if (file_exists('/var/www/counter-main.txt')) {
@ -129,6 +131,13 @@ class DebugController extends Controller
if (file_exists('/var/www/build-date-main.txt')) {
$buildDate = trim(file_get_contents('/var/www/build-date-main.txt'));
}
if('' !== (string)env('BASE_IMAGE_BUILD')) {
$baseBuildNr = env('BASE_IMAGE_BUILD');
}
if('' !== (string)env('BASE_IMAGE_DATE')) {
$baseBuildDate = env('BASE_IMAGE_DATE');
}
$phpVersion = PHP_VERSION;
$phpOs = PHP_OS;
@ -220,6 +229,8 @@ class DebugController extends Controller
'loginProvider',
'buildNr',
'buildDate',
'baseBuildNr',
'baseBuildDate',
'bcscale',
'userAgent',
'displayErrors',

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\System;
use FireflyIII\User;
use FireflyIII\Http\Controllers\Controller;
use Illuminate\Http\Response;
@ -38,6 +39,7 @@ class HealthcheckController extends Controller
*/
public function check(): Response
{
User::count(); // sanity check for database health. Will crash if not OK.
return response('OK', 200);
}
}

View File

@ -70,6 +70,8 @@ class AutoBudget extends Model
public const AUTO_BUDGET_RESET = 1;
public const AUTO_BUDGET_ROLLOVER = 2;
protected $fillable = ['budget_id','amount','period'];
/**
* @return BelongsTo
*/

View File

@ -298,7 +298,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $budgets = null,
?TransactionCurrency $currency = null
): array {
Log::debug(sprintf('Now in %s', __METHOD__));
//Log::debug(sprintf('Now in %s', __METHOD__));
$start->startOfDay();
$end->endOfDay();
@ -340,7 +340,7 @@ class OperationsRepository implements OperationsRepositoryInterface
// same but for foreign currencies:
if (null !== $currency) {
Log::debug(sprintf('Currency is "%s".', $currency->name));
//Log::debug(sprintf('Currency is "%s".', $currency->name));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])
@ -350,7 +350,7 @@ class OperationsRepository implements OperationsRepositoryInterface
$collector->setAccounts($accounts);
}
$result = $collector->getExtractedJournals();
Log::debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
//Log::debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
$journals = $result + $journals;
}

View File

@ -52,6 +52,8 @@ class BudgetDestroyService
DB::table('budget_transaction')->where('budget_id', (int)$budget->id)->delete();
// also delete all budget limits
$budget->budgetlimits()->delete();
foreach($budget->budgetlimits()->get() as $limit) {
$limit->delete();
}
}
}

View File

@ -280,6 +280,23 @@ class Navigation
return $currentEnd;
}
$result = match ($repeatFreq) {
'last7' => $currentEnd->addDays(7)->startOfDay(),
'last30' => $currentEnd->addDays(30)->startOfDay(),
'last90' => $currentEnd->addDays(90)->startOfDay(),
'last365' => $currentEnd->addDays(365)->startOfDay(),
'MTD' => $currentEnd->startOfMonth()->startOfDay(),
'QTD' => $currentEnd->firstOfQuarter()->startOfDay(),
'YTD' => $currentEnd->startOfYear()->startOfDay(),
default => null,
};
if (null !== $result) {
return $result;
}
unset($result);
if (!array_key_exists($repeatFreq, $functionMap)) {
Log::error(sprintf('Cannot do endOfPeriod for $repeat_freq "%s"', $repeatFreq));

View File

@ -273,6 +273,7 @@ class TransactionGroupTransformer extends AbstractTransformer
if (10 === strlen($string)) {
return Carbon::createFromFormat('Y-m-d', $string, config('app.timezone'));
}
return Carbon::createFromFormat('Y-m-d H:i:s', $string, config('app.timezone'));
// 2022-01-01 01:01:01
return Carbon::createFromFormat('Y-m-d H:i:s', substr($string, 0, 19), config('app.timezone'));
}
}

View File

@ -130,11 +130,13 @@ trait WithdrawalValidation
{
$accountId = array_key_exists('id', $array) ? $array['id'] : null;
$accountName = array_key_exists('name', $array) ? $array['name'] : null;
$accountIban = array_key_exists('iban', $array) ? $array['iban'] : null;
$accountNumber =array_key_exists('number', $array) ? $array['number'] : null;
Log::debug('Now in validateWithdrawalSource', $array);
// source can be any of the following types.
$validTypes = array_keys($this->combinations[$this->transactionType]);
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
if (null === $accountId && null === $accountName && null === $accountNumber && null === $accountIban && false === $this->canCreateTypes($validTypes)) {
// if both values are NULL we return false,
// because the source of a withdrawal can't be created.
$this->sourceError = (string)trans('validation.withdrawal_source_need_data');

View File

@ -90,7 +90,7 @@ trait TransactionValidation
Log::error(sprintf('Transactions array is not countable, because its a %s', gettype($transactions)));
return [];
}
Log::debug('Returning transactions.', $transactions);
//Log::debug('Returning transactions.', $transactions);
return $transactions;
}

View File

@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.0.10 - 2023-05-14
### Added
- The debug screen will also report on the build version of the BASE image.
### Changed
- Health check will also check if the database is up.
- [Issue 7461](https://github.com/firefly-iii/firefly-iii/issues/7461) MFA field will now autofocus, thanks @eandersons!
### Removed
- IBAN check no longer triggers on empty IBANs
### Fixed
- Account validation when you only submit an IBAN.
- [Issue 7478](https://github.com/firefly-iii/firefly-iii/issues/7478) [issue 7457](https://github.com/firefly-iii/firefly-iii/issues/7457) Various fixes in budget limit and available amount management.
- [Issue 7446](https://github.com/firefly-iii/firefly-iii/issues/7446) Bills "Next expected match" was incorrect
- [Issue 7456](https://github.com/firefly-iii/firefly-iii/issues/7456) Missing date calculation fields.
- [Issue 7448](https://github.com/firefly-iii/firefly-iii/issues/7448) [issue 7444](https://github.com/firefly-iii/firefly-iii/issues/7444) Dark mode bad CSS
## 6.0.9 - 2023-04-29
### Added

View File

@ -105,13 +105,13 @@
"rcrowe/twigbridge": "^0.14",
"spatie/laravel-ignition": "^2",
"spatie/period": "^2.4",
"symfony/http-client": "^6.0",
"symfony/mailgun-mailer": "^6.0",
"symfony/http-client": "^6.2",
"symfony/mailgun-mailer": "^6.2",
"therobfonz/laravel-mandrill-driver": "^5.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "2.*",
"ergebnis/phpstan-rules": "^1.0",
"ergebnis/phpstan-rules": "^2.0",
"fakerphp/faker": "1.*",
"filp/whoops": "2.*",
"mockery/mockery": "1.*",

448
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -107,7 +107,7 @@ return [
'webhooks' => true,
'handle_debts' => true,
],
'version' => '6.0.9',
'version' => '6.0.10',
'api_version' => '2.0.1',
'db_version' => 19,

View File

@ -69,13 +69,25 @@ class ChangesForV540 extends Migration
Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
if(Schema::hasColumn('bills', 'end_date') && Schema::hasColumn('bills', 'extension_date')) {
// in two steps for sqlite
if(Schema::hasColumn('bills', 'end_date')) {
try {
Schema::table(
'bills',
static function (Blueprint $table) {
$table->dropColumn('end_date');
}
);
} catch (QueryException|ColumnDoesNotExist $e) {
Log::error(sprintf('Could not execute query: %s', $e->getMessage()));
Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
if(Schema::hasColumn('bills', 'extension_date')) {
try {
Schema::table(
'bills',
static function (Blueprint $table) {
$table->dropColumn('extension_date');
}
);

View File

@ -72,7 +72,9 @@ class ChangesForV550 extends Migration
Schema::table(
'budget_transaction_journal',
function (Blueprint $table) {
if ('sqlite' !== config('database.default')) {
$table->dropForeign('budget_id_foreign');
}
$table->dropColumn('budget_limit_id');
}
);
@ -86,12 +88,25 @@ class ChangesForV550 extends Migration
Schema::dropIfExists('failed_jobs');
// drop fields from budget limits
if(Schema::hasColumn('budget_limits', 'period') && Schema::hasColumn('budget_limits', 'generated')) {
// in two steps for sqlite
if(Schema::hasColumn('budget_limits', 'period')) {
try {
Schema::table(
'budget_limits',
static function (Blueprint $table) {
$table->dropColumn('period');
}
);
} catch (QueryException|ColumnDoesNotExist $e) {
Log::error(sprintf('Could not execute query: %s', $e->getMessage()));
Log::error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
if(Schema::hasColumn('budget_limits', 'generated')) {
try {
Schema::table(
'budget_limits',
static function (Blueprint $table) {
$table->dropColumn('generated');
}
);

View File

@ -45,7 +45,9 @@ class ChangesForV550b2 extends Migration
Schema::table(
'recurrences_transactions',
function (Blueprint $table) {
if ('sqlite' !== config('database.default')) {
$table->dropForeign('type_foreign');
}
if (Schema::hasColumn('recurrences_transactions', 'transaction_type_id')) {
$table->dropColumn('transaction_type_id');
}

View File

@ -65,7 +65,9 @@ class UserGroups extends Migration
Schema::table(
$tableName,
function (Blueprint $table) use ($tableName) {
if ('sqlite' !== config('database.default')) {
$table->dropForeign(sprintf('%s_to_ugi', $tableName));
}
if (Schema::hasColumn($tableName, 'user_group_id')) {
$table->dropColumn('user_group_id');
}
@ -83,7 +85,9 @@ class UserGroups extends Migration
Schema::table(
'users',
function (Blueprint $table) {
if ('sqlite' !== config('database.default')) {
$table->dropForeign('type_user_group_id');
}
if (Schema::hasColumn('users', 'user_group_id')) {
$table->dropColumn('user_group_id');
}

View File

@ -67,7 +67,9 @@ return new class () extends Migration {
Schema::table(
'currency_exchange_rates',
function (Blueprint $table) {
if ('sqlite' !== config('database.default')) {
$table->dropForeign('cer_to_ugi');
}
if (Schema::hasColumn('currency_exchange_rates', 'user_group_id')) {
$table->dropColumn('user_group_id');
}

View File

@ -18,7 +18,7 @@
"core-js": "^3.6.5",
"date-fns": "^2.28.0",
"pinia": "^2.0.14",
"quasar": "^2.11.10",
"quasar": "^2.12.0",
"vue": "3",
"vue-i18n": "^9.0.0",
"vue-router": "^4.0.0",
@ -26,7 +26,7 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@quasar/app-webpack": "^3.7.2",
"@quasar/app-webpack": "^3.9.2",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",

View File

@ -60,7 +60,7 @@ export default {
"liabilities_accounts": "\u03a5\u03c0\u03bf\u03c7\u03c1\u03b5\u03ce\u03c3\u03b5\u03b9\u03c2"
},
"firefly": {
"administration_index": "Financial administration",
"administration_index": "\u039f\u03b9\u03ba\u03bf\u03bd\u03bf\u03bc\u03b9\u03ba\u03ae \u03b4\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7",
"actions": "\u0395\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b5\u03c2",
"edit": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1",
"delete": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae",
@ -72,7 +72,7 @@ export default {
"newTransfer": "\u039d\u03ad\u03b1 \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac",
"submission_options": "Submission options",
"apply_rules_checkbox": "Apply rules",
"fire_webhooks_checkbox": "Fire webhooks",
"fire_webhooks_checkbox": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03c9\u03bd webhook",
"newDeposit": "\u039d\u03ad\u03b1 \u03ba\u03b1\u03c4\u03ac\u03b8\u03b5\u03c3\u03b7",
"newWithdrawal": "\u039d\u03ad\u03b1 \u03b4\u03b1\u03c0\u03ac\u03bd\u03b7",
"bills_paid": "\u03a0\u03bb\u03b7\u03c1\u03c9\u03bc\u03ad\u03bd\u03b1 \u03c0\u03ac\u03b3\u03b9\u03b1 \u03ad\u03be\u03bf\u03b4\u03b1",

View File

@ -71,8 +71,8 @@ export default {
"new_asset_account": "\u65b0\u3057\u3044\u8cc7\u7523\u53e3\u5ea7",
"newTransfer": "\u65b0\u3057\u3044\u9001\u91d1",
"submission_options": "Submission options",
"apply_rules_checkbox": "Apply rules",
"fire_webhooks_checkbox": "Fire webhooks",
"apply_rules_checkbox": "\u30eb\u30fc\u30eb\u3092\u9069\u7528",
"fire_webhooks_checkbox": "Webhook \u3092\u5b9f\u884c\u3059\u308b",
"newDeposit": "\u65b0\u3057\u3044\u5165\u91d1",
"newWithdrawal": "\u65b0\u3057\u3044\u652f\u51fa",
"bills_paid": "\u652f\u6255\u3044\u6e08\u307f\u8acb\u6c42",

View File

@ -338,7 +338,7 @@ page container: q-ma-xs (margin all, xs) AND q-mb-md to give the page content so
<q-footer class="bg-grey-8 text-white" bordered>
<q-toolbar>
<div>
<small>Firefly III v v6.0.9 &copy; James Cole, AGPL-3.0-or-later.</small>
<small>Firefly III v v6.0.10 &copy; James Cole, AGPL-3.0-or-later.</small>
</div>
</q-toolbar>
</q-footer>

View File

@ -1337,18 +1337,13 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
"@positron/stack-trace@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
"@quasar/app-webpack@^3.7.2":
version "3.7.2"
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.7.2.tgz#feaea083d79c5e6c62052dfe87a6e7df73d44e5e"
integrity sha512-OztjmlXaLqjBRm6YPV9/Q+WfXhdu9mcXk5OXExyEpzCII7dfa4m1UMzp+AOsgDXUgoSBt5Wg1WmAICLnKELAjw==
"@quasar/app-webpack@^3.9.2":
version "3.9.2"
resolved "https://registry.yarnpkg.com/@quasar/app-webpack/-/app-webpack-3.9.2.tgz#cbf28e2ef84a94a0507f2f17218b29a2ebf6dd88"
integrity sha512-F/LyEXExcnw1niVjuLoSaRLBS2rlmsQJxuOWed6qL9Xnh+KzW1S/V1FxtDtJOQCjbI2S9YvOP6wcmuB6xEiatg==
dependencies:
"@quasar/babel-preset-app" "2.0.2"
"@quasar/fastclick" "1.1.5"
"@quasar/render-ssr-error" "^1.0.1"
"@quasar/ssr-helpers" "2.2.2"
"@types/chrome" "^0.0.208"
"@types/compression" "^1.7.2"
@ -1388,11 +1383,9 @@
node-loader "2.0.0"
null-loader "4.0.1"
open "8.4.0"
ouch "^2.0.1"
postcss "^8.4.4"
postcss-loader "7.0.2"
postcss-rtlcss "4.0.1"
pretty-error "4.0.0"
register-service-worker "1.7.2"
sass "1.32.12"
sass-loader "13.2.0"
@ -1442,10 +1435,12 @@
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.16.3.tgz#72216e2d450a2ee70613957da88191339b90de41"
integrity sha512-c9p2j4KWrWqOcCcCD9IMjgPZzAiXKSMEMc3uYbqa5mHlEJ1kl88OMaeJUcmP+INRQ29AFSEQ9tZE20jLmdnLXw==
"@quasar/fastclick@1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@quasar/fastclick/-/fastclick-1.1.5.tgz#948e79c44098cced6c3d1645315683ebc29ed834"
integrity sha512-p3JKgTjRlJ1YQXbqTw3Bsa4j0mQdt5dq+WfYvyb7MgKGdephHCKdR/kxA5PCTAmJanGJuDKqRdyGYX/hYN4KGw==
"@quasar/render-ssr-error@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@quasar/render-ssr-error/-/render-ssr-error-1.0.1.tgz#5400b51c1bc55f4ee7bfb62ecb6f7c7d0f88ce7f"
integrity sha512-4Shxl079hew/yZnIsDtWpRD8enOmqMjMu/s2bkGN0QBvlsRkpWv9pwOz5geJXZxBa17q1S4txvByBxkhPfhWaQ==
dependencies:
stack-trace "^1.0.0-pre2"
"@quasar/ssr-helpers@2.2.2":
version "2.2.2"
@ -2506,7 +2501,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001400, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz#6d4ba828ab19f49f9bcd14a8430d30feebf1e0c5"
integrity sha512-fEoga4PrImGcwUUGEol/PoFCSBnSkA9drgdkxXkJLsUBOnJ8rs3zDv6ApqYXGQFOyMPsjh79naWhF4DAxbF8rw==
chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1:
chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -3124,13 +3119,6 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
ejs@^3.1.7:
version "3.1.8"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
dependencies:
jake "^10.8.5"
electron-to-chromium@^1.4.251:
version "1.4.284"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
@ -3222,7 +3210,7 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-html@^1.0.1, escape-html@~1.0.3:
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
@ -3572,13 +3560,6 @@ file-loader@6.2.0:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
filelist@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
dependencies:
minimatch "^5.0.1"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -4234,16 +4215,6 @@ isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
dependencies:
async "^3.2.3"
chalk "^4.0.2"
filelist "^1.0.1"
minimatch "^3.0.4"
javascript-stringify@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79"
@ -4482,7 +4453,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.17.10, lodash@^4.17.20, lodash@^4.17.21:
lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -4906,16 +4877,6 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
ouch@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ouch/-/ouch-2.0.1.tgz#9107089819b99146e3d10da57e8f75b39f610993"
integrity sha512-SdkEqpEhsmkEpjTPSvB1DMA//w9ChMUr16m4TayNRVfaULzJ3AnNr3CI4cz1QSZ9a+E/g06c6SQzxjkIc3/GMw==
dependencies:
"@positron/stack-trace" "1.0.0"
ejs "^3.1.7"
escape-html "^1.0.1"
lodash "^4.17.10"
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@ -5353,7 +5314,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
pretty-error@4.0.0, pretty-error@^4.0.0:
pretty-error@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==
@ -5396,10 +5357,10 @@ qs@6.9.7:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
quasar@^2.11.10:
version "2.11.10"
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.11.10.tgz#31bf49dd7995673b2116aa250bb0b019ddcae74f"
integrity sha512-pV7bMdY/FUmOvNhZ2XjKSXJH92fsDu0cU/z7a9roPKV54cW41N1en3sLATrirjPComyZnk4uXrMdGtXp8+IpCg==
quasar@^2.12.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.12.0.tgz#f145ad2b677a0925ea9ca6a3b44b5502be1cbd87"
integrity sha512-B8xoeOWNs/Iv7M+FGRvXGYI1qDnJH8AtIb7RiP+zMfMkBcEp+6HJHU/9ODPemC4yteDjO+HPX2f7OhNZKgsPIw==
queue-microtask@^1.2.2:
version "1.2.3"
@ -5975,6 +5936,11 @@ stable@^0.1.8:
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stack-trace@^1.0.0-pre2:
version "1.0.0-pre2"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-1.0.0-pre2.tgz#46a83a79f1b287807e9aaafc6a5dd8bcde626f9c"
integrity sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==
stackframe@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"

View File

@ -9,7 +9,7 @@
"prod": "mix --production"
},
"dependencies": {
"date-fns": "^2.29.3",
"date-fns": "^2.30.0",
"stream-browserify": "^3.0.0"
},
"devDependencies": {

2
public/v1/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -5,26 +5,12 @@
*/
/*!
* Sizzle CSS Selector Engine v2.3.10
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://js.foundation/
*
* Date: 2023-02-14
*/
/*!
* jQuery JavaScript Library v3.6.4
* jQuery JavaScript Library v3.7.0
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2023-03-08T15:28Z
* Date: 2023-05-11T18:29Z
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,13 @@
* https://raw.githubusercontent.com/anvyst/adminlte-skin-midnight/master/build/less/skins/skin-midnight.less
* ------------
*/
.force-background-tags-input {
background-color: #353c42 !important;
}
.ti-autocomplete {
background-color: #626f7b !important;
border: 1px #353c42 solid !important;
}
.skin-firefly-iii {
color: #bec5cb;
/* PAGINATION */
@ -15,6 +22,9 @@
.skin-firefly-iii .alert-success > a {
color: #fff;
}
.skin-firefly-iii .alert-success a {
color: #fff;
}
.skin-firefly-iii .text-muted {
color: #b0b8c0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html><head><base href=/v3/ ><title>Firefly III</title><meta charset=utf-8><meta content="Personal finances manager" name=description><meta content="telephone=no" name=format-detection><meta content=no name=msapplication-tap-highlight><meta content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width" name=viewport><link href=favicon-32x32.png rel=icon sizes=32x32 type=image/png><link href=favicon-16x16.png rel=icon sizes=16x16 type=image/png><link href=maskable76.png rel=apple-touch-icon sizes=76x76><link href=maskable120.png rel=apple-touch-icon sizes=120x120><link href=maskable152.png rel=apple-touch-icon sizes=152x152><link href=apple-touch-icon.png rel=apple-touch-icon sizes=180x180><link color=#3c8dbc href=safari-pinned-tab.svg rel=mask-icon><link href=maskable192.png rel=icon sizes=192x192><link href=maskable128.png rel=icon sizes=128x128><link href=manifest.webmanifest rel=manifest><meta content=#1e6581 name=msapplication-TileColor><meta content=maskable512.png name=msapplication-TileImage><meta content=no name=msapplication-tap-highlight><meta content="Firefly III" name=application-name><meta content="noindex, nofollow, noarchive, noodp, NoImageIndex, noydir" name=robots><meta content=yes name=apple-mobile-web-app-capable><meta content="Firefly III" name=apple-mobile-web-app-title><meta content="Firefly III" name=application-name><meta content=#3c8dbc name=msapplication-TileColor><meta content="mstile-144x144.png?v=3e8AboOwbd" name=msapplication-TileImage><meta content=#3c8dbc name=theme-color><script defer src=/v3/js/vendor.77517468.js></script><script defer src=/v3/js/app.5f590096.js></script><link href=/v3/css/vendor.04b47167.css rel=stylesheet><link href=/v3/css/app.50c7ba73.css rel=stylesheet></head><body><div id=q-app></div></body></html>
<!DOCTYPE html><html><head><base href=/v3/ ><title>Firefly III</title><meta charset=utf-8><meta content="Personal finances manager" name=description><meta content="telephone=no" name=format-detection><meta content=no name=msapplication-tap-highlight><meta content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width" name=viewport><link href=favicon-32x32.png rel=icon sizes=32x32 type=image/png><link href=favicon-16x16.png rel=icon sizes=16x16 type=image/png><link href=maskable76.png rel=apple-touch-icon sizes=76x76><link href=maskable120.png rel=apple-touch-icon sizes=120x120><link href=maskable152.png rel=apple-touch-icon sizes=152x152><link href=apple-touch-icon.png rel=apple-touch-icon sizes=180x180><link color=#3c8dbc href=safari-pinned-tab.svg rel=mask-icon><link href=maskable192.png rel=icon sizes=192x192><link href=maskable128.png rel=icon sizes=128x128><link href=manifest.webmanifest rel=manifest><meta content=#1e6581 name=msapplication-TileColor><meta content=maskable512.png name=msapplication-TileImage><meta content=no name=msapplication-tap-highlight><meta content="Firefly III" name=application-name><meta content="noindex, nofollow, noarchive, noodp, NoImageIndex, noydir" name=robots><meta content=yes name=apple-mobile-web-app-capable><meta content="Firefly III" name=apple-mobile-web-app-title><meta content="Firefly III" name=application-name><meta content=#3c8dbc name=msapplication-TileColor><meta content="mstile-144x144.png?v=3e8AboOwbd" name=msapplication-TileImage><meta content=#3c8dbc name=theme-color><script defer src=/v3/js/vendor.5b4ef590.js></script><script defer src=/v3/js/app.4fcae26a.js></script><link href=/v3/css/vendor.e1e7dc9b.css rel=stylesheet><link href=/v3/css/app.50c7ba73.css rel=stylesheet></head><body><div id=q-app></div></body></html>

1
public/v3/js/1056.35aff46f.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/v3/js/app.4fcae26a.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@
:autocomplete-items="autocompleteItems"
:tags="tags"
:title="$t('firefly.tags')"
classes="form-input"
class="force-background-tags-input"
v-bind:placeholder="$t('firefly.tags')"
@tags-changed="update"
/>

View File

@ -16,7 +16,7 @@
"transaction_journal_information": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2",
"submission_options": "Submission options",
"apply_rules_checkbox": "Apply rules",
"fire_webhooks_checkbox": "Fire webhooks",
"fire_webhooks_checkbox": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03c4\u03c9\u03bd webhook",
"no_budget_pointer": "\u03a6\u03b1\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c0\u03c9\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03cd\u03c2 \u03b1\u03ba\u03cc\u03bc\u03b7. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf\u03bd \u03c3\u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 <a href=\"budgets\">\u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03ce\u03bd<\/a>. \u039f\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af \u03c3\u03b1\u03c2 \u03b2\u03bf\u03b7\u03b8\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b5\u03c0\u03b9\u03b2\u03bb\u03ad\u03c0\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b4\u03b1\u03c0\u03ac\u03bd\u03b5\u03c2 \u03c3\u03b1\u03c2.",
"no_bill_pointer": "\u03a6\u03b1\u03af\u03bd\u03b5\u03c4\u03b1\u03b9 \u03c0\u03c9\u03c2 \u03b4\u03b5\u03bd \u03ad\u03c7\u03b5\u03c4\u03b5 \u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c0\u03ac\u03b3\u03b9\u03b1 \u03ad\u03be\u03bf\u03b4\u03b1 \u03b1\u03ba\u03cc\u03bc\u03b7. \u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf \u03c3\u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1 <a href=\"bills\">\u03c0\u03ac\u03b3\u03b9\u03c9\u03bd \u03b5\u03be\u03cc\u03b4\u03c9\u03bd<\/a>. \u03a4\u03b1 \u03c0\u03ac\u03b3\u03b9\u03b1 \u03ad\u03be\u03bf\u03b4\u03b1 \u03c3\u03b1\u03c2 \u03b2\u03bf\u03b7\u03b8\u03bf\u03cd\u03bd \u03bd\u03b1 \u03b5\u03c0\u03b9\u03b2\u03bb\u03ad\u03c0\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03b4\u03b1\u03c0\u03ac\u03bd\u03b5\u03c2 \u03c3\u03b1\u03c2.",
"source_account": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2",

View File

@ -15,8 +15,8 @@
"transaction_new_stored_link": "<a href=\"transactions\/show\/{ID}\">\u53d6\u5f15 #{ID}<\/a> \u304c\u4fdd\u5b58\u3055\u308c\u307e\u3057\u305f\u3002",
"transaction_journal_information": "\u53d6\u5f15\u60c5\u5831",
"submission_options": "Submission options",
"apply_rules_checkbox": "Apply rules",
"fire_webhooks_checkbox": "Fire webhooks",
"apply_rules_checkbox": "\u30eb\u30fc\u30eb\u3092\u9069\u7528",
"fire_webhooks_checkbox": "Webhook \u3092\u5b9f\u884c\u3059\u308b",
"no_budget_pointer": "\u307e\u3060\u4e88\u7b97\u3092\u7acb\u3066\u3066\u3044\u306a\u3044\u3088\u3046\u3067\u3059\u3002<a href=\"\/budgets\">\u4e88\u7b97<\/a>\u30da\u30fc\u30b8\u3067\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u4e88\u7b97\u306f\u652f\u51fa\u306e\u628a\u63e1\u306b\u5f79\u7acb\u3061\u307e\u3059\u3002",
"no_bill_pointer": "\u307e\u3060\u8acb\u6c42\u304c\u306a\u3044\u3088\u3046\u3067\u3059\u3002<a href=\"\/budgets\">\u8acb\u6c42<\/a>\u30da\u30fc\u30b8\u3067\u4f5c\u6210\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u8acb\u6c42\u306f\u652f\u51fa\u306e\u628a\u63e1\u306b\u5f79\u7acb\u3061\u307e\u3059\u3002",
"source_account": "\u652f\u51fa\u5143\u53e3\u5ea7",
@ -97,15 +97,15 @@
"webhook_trigger_DESTROY_TRANSACTION": "After transaction delete",
"webhook_response_TRANSACTIONS": "Transaction details",
"webhook_response_ACCOUNTS": "Account details",
"webhook_response_none_NONE": "No details",
"webhook_response_none_NONE": "\u8a73\u7d30\u306a\u3057",
"webhook_delivery_JSON": "JSON",
"actions": "\u64cd\u4f5c",
"meta_data": "\u30e1\u30bf\u30c7\u30fc\u30bf",
"webhook_messages": "Webhook message",
"webhook_messages": "Webhook \u30e1\u30c3\u30bb\u30fc\u30b8",
"inactive": "\u975e\u30a2\u30af\u30c6\u30a3\u30d6",
"no_webhook_messages": "There are no webhook messages",
"inspect": "Inspect",
"create_new_webhook": "Create new webhook",
"create_new_webhook": "Webhook\u3092\u4f5c\u6210",
"webhooks": "Webhooks",
"webhook_trigger_form_help": "Indicate on what event the webhook will trigger",
"webhook_response_form_help": "Indicate what the webhook must submit to the URL.",
@ -113,18 +113,18 @@
"webhook_active_form_help": "The webhook must be active or it won't be called.",
"edit_webhook_js": "Edit webhook \"{title}\"",
"webhook_was_triggered": "The webhook was triggered on the indicated transaction. Please wait for results to appear.",
"view_message": "View message",
"view_attempts": "View failed attempts",
"message_content_title": "Webhook message content",
"view_message": "\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u898b\u308b",
"view_attempts": "\u5931\u6557\u3057\u305f\u8a66\u884c\u306e\u8868\u793a",
"message_content_title": "Webhook \u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u5185\u5bb9",
"message_content_help": "This is the content of the message that was sent (or tried) using this webhook.",
"attempt_content_title": "Webhook attempts",
"attempt_content_title": "Webhook \u306e\u8a66\u884c",
"attempt_content_help": "These are all the unsuccessful attempts of this webhook message to submit to the configured URL. After some time, Firefly III will stop trying.",
"no_attempts": "There are no unsuccessful attempts. That's a good thing!",
"webhook_attempt_at": "Attempt at {moment}",
"logs": "Logs",
"response": "Response",
"logs": "\u30ed\u30b0",
"response": "\u30ec\u30b9\u30dd\u30f3\u30b9",
"visit_webhook_url": "Visit webhook URL",
"reset_webhook_secret": "Reset webhook secret"
"reset_webhook_secret": "Webhook \u306e\u30b7\u30fc\u30af\u30ec\u30c3\u30c8\u3092\u30ea\u30bb\u30c3\u30c8"
},
"form": {
"url": "URL",

View File

@ -271,8 +271,8 @@ return [
'generic_invalid_source' => 'No pots fer servir aquest compte com a compte d\'origen.',
'generic_invalid_destination' => 'No pots fer servir aquest compte com a compte de destí.',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => 'Has de confirmar l\'informació del compte font, o afegir un identificador de transacció.',
'generic_no_destination' => 'Has de confirmar la informació del compte de destinació, o introduïr un identificador de transacció.',
'gte.numeric' => 'El camp :attribute ha de ser més gran o igual que :value.',
'gt.numeric' => 'El camp :attribute ha de ser més gran que :value.',

View File

@ -1302,12 +1302,12 @@ return [
// preferences
'dark_mode_option_browser' => 'Let your browser decide',
'dark_mode_option_light' => 'Always light',
'dark_mode_option_dark' => 'Always dark',
'dark_mode_option_browser' => 'Σύμφωνα με τις προτιμήσεις του περιηγητή σας',
'dark_mode_option_light' => 'Πάντα φωτεινό',
'dark_mode_option_dark' => 'Πάντα σκοτεινό',
'equal_to_language' => '(ίδιο με τη γλώσσα)',
'dark_mode_preference' => 'Dark mode',
'dark_mode_preference_help' => 'Tell Firefly III when to use dark mode.',
'dark_mode_preference' => 'Σκοτεινή λειτουργία',
'dark_mode_preference_help' => 'Πείτε στο Firefly III πότε να χρησιμοποιεί σκοτεινή λειτουργία.',
'pref_home_screen_accounts' => 'Λογαριασμοί αρχικής οθόνης',
'pref_home_screen_accounts_help' => 'Ποιοι λογαριασμοί θα πρέπει να εμφανίζονται στην αρχική σελίδα;',
'pref_view_range' => 'Εύρος εμφάνισης',
@ -1353,7 +1353,7 @@ return [
'preferences_frontpage' => 'Αρχική οθόνη',
'preferences_security' => 'Ασφάλεια',
'preferences_layout' => 'Διάταξη',
'preferences_notifications' => 'Notifications',
'preferences_notifications' => 'Ειδοποιήσεις',
'pref_home_show_deposits' => 'Εμφάνιση καταθέσεων στην αρχική οθόνη',
'pref_home_show_deposits_info' => 'Η αρχική οθόνη δείχνει ήδη τους λογαριασμούς δαπανών σας. Μήπως θα έπρεπε να δείχνει και τους λογαριασμούς εσόδων σας;',
'pref_home_do_show_deposits' => 'Ναι, δείξτε τους',
@ -1384,32 +1384,32 @@ return [
'optional_field_attachments' => 'Συνημμένα',
'optional_field_meta_data' => 'Προαιρετικά μετα-δεδομένα',
'external_url' => 'Εξωτερικό URL',
'pref_notification_bill_reminder' => 'Reminder about expiring bills',
'pref_notification_new_access_token' => 'Alert when a new API access token is created',
'pref_notification_transaction_creation' => 'Alert when a transaction is created automatically',
'pref_notification_user_login' => 'Alert when you login from a new location',
'pref_notifications' => 'Notifications',
'pref_notifications_help' => 'Indicate if these are notifications you would like to get. Some notifications may contain sensitive financial information.',
'pref_notification_bill_reminder' => 'Υπενθύμιση τελευταίας προθεσμίας για πάγια έξοδα',
'pref_notification_new_access_token' => 'Ειδοποίηση όταν δημιουργείται ένα νέο διακριτικό πρόσβασης API',
'pref_notification_transaction_creation' => 'Ειδοποίηση όταν δημιουργείται αυτόματα μια συναλλαγή',
'pref_notification_user_login' => 'Ειδοποίηση όταν συνδέεστε από μια νέα τοποθεσία',
'pref_notifications' => 'Ειδοποιήσεις',
'pref_notifications_help' => 'Υποδείξτε εάν πρόκειται για ειδοποιήσεις που θέλετε να λαμβάνετε. Ορισμένες ειδοποιήσεις ενδέχεται να περιέχουν ευαίσθητες οικονομικές πληροφορίες.',
'slack_webhook_url' => 'Slack Webhook URL',
'slack_webhook_url_help' => 'If you want Firefly III to notify you using Slack, enter the webhook URL here. Otherwise leave the field blank. If you are an admin, you need to set this URL in the administration as well.',
'slack_url_label' => 'Slack "incoming webhook" URL',
'slack_webhook_url_help' => 'Αν θέλετε το Firefly III να σας ειδοποιεί χρησιμοποιώντας το Slack, εισάγετε εδώ το webhook URL. Διαφορετικά αφήστε το πεδίο κενό. Αν είστε διαχειριστής, θα πρέπει να ορίσετε αυτό το URL και στη διαχείριση.',
'slack_url_label' => '"Εισερχόμενο webhook" URL στο Slack',
// Financial administrations
'administration_index' => 'Financial administration',
'administration_index' => 'Οικονομική διαχείριση',
// profile:
'purge_data_title' => 'Purge data from Firefly III',
'purge_data_expl' => '"Purging" means "deleting that which is already deleted". In normal circumstances, Firefly III deletes nothing permanently. It just hides it. The button below deletes all of these previously "deleted" records FOREVER.',
'delete_stuff_header' => 'Delete and purge data',
'purge_all_data' => 'Purge all deleted records',
'purge_data' => 'Purge data',
'purged_all_records' => 'All deleted records have been purged.',
'delete_data_title' => 'Delete data from Firefly III',
'permanent_delete_stuff' => 'You can delete stuff from Firefly III. Using the buttons below means that your items will be removed from view and hidden. There is no undo-button for this, but the items may remain in the database where you can salvage them if necessary.',
'purge_data_title' => 'Εκκαθάριση δεδομένων από το Firefly III',
'purge_data_expl' => '"Εκκαθάριση" σημαίνει "διαγραφή αυτού που έχει ήδη διαγραφεί". Σε κανονικές συνθήκες, το Firefly III δε διαγράφει τίποτα οριστικά. Απλώς το κρύβει. Το παρακάτω κουμπί διαγράφει όλες αυτές τις προηγουμένως "διαγραμμένες" εγγραφές ΓΙΑ ΠΑΝΤΑ.',
'delete_stuff_header' => 'Διαγραφή και εκκαθάριση δεδομένων',
'purge_all_data' => 'Εκκαθάριση όλων των διαγραμμένων εγγραφών',
'purge_data' => 'Εκκαθάριση δεδομένων',
'purged_all_records' => 'Όλες οι διαγραμμένες εγγραφές έχουν εκκαθαριστεί οριστικά.',
'delete_data_title' => 'Διαγραφή δεδομένων από το Firefly III',
'permanent_delete_stuff' => 'Μπορείτε να διαγράψετε πράγματα από το Firefly III. Η χρήση των παρακάτω κουμπιών σημαίνει ότι τα στοιχεία σας αυτά θα είναι κρυμμένα και δε θα εμφανίζονται. Δεν υπάρχει κουμπί αναίρεσης για αυτή την ενέργεια, αλλά τα στοιχεία θα παραμείνουν στη βάση δεδομένων όπου μπορείτε να τα αναζητήσετε χειροκίνητα σε κάποια άλλη στιγμή εάν είναι απαραίτητο.',
'other_sessions_logged_out' => 'Όλες οι άλλες συνεδρίες σας έχουν αποσυνδεθεί.',
'delete_unused_accounts' => 'Deleting unused accounts will clean your auto-complete lists.',
'delete_all_unused_accounts' => 'Delete unused accounts',
'deleted_all_unused_accounts' => 'All unused accounts are deleted',
'delete_unused_accounts' => 'Η διαγραφή αχρησιμοποίητων λογαριασμών θα καθαρίσει τις λίστες αυτόματης συμπλήρωσης.',
'delete_all_unused_accounts' => 'Διαγραφή αχρησιμοποίητων λογαριασμών',
'deleted_all_unused_accounts' => 'Όλοι οι αχρησιμοποίητοι λογαριασμοί έχουν διαγραφεί',
'delete_all_budgets' => 'Διαγραφή ΟΛΩΝ των προϋπολογισμών σας',
'delete_all_categories' => 'Διαγραφή ΟΛΩΝ των κατηγοριών σας',
'delete_all_tags' => 'Διαγραφή ΟΛΩΝ των ετικετών σας',
@ -1564,7 +1564,7 @@ return [
'title_transfers' => 'Μεταφορές',
'submission_options' => 'Submission options',
'apply_rules_checkbox' => 'Apply rules',
'fire_webhooks_checkbox' => 'Fire webhooks',
'fire_webhooks_checkbox' => 'Ενεργοποίηση των webhook',
// convert stuff:
'convert_is_already_type_Withdrawal' => 'Αυτή η συναλλαγή είναι ήδη μία ανάληψη',
@ -2369,15 +2369,15 @@ return [
// administration
'invite_is_already_redeemed' => 'The invite to ":address" has already been redeemed.',
'invite_is_deleted' => 'The invite to ":address" has been deleted.',
'invite_new_user_title' => 'Invite new user',
'invite_new_user_text' => 'As an administrator, you can invite users to register on your Firefly III administration. Using the direct link you can share with them, they will be able to register an account. The invited user and their invite link will appear in the table below. You are free to share the invitation link with them.',
'invited_user_mail' => 'Email address',
'invite_user' => 'Invite user',
'user_is_invited' => 'Email address ":address" was invited to Firefly III',
'invite_is_already_redeemed' => 'Η πρόσκληση στο ":address" έχει ήδη χρησιμοποιηθεί.',
'invite_is_deleted' => 'Η πρόσκληση στο ":address" έχει διαγραφεί.',
'invite_new_user_title' => 'Πρόσκληση νέου χρήστη',
'invite_new_user_text' => 'Ως διαχειριστής, μπορείτε να προσκαλέσετε νέους χρήστες για να εγγραφούν στο Firefly III. Χρησιμοποιώντας ένα άμεσο σύνδεσμο URL που μπορείτε να μοιραστείτε μαζί τους, θα μπορούν να δημιουργήσουν ένα νέο λογαριασμό στην πλατφόρμα. Ο προσκεκλημένος χρήστης και ο σύνδεσμος για την πρόσκλησή του, θα εμφανιστούν στον παρακάτω πίνακα. Μπορείτε να του κοινοποιήσετε το σύνδεσμο της πρόσκλησης.',
'invited_user_mail' => 'Διεύθυνση E-mail',
'invite_user' => 'Πρόσκληση χρήστη',
'user_is_invited' => 'Η διεύθυνση ηλεκτρονικού ταχυδρομείου ":address" έλαβε πρόσκληση για εγγραφή στο Firefly III',
'administration' => 'Διαχείριση',
'code_already_used' => 'Invite code has been used',
'code_already_used' => 'Ο κωδικός πρόσκλησης έχει χρησιμοποιηθεί',
'user_administration' => 'Διαχείριση χρηστών',
'list_all_users' => 'Όλοι οι χρήστες',
'all_users' => 'Όλοι οι χρήστες',
@ -2409,23 +2409,23 @@ return [
'delete_user' => 'Διαγραφή χρήστη :email',
'user_deleted' => 'Ο χρήστης έχει διαγραφεί',
'send_test_email' => 'Αποστολή δοκιμαστικού μηνύματος email',
'send_test_email_text' => 'To see if your installation is capable of sending email or posting Slack messages, please press this button. You will not see an error here (if any), <strong>the log files will reflect any errors</strong>. You can press this button as many times as you like. There is no spam control. The message will be sent to <code>:email</code> and should arrive shortly.',
'send_test_email_text' => 'Για να δείτε εάν η εγκατάστασή σας μπορεί να στείλει email ή να δημοσιεύσει μηνύματα στο Slack, πατήστε αυτό το κουμπί. Δεν θα δείτε κάποιο σφάλμα εδώ (αν υπάρχει), όμως <strong>τυχόν σφάλματα θα τα βρείτε στα αρχεία καταγραφής (log files)</strong>. Μπορείτε να πατήσετε το κουμπί αποστολής όσες φορές θέλετε. Δεν υπάρχει έλεγχος ανεπιθύμητων μηνυμάτων. Το μήνυμα θα σταλεί στο <code>:email</code> και θα πρέπει να φτάσει σύντομα.',
'send_message' => 'Αποστολή μηνύματος',
'send_test_triggered' => 'Η δοκιμή ενεργοποιήθηκε. Ελέγξτε τα εισερχόμενα μηνύματα στο Email σας και τα αρχεία καταγραφής.',
'give_admin_careful' => 'Οι χρήστες στους οποίους έχουν δοθεί δικαιώματα διαχειριστή μπορούν να καθαιρέσουν τη δική σας πρόσβαση. Ενεργήστε με προσοχή.',
'admin_maintanance_title' => 'Συντήρηση',
'admin_maintanance_expl' => 'Μερικά ωραία κουμπιά για συντήρηση στο Firefly III',
'admin_maintanance_expl' => 'Χρήσιμες λειτουργίες για συντήρηση στο Firefly III',
'admin_maintenance_clear_cache' => 'Εκκαθάριση cache',
'admin_notifications' => 'Ειδοποιήσεις διαχειριστή',
'admin_notifications_expl' => 'The following notifications can be enabled or disabled by the administrator. If you want to get these messages over Slack as well, set the "incoming webhook" URL.',
'admin_notification_check_user_new_reg' => 'User gets post-registration welcome message',
'admin_notification_check_admin_new_reg' => 'Administrator(s) get new user registration notification',
'admin_notifications_expl' => 'Οι ακόλουθες ειδοποιήσεις μπορούν να ενεργοποιηθούν ή να απενεργοποιηθούν από το διαχειριστή. Αν θέλετε να παίρνετε αυτά τα μηνύματα και από το Slack, θα πρέπει να ορίστε το "εισερχόμενο webhook" URL.',
'admin_notification_check_user_new_reg' => 'Ο χρήστης παίρνει μήνυμα καλωσορίσματος μετά την εγγραφή',
'admin_notification_check_admin_new_reg' => 'Ο διαχειριστής(ες) λαμβάνει ειδοποίηση εγγραφής νέου χρήστη',
'admin_notification_check_new_version' => 'Μια νέα έκδοση είναι διαθέσιμη',
'admin_notification_check_invite_created' => 'Ένας χρήστης θα λάβει πρόσκληση για το Firefly III',
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed',
'all_invited_users' => 'All invited users',
'save_notification_settings' => 'Save settings',
'notification_settings_saved' => 'The notification settings have been saved',
'admin_notification_check_invite_redeemed' => 'Μια πρόσκληση χρήστη χρησιμοποιήθηκε',
'all_invited_users' => 'Όλοι οι προσκεκλημένοι χρήστες',
'save_notification_settings' => 'Αποθήκευση ρυθμίσεων',
'notification_settings_saved' => 'Οι ρυθμίσεις ειδοποιήσεων έχουν αποθηκευτεί',
'split_transaction_title' => 'Περιγραφή της συναλλαγής με διαχωρισμό',
@ -2572,8 +2572,8 @@ return [
'no_bills_create_default' => 'Δημιουργία νέου πάγιου έξοδου',
// recurring transactions
'create_right_now' => 'Create right now',
'no_new_transaction_in_recurrence' => 'No new transaction was created. Perhaps it was already fired for this date?',
'create_right_now' => 'Δημιουργία αυτή τη στιγμή',
'no_new_transaction_in_recurrence' => 'Δε δημιουργήθηκε καμία νέα συναλλαγή. Μήπως ενεργοποιήθηκε ήδη για αυτή την ημερομηνία;',
'recurrences' => 'Επαναλαμβανόμενες συναλλαγές',
'repeat_until_in_past' => 'Αυτή η επαναλαμβανόμενη συναλλαγή σταμάτησε να επαναλαμβάνεται στις :date.',
'recurring_calendar_view' => 'Ημερολόγιο',
@ -2688,25 +2688,25 @@ return [
'placeholder' => '[Placeholder]',
// audit log entries
'audit_log_entries' => 'Audit log entries',
'ale_action_log_add' => 'Added :amount to piggy bank ":name"',
'ale_action_log_remove' => 'Removed :amount from piggy bank ":name"',
'ale_action_clear_budget' => 'Removed from budget',
'ale_action_clear_category' => 'Removed from category',
'ale_action_clear_notes' => 'Removed notes',
'ale_action_clear_tag' => 'Cleared tag',
'ale_action_clear_all_tags' => 'Cleared all tags',
'ale_action_set_bill' => 'Linked to bill',
'ale_action_set_budget' => 'Set budget',
'ale_action_set_category' => 'Set category',
'ale_action_set_source' => 'Set source account',
'ale_action_set_destination' => 'Set destination account',
'ale_action_update_transaction_type' => 'Changed transaction type',
'ale_action_update_notes' => 'Changed notes',
'ale_action_update_description' => 'Changed description',
'audit_log_entries' => 'Έλεγχος καταχώρησης',
'ale_action_log_add' => 'Προστέθηκαν :amount στον κουμπαρά ":name"',
'ale_action_log_remove' => 'Αφαιρέθηκαν :amount από τον κουμπαρά ":name"',
'ale_action_clear_budget' => 'Αφαιρέθηκε από τον προϋπολογισμό',
'ale_action_clear_category' => 'Αφαιρέθηκε από την κατηγορία',
'ale_action_clear_notes' => 'Αφαιρέθηκαν σημειώσεις',
'ale_action_clear_tag' => 'Αφαίρεση ετικέτας',
'ale_action_clear_all_tags' => 'Αφαίρεση όλων των ετικετών',
'ale_action_set_bill' => 'Σύνδεση με πάγιο έξοδο',
'ale_action_set_budget' => 'Ορισμός προϋπολογισμού',
'ale_action_set_category' => 'Ορισμός κατηγορίας',
'ale_action_set_source' => 'Ορισμός λογαριασμού προέλευσης',
'ale_action_set_destination' => 'Ορισμός λογαριασμού προορισμού',
'ale_action_update_transaction_type' => 'Αλλαγή τύπου συναλλαγής',
'ale_action_update_notes' => 'Αλλαγή σημειώσεων',
'ale_action_update_description' => 'Αλλαγή περιγραφής',
'ale_action_add_to_piggy' => 'Κουμπαράς',
'ale_action_remove_from_piggy' => 'Κουμπαράς',
'ale_action_add_tag' => 'Added tag',
'ale_action_add_tag' => 'Προστέθηκε ετικέτα',
];

View File

@ -150,7 +150,7 @@ return [
'start' => 'Αρχή του εύρους',
'end' => 'Τέλος του εύρους',
'delete_account' => 'Διαγραφή λογαριασμού ":name"',
'delete_webhook' => 'Delete webhook ":title"',
'delete_webhook' => 'Διαγραφή του webhook ":title"',
'delete_bill' => 'Διαγραφή πάγιου έξοδου ":name"',
'delete_budget' => 'Διαγραφή προϋπολογισμού ":name"',
'delete_category' => 'Διαγραφή κατηγορίας ":name"',
@ -171,7 +171,7 @@ return [
'object_group_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε την ομάδα με τίτλο ":title";',
'ruleGroup_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε την ομάδα κανόνων με τίτλο ":title";',
'budget_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε τον προϋπολογισμό με όνομα ":name";',
'webhook_areYouSure' => 'Are you sure you want to delete the webhook named ":title"?',
'webhook_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε το webhook με τίτλο ":title";',
'category_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε την κατηγορία με όνομα ":name";',
'recurring_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε την επαναλαμβανόμενη συναλλαγή με τίτλο ":title";',
'currency_areYouSure' => 'Είστε σίγουροι ότι θέλετε να διαγράψετε το νόμισμα με όνομα ":name";',

View File

@ -66,9 +66,9 @@ return [
'require_currency_info' => 'Το περιεχόμενο αυτού του πεδίου δεν είναι έγκυρη χωρίς νομισματικές πληροφορίες.',
'not_transfer_account' => 'Αυτός ο λογαριασμός δεν είναι λογαριασμός που μπορεί να χρησιμοποιηθεί για συναλλαγές.',
'require_currency_amount' => 'Το περιεχόμενο αυτού του πεδίου δεν είναι έγκυρο χωρίς πληροφορίες ετερόχθονος ποσού.',
'require_foreign_currency' => 'This field requires a number',
'require_foreign_dest' => 'This field value must match the currency of the destination account.',
'require_foreign_src' => 'This field value must match the currency of the source account.',
'require_foreign_currency' => 'Αυτό το πεδίο απαιτεί έναν αριθμό',
'require_foreign_dest' => 'Αυτή η τιμή πεδίου πρέπει να ταιριάζει με το νόμισμα του λογαριασμού προορισμού.',
'require_foreign_src' => 'Αυτή η τιμή πεδίου πρέπει να ταιριάζει με το νόμισμα του λογαριασμού προέλευσης.',
'equal_description' => 'Η περιγραφή της συναλλαγής δεν πρέπει να ισούται με καθολική περιγραφή.',
'file_invalid_mime' => 'Το αρχείο ":name" είναι τύπου ":mime" που δεν είναι αποδεκτός ως νέας μεταφόρτωσης.',
'file_too_large' => 'Το αρχείο ":name" είναι πολύ μεγάλο.',
@ -169,8 +169,8 @@ return [
'unique_piggy_bank_for_user' => 'Το όνομα του κουμπαρά πρέπει να είναι μοναδικό.',
'unique_object_group' => 'Το όνομα της ομάδας πρέπει να είναι μοναδικό',
'starts_with' => 'Η τιμή πρέπει να ξεκινά με :values.',
'unique_webhook' => 'You already have a webhook with this combination of URL, trigger, response and delivery.',
'unique_existing_webhook' => 'You already have another webhook with this combination of URL, trigger, response and delivery.',
'unique_webhook' => 'Έχετε ήδη ένα webhook με αυτόν τον συνδυασμό URL, ενεργοποίησης, απόκρισης και παράδοσης.',
'unique_existing_webhook' => 'Έχετε ήδη ένα άλλο webhook με αυτόν τον συνδυασμό URL, ενεργοποίησης, απόκρισης και παράδοσης.',
'same_account_type' => 'Και οι δύο λογαριασμοί πρέπει να έχουν τον ίδιο τύπο λογαριασμού',
'same_account_currency' => 'Και οι δύο λογαριασμοί πρέπει να έχουν την ίδια ρύθμιση νομίσματος',
@ -285,7 +285,7 @@ return [
'auto_budget_period_mandatory' => 'Η περίοδος αυτόματου προϋπολογισμού είναι υποχρεωτικό πεδίο.',
// no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'no_access_user_group' => 'Δεν έχετε τα σωστά δικαιώματα πρόσβασης για αυτή τη διαχείριση.',
];
/*

View File

@ -271,8 +271,8 @@ return [
'generic_invalid_source' => 'No puedes usar esta cuenta como cuenta de origen.',
'generic_invalid_destination' => 'No puede usar esta cuenta como cuenta de destino.',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => 'Debe indicar la información de la cuenta de origen o un número de registro de transacción.',
'generic_no_destination' => 'Debe indicar la información de la cuenta de destino o un número de registro de transacción.',
'gte.numeric' => ':attribute debe ser mayor o igual que :value.',
'gt.numeric' => 'El :attribute debe ser mayor que :value.',

View File

@ -271,8 +271,8 @@ return [
'generic_invalid_source' => 'Vous ne pouvez pas utiliser ce compte comme compte source.',
'generic_invalid_destination' => 'Vous ne pouvez pas utiliser ce compte comme compte de destination.',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => 'Vous devez saisir les informations du compte source ou saisir un ID de journal d\'opération.',
'generic_no_destination' => 'Vous devez saisir les informations du compte destination ou saisir un ID de journal d\'opération.',
'gte.numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.',
'gt.numeric' => 'Le champ :attribute doit être plus grand que :value.',

View File

@ -249,36 +249,36 @@ return [
'webhook_trigger_DESTROY_TRANSACTION' => 'After transaction delete',
'webhook_response_TRANSACTIONS' => 'Transaction details',
'webhook_response_ACCOUNTS' => 'Account details',
'webhook_response_none_NONE' => 'No details',
'webhook_response_none_NONE' => '詳細なし',
'webhook_delivery_JSON' => 'JSON',
'inspect' => 'Inspect',
'create_new_webhook' => 'Create new webhook',
'webhooks_create_breadcrumb' => 'Create new webhook',
'create_new_webhook' => 'Webhookを作成',
'webhooks_create_breadcrumb' => 'Webhookを作成',
'webhook_trigger_form_help' => 'Indicate on what event the webhook will trigger',
'webhook_response_form_help' => 'Indicate what the webhook must submit to the URL.',
'webhook_delivery_form_help' => 'Which format the webhook must deliver data in.',
'webhook_active_form_help' => 'The webhook must be active or it won\'t be called.',
'stored_new_webhook' => 'Stored new webhook ":title"',
'delete_webhook' => 'Delete webhook',
'delete_webhook' => 'Webhook を削除する',
'deleted_webhook' => 'Deleted webhook ":title"',
'edit_webhook' => 'Edit webhook ":title"',
'updated_webhook' => 'Updated webhook ":title"',
'edit_webhook_js' => 'Edit webhook "{title}"',
'show_webhook' => 'Webhook ":title"',
'webhook_was_triggered' => 'The webhook was triggered on the indicated transaction. Please wait for results to appear.',
'webhook_messages' => 'Webhook message',
'view_message' => 'View message',
'view_attempts' => 'View failed attempts',
'message_content_title' => 'Webhook message content',
'webhook_messages' => 'Webhook メッセージ',
'view_message' => 'メッセージを見る',
'view_attempts' => '失敗した試行の表示',
'message_content_title' => 'Webhook メッセージの内容',
'message_content_help' => 'This is the content of the message that was sent (or tried) using this webhook.',
'attempt_content_title' => 'Webhook attempts',
'attempt_content_title' => 'Webhook の試行',
'attempt_content_help' => 'These are all the unsuccessful attempts of this webhook message to submit to the configured URL. After some time, Firefly III will stop trying.',
'no_attempts' => 'There are no unsuccessful attempts. That\'s a good thing!',
'webhook_attempt_at' => 'Attempt at {moment}',
'logs' => 'Logs',
'response' => 'Response',
'logs' => 'ログ',
'response' => 'レスポンス',
'visit_webhook_url' => 'Visit webhook URL',
'reset_webhook_secret' => 'Reset webhook secret',
'reset_webhook_secret' => 'Webhook のシークレットをリセット',
'webhook_stored_link' => '<a href="webhooks/show/{ID}">Webhook #{ID} ("{title}")</a> has been stored.',
'webhook_updated_link' => '<a href="webhooks/show/{ID}">Webhook #{ID}</a> ("{title}") has been updated.',
@ -1563,8 +1563,8 @@ return [
'title_transfer' => '送金',
'title_transfers' => '送金',
'submission_options' => 'Submission options',
'apply_rules_checkbox' => 'Apply rules',
'fire_webhooks_checkbox' => 'Fire webhooks',
'apply_rules_checkbox' => 'ルールを適用',
'fire_webhooks_checkbox' => 'Webhook を実行する',
// convert stuff:
'convert_is_already_type_Withdrawal' => 'この取引はすでに出金です',
@ -2299,7 +2299,7 @@ return [
'no_tags' => '(タグなし)',
// piggy banks:
'event_history' => 'Event history',
'event_history' => 'イベント履歴',
'add_money_to_piggy' => '貯金箱「:name」にお金を追加',
'piggy_bank' => '貯金箱',
'new_piggy_bank' => '新しい貯金箱',
@ -2371,10 +2371,10 @@ return [
// administration
'invite_is_already_redeemed' => 'The invite to ":address" has already been redeemed.',
'invite_is_deleted' => 'The invite to ":address" has been deleted.',
'invite_new_user_title' => 'Invite new user',
'invite_new_user_title' => 'ユーザーを招待する',
'invite_new_user_text' => 'As an administrator, you can invite users to register on your Firefly III administration. Using the direct link you can share with them, they will be able to register an account. The invited user and their invite link will appear in the table below. You are free to share the invitation link with them.',
'invited_user_mail' => 'Email address',
'invite_user' => 'Invite user',
'invited_user_mail' => 'メールアドレス',
'invite_user' => 'ユーザーを招待する',
'user_is_invited' => 'Email address ":address" was invited to Firefly III',
'administration' => '管理',
'code_already_used' => 'Invite code has been used',
@ -2424,7 +2424,7 @@ return [
'admin_notification_check_invite_created' => 'A user is invited to Firefly III',
'admin_notification_check_invite_redeemed' => 'A user invitation is redeemed',
'all_invited_users' => 'All invited users',
'save_notification_settings' => 'Save settings',
'save_notification_settings' => '設定を保存する',
'notification_settings_saved' => 'The notification settings have been saved',
@ -2697,15 +2697,15 @@ return [
'ale_action_clear_tag' => 'Cleared tag',
'ale_action_clear_all_tags' => 'Cleared all tags',
'ale_action_set_bill' => 'Linked to bill',
'ale_action_set_budget' => 'Set budget',
'ale_action_set_category' => 'Set category',
'ale_action_set_budget' => '予算を設定する',
'ale_action_set_category' => 'カテゴリを設定する',
'ale_action_set_source' => 'Set source account',
'ale_action_set_destination' => 'Set destination account',
'ale_action_update_transaction_type' => 'Changed transaction type',
'ale_action_update_notes' => 'Changed notes',
'ale_action_update_description' => 'Changed description',
'ale_action_add_to_piggy' => 'Piggy bank',
'ale_action_remove_from_piggy' => 'Piggy bank',
'ale_action_add_to_piggy' => '貯金箱',
'ale_action_remove_from_piggy' => '貯金箱',
'ale_action_add_tag' => 'Added tag',
];

View File

@ -1122,7 +1122,7 @@ return [
'rule_trigger_not_budget_is' => 'Budsjett er ikke ":trigger_value"',
'rule_trigger_not_budget_contains' => 'Budsjett inneholder ikke ":trigger_value"',
'rule_trigger_not_budget_ends' => 'Budsjett slutter ikke på ":trigger_value"',
'rule_trigger_not_budget_starts' => 'Budget does not start with ":trigger_value"',
'rule_trigger_not_budget_starts' => 'Budsjettet starter ikke med ":trigger_value"',
'rule_trigger_not_bill_is' => 'Regningen er ikke ":trigger_value"',
'rule_trigger_not_bill_contains' => 'Regningen inneholder ikke ":trigger_value"',
'rule_trigger_not_bill_ends' => 'Regningen slutter ikke på ":trigger_value"',
@ -1302,11 +1302,11 @@ return [
// preferences
'dark_mode_option_browser' => 'Let your browser decide',
'dark_mode_option_light' => 'Always light',
'dark_mode_option_dark' => 'Always dark',
'dark_mode_option_browser' => 'La nettleseren din avgjøre',
'dark_mode_option_light' => 'Alltid lys',
'dark_mode_option_dark' => 'Alltid mørk',
'equal_to_language' => '(likt språk)',
'dark_mode_preference' => 'Dark mode',
'dark_mode_preference' => 'Mørk modus',
'dark_mode_preference_help' => 'Tell Firefly III when to use dark mode.',
'pref_home_screen_accounts' => 'Startskjermkontoer',
'pref_home_screen_accounts_help' => 'Hvilke kontoer skal vises på startsiden?',

View File

@ -66,9 +66,9 @@ return [
'require_currency_info' => 'Innholdet i dette feltet er ugyldig uten valutainformasjon.',
'not_transfer_account' => 'Denne kontoen er ikke en konto som kan benyttes for overføringer.',
'require_currency_amount' => 'Innholdet i dette feltet er ugyldig uten utenlandsk beløpsinformasjon.',
'require_foreign_currency' => 'This field requires a number',
'require_foreign_dest' => 'This field value must match the currency of the destination account.',
'require_foreign_src' => 'This field value must match the currency of the source account.',
'require_foreign_currency' => 'Dette feltet krever et tall',
'require_foreign_dest' => 'Denne feltverdien må samsvare med valutaen til målkontoen.',
'require_foreign_src' => 'Denne feltverdien må samsvare med valutaen til kildekontoen.',
'equal_description' => 'Transaksjonsbeskrivelsen bør ikke være lik global beskrivelse.',
'file_invalid_mime' => 'Kan ikke akseptere fil ":name" av typen ":mime" for opplasting.',
'file_too_large' => '":name"-filen er for stor.',
@ -271,8 +271,8 @@ return [
'generic_invalid_source' => 'Du kan ikke bruke denne kontoen som kildekonto.',
'generic_invalid_destination' => 'Du kan ikke bruke denne kontoen som destinasjonskonto.',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => 'Du må sende inn kontoinformasjon eller sende inn transaksjons-journal-ID.',
'generic_no_destination' => 'Du må sende inn kontoinformasjon om mottakerkontoen, eller sende inn en transaksjons-journal-ID.',
'gte.numeric' => ':attribute må være større enn eller lik :value.',
'gt.numeric' => ':attribute må være større enn :value.',

View File

@ -271,8 +271,8 @@ return [
'generic_invalid_source' => 'Nie możesz użyć tego konta jako konta źródłowego.',
'generic_invalid_destination' => 'Nie możesz użyć tego konta jako konta docelowego.',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => 'Musisz przesłać informacje o koncie źródłowym lub przesłać identyfikator dziennika transakcji.',
'generic_no_destination' => 'Musisz przesłać informacje o koncie docelowym lub przesłać identyfikator dziennika transakcji.',
'gte.numeric' => ':attribute musi być większy lub równy :value.',
'gt.numeric' => ':attribute musi być większy niż :value.',

View File

@ -505,33 +505,33 @@ return [
'search_modifier_category_contains' => '分类包含":value"',
'search_modifier_not_category_contains' => '分类不包含":value"',
'search_modifier_category_ends' => '分类结尾为“:value”',
'search_modifier_not_category_ends' => 'Category does not end on ":value"',
'search_modifier_not_category_ends' => '分类结尾不为":value"',
'search_modifier_category_starts' => '分类开头为":value"',
'search_modifier_not_category_starts' => 'Category does not start with ":value"',
'search_modifier_not_category_starts' => '分类开头不为":value"',
'search_modifier_budget_contains' => '预算包含":value"',
'search_modifier_not_budget_contains' => 'Budget does not contain ":value"',
'search_modifier_not_budget_contains' => '预算不包含":value"',
'search_modifier_budget_ends' => '预算结尾为":value"',
'search_modifier_not_budget_ends' => 'Budget does not end on ":value"',
'search_modifier_not_budget_ends' => '预算结尾不为":value"',
'search_modifier_budget_starts' => '预算开头为":value"',
'search_modifier_not_budget_starts' => 'Budget does not start with ":value"',
'search_modifier_not_budget_starts' => '预算开头不为":value"',
'search_modifier_bill_contains' => '账单包含":value"',
'search_modifier_not_bill_contains' => 'Bill does not contain ":value"',
'search_modifier_not_bill_contains' => '账单不包含":value"',
'search_modifier_bill_ends' => '账单结尾为":value"',
'search_modifier_not_bill_ends' => 'Bill does not end on ":value"',
'search_modifier_not_bill_ends' => '账单结尾不为":value"',
'search_modifier_bill_starts' => '账单开头为":value"',
'search_modifier_not_bill_starts' => 'Bill does not start with ":value"',
'search_modifier_not_bill_starts' => '账单开头不为":value"',
'search_modifier_external_id_contains' => '外部ID包含 ":value"',
'search_modifier_not_external_id_contains' => '外部ID不包含 ":value"',
'search_modifier_external_id_ends' => '外部ID结尾是 ":value"',
'search_modifier_not_external_id_ends' => '外部ID结尾不是 ":value"',
'search_modifier_external_id_starts' => '外部ID开头是 ":value"',
'search_modifier_not_external_id_starts' => '外部ID开头不是 ":value"',
'search_modifier_internal_reference_contains' => 'Internal reference contains ":value"',
'search_modifier_not_internal_reference_contains' => 'Internal reference does not contain ":value"',
'search_modifier_internal_reference_ends' => 'Internal reference ends with ":value"',
'search_modifier_internal_reference_starts' => 'Internal reference starts with ":value"',
'search_modifier_not_internal_reference_ends' => 'Internal reference does not end with ":value"',
'search_modifier_not_internal_reference_starts' => 'Internal reference does not start with ":value"',
'search_modifier_internal_reference_contains' => '内部引用包含":value"',
'search_modifier_not_internal_reference_contains' => '内部引用不含":value"',
'search_modifier_internal_reference_ends' => '内部引用结尾为":value"',
'search_modifier_internal_reference_starts' => '内部引用开头为":value"',
'search_modifier_not_internal_reference_ends' => '内部引用结尾不为":value"',
'search_modifier_not_internal_reference_starts' => '内部引用开头不为":value"',
'search_modifier_external_url_is' => '外部URL是“:value”',
'search_modifier_not_external_url_is' => '外部URL不是 ":value"',
'search_modifier_external_url_contains' => '外部URL包含 ":value"',
@ -543,12 +543,12 @@ return [
'search_modifier_has_no_attachments' => '交易没有附件',
'search_modifier_not_has_no_attachments' => '交易包含附件',
'search_modifier_not_has_attachments' => '交易没有附件',
'search_modifier_account_is_cash' => 'Either account is the "(cash)" account.',
'search_modifier_not_account_is_cash' => 'Neither account is the "(cash)" account.',
'search_modifier_account_is_cash' => '其中一个账户为现金账户',
'search_modifier_not_account_is_cash' => '两个账户都不为现金账户',
'search_modifier_journal_id' => '日志ID是":value"',
'search_modifier_not_journal_id' => '日志ID不是":value"',
'search_modifier_recurrence_id' => 'The recurring transaction ID is ":value"',
'search_modifier_not_recurrence_id' => 'The recurring transaction ID is not ":value"',
'search_modifier_recurrence_id' => '定期交易ID为":value"',
'search_modifier_not_recurrence_id' => '定期交易ID不为":value"',
'search_modifier_foreign_amount_is' => 'The foreign amount is ":value"',
'search_modifier_not_foreign_amount_is' => 'The foreign amount is not ":value"',
'search_modifier_foreign_amount_less' => 'The foreign amount is less than ":value"',

View File

@ -271,8 +271,8 @@ return [
'generic_invalid_source' => '您不能使用此账户作为来源账户',
'generic_invalid_destination' => '您不能使用此账户作为目标账户',
'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.',
'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.',
'generic_no_source' => '必须提供来源账户或者交易ID',
'generic_no_destination' => '必须提供目标账户或者交易ID',
'gte.numeric' => ':attribute 必须大于或等于 :value',
'gt.numeric' => ':attribute 必须大于 :value',

View File

@ -24,7 +24,7 @@
<form action="{{ route('two-factor.submit') }}" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="input-group mb-3">
<input type="text" name="one_time_password" inputmode="numeric" autocomplete="one-time-code" class="form-control" placeholder="{{ 'two_factor_code_here'|_ }}" />
<input type="text" name="one_time_password" inputmode="numeric" autocomplete="one-time-code" class="form-control" placeholder="{{ 'two_factor_code_here'|_ }}" autofocus />
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-calculator"></span>

View File

@ -18,11 +18,16 @@ Debug information generated at {{ now }} for Firefly III version **{{ FF_VERSION
| --- | --- |
| Firefly III | {{ FF_VERSION }} |
| Firefly III API | {{ config('firefly.api_version') }} |
| Build | {{ buildNr }}, {{ buildDate }} |
{% if isDocker %}| Build | {{ buildNr }}, {{ buildDate }} |
| Base Build | {{ baseBuildNr }}, {{ baseBuildDate }} |
{% endif %}
| DB version | {{ foundDBversion }} (exp. {{ expectedDBversion}}) |
{% if not isDocker %}| Docker | No |
{% endif %}
| PHP | `{{ phpVersion }}` |
| Host | `{{ phpOs }}` |
| System info | Value |
| --- | --- |
| System TZ | {{ tz }} |

711
yarn.lock

File diff suppressed because it is too large Load Diff