mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-01-05 18:11:22 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5085a384dc | ||
|
|
07abfd78e1 | ||
|
|
8b90d2297d | ||
|
|
28479ef2ed | ||
|
|
9b03ae160d | ||
|
|
5303321952 | ||
|
|
906fca7e9e | ||
|
|
f64e1f3c1b | ||
|
|
c8af3684c6 | ||
|
|
e3474bb075 | ||
|
|
8140613a29 | ||
|
|
00d5b4d29d | ||
|
|
251a347a42 | ||
|
|
7fb090392f | ||
|
|
13b96bb3b6 | ||
|
|
c318790bbf | ||
|
|
0d969dd42a | ||
|
|
4e70601d19 | ||
|
|
e4a6ff293a | ||
|
|
d0edae76f2 | ||
|
|
9eaacf30ad | ||
|
|
40111ed25e | ||
|
|
506f972c75 | ||
|
|
843f3c9b45 | ||
|
|
6c2c2ca41f | ||
|
|
8315734471 | ||
|
|
2d9b2ab379 | ||
|
|
45f20509f4 | ||
|
|
c5b9c79b82 | ||
|
|
6a196884e5 | ||
|
|
1d9431795b | ||
|
|
b3074b2f6a | ||
|
|
3be65ff806 | ||
|
|
8c0005e460 | ||
|
|
c7128dedf2 | ||
|
|
e5736c822d | ||
|
|
8f6655f85e | ||
|
|
dbe827e3c5 | ||
|
|
5a7f933a5c | ||
|
|
54d5f9a9c3 | ||
|
|
cc682485fc | ||
|
|
f8cb8967d9 | ||
|
|
0480db10ac | ||
|
|
f52c6f7b00 | ||
|
|
7d1f5f5257 | ||
|
|
ea0942b7fe | ||
|
|
3298f2d815 | ||
|
|
fae5cdae50 | ||
|
|
8ffe08bfb9 | ||
|
|
1c2b14868b | ||
|
|
7775a0141b | ||
|
|
831272d971 | ||
|
|
c8c4507d4b | ||
|
|
6a74cd21fb | ||
|
|
eb5eca9fa5 | ||
|
|
0e702fb334 | ||
|
|
626f97cd65 | ||
|
|
f03b0569cf | ||
|
|
82f3a37a3e | ||
|
|
112a27dbd9 | ||
|
|
366eca3173 | ||
|
|
fab0c5bfd9 | ||
|
|
dc3b923258 | ||
|
|
3622d3234a | ||
|
|
cf2c99d986 | ||
|
|
672add8668 | ||
|
|
d5826861a0 | ||
|
|
a8494bd6f0 | ||
|
|
7fa0c14f8c | ||
|
|
9bd22d4252 | ||
|
|
b78f5bd54a | ||
|
|
0949a264b8 | ||
|
|
97a0110931 | ||
|
|
83ec935ac3 | ||
|
|
e46da428eb | ||
|
|
26089f992e | ||
|
|
13233e0893 | ||
|
|
da934575a6 | ||
|
|
6feb04c800 | ||
|
|
d56f97e86d | ||
|
|
a78614f198 | ||
|
|
2b25631611 | ||
|
|
98613b5ea6 | ||
|
|
105ecc4452 | ||
|
|
418a682f7e | ||
|
|
215e5d42a7 | ||
|
|
21d71bd03c | ||
|
|
3184a8536e | ||
|
|
ddf9938c00 | ||
|
|
388d19b99c | ||
|
|
ec03017eca | ||
|
|
0b920b5c64 | ||
|
|
6e0be9a6a2 | ||
|
|
f1798a1c97 | ||
|
|
57cb428105 |
@@ -30,4 +30,4 @@ parameters:
|
||||
- ../bootstrap/app.php
|
||||
|
||||
# The level 8 is the highest level. original was 5
|
||||
level: 2
|
||||
level: 3
|
||||
|
||||
@@ -64,7 +64,7 @@ AUDIT_LOG_LEVEL=info
|
||||
# Use "mysql" for MySQL and MariaDB.
|
||||
# Use "sqlite" for SQLite.
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=fireflyiiidb
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=firefly
|
||||
DB_USERNAME=firefly
|
||||
|
||||
35
.github/lock.yml
vendored
35
.github/lock.yml
vendored
@@ -1,35 +0,0 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 90
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
19
.github/workflows/lock.yml
vendored
Normal file
19
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Lock old issues
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '90'
|
||||
issue-lock-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
@@ -1,24 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* AccountController.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Data\Bulk;
|
||||
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* MoveTransactionsRequest.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Data\Bulk;
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ class StoreRequest extends FormRequest
|
||||
'currency_code' => 'exists:transaction_currencies,code',
|
||||
// auto budget info
|
||||
'auto_budget_type' => 'in:reset,rollover,none',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class CorrectDatabase extends Command
|
||||
'firefly-iii:fix-recurring-transactions',
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
'firefly-iii:fix-transaction-types',
|
||||
'firefly-iii:fix-frontpage-accounts'
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$this->line(sprintf('Now executing %s', $command));
|
||||
|
||||
@@ -71,6 +71,7 @@ class DeleteEmptyJournals extends Command
|
||||
->groupBy('transactions.transaction_journal_id')
|
||||
->get([DB::raw('COUNT(transactions.transaction_journal_id) as the_count'), 'transaction_journal_id']);
|
||||
$total = 0;
|
||||
/** @var Transaction $row */
|
||||
foreach ($set as $row) {
|
||||
$count = (int)$row->the_count;
|
||||
if (1 === $count % 2) {
|
||||
|
||||
106
app/Console/Commands/Correction/FixFrontpageAccounts.php
Normal file
106
app/Console/Commands/Correction/FixFrontpageAccounts.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* FixFrontpageAccounts.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class FixFrontpageAccounts
|
||||
*/
|
||||
class FixFrontpageAccounts extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Fixes a preference that may include deleted accounts or accounts of another type.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:fix-frontpage-accounts';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$preference = Preferences::getForUser($user, 'frontPageAccounts', null);
|
||||
if (null !== $preference) {
|
||||
$this->fixPreference($preference);
|
||||
}
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verifying account preferences took %s seconds', $end));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Preference $preference
|
||||
*/
|
||||
private function fixPreference(Preference $preference): void
|
||||
{
|
||||
$fixed = [];
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
if (null === $preference->user) {
|
||||
return;
|
||||
}
|
||||
$repository->setUser($preference->user);
|
||||
$data = $preference->data;
|
||||
if (is_array($data)) {
|
||||
/** @var string $accountId */
|
||||
foreach ($data as $accountId) {
|
||||
$accountId = (int)$accountId;
|
||||
$account = $repository->findNull($accountId);
|
||||
if (null !== $account) {
|
||||
if (
|
||||
in_array($account->accountType->type, [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], true)
|
||||
&& true === $account->active
|
||||
) {
|
||||
$fixed[] = $account->id;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Preferences::setForUser($preference->user, 'frontPageAccounts', $fixed);
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ class FixGroupAccounts extends Command
|
||||
$res = TransactionJournal
|
||||
::groupBy('transaction_group_id')
|
||||
->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($res as $journal) {
|
||||
if ((int)$journal->the_count > 1) {
|
||||
$groups[] = (int)$journal->transaction_group_id;
|
||||
|
||||
132
app/Console/Commands/Correction/FixPostgresSequences.php
Normal file
132
app/Console/Commands/Correction/FixPostgresSequences.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class FixPostgresSequences
|
||||
*/
|
||||
class FixPostgresSequences extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Fixes issues with PostgreSQL sequences.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:fix-pgsql-sequences';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
|
||||
if (DB::connection()->getName() !== 'pgsql') {
|
||||
$this->info('Command executed successfully.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->line('Going to verify PostgreSQL table sequences.');
|
||||
$tablesToCheck = [
|
||||
'2fa_tokens',
|
||||
'account_meta',
|
||||
'account_types',
|
||||
'accounts',
|
||||
'attachments',
|
||||
'auto_budgets',
|
||||
'available_budgets',
|
||||
'bills',
|
||||
'budget_limits',
|
||||
'budget_transaction',
|
||||
'budget_transaction_journal',
|
||||
'budgets',
|
||||
'categories',
|
||||
'category_transaction',
|
||||
'category_transaction_journal',
|
||||
'configuration',
|
||||
'currency_exchange_rates',
|
||||
'export_jobs',
|
||||
'failed_jobs',
|
||||
'group_journals',
|
||||
'import_jobs',
|
||||
'jobs',
|
||||
'journal_links',
|
||||
'journal_meta',
|
||||
'limit_repetitions',
|
||||
'link_types',
|
||||
'locations',
|
||||
'migrations',
|
||||
'notes',
|
||||
'oauth_clients',
|
||||
'oauth_personal_access_clients',
|
||||
'object_groups',
|
||||
'permissions',
|
||||
'piggy_bank_events',
|
||||
'piggy_bank_repetitions',
|
||||
'piggy_banks',
|
||||
'preferences',
|
||||
'recurrences',
|
||||
'recurrences_meta',
|
||||
'recurrences_repetitions',
|
||||
'recurrences_transactions',
|
||||
'roles',
|
||||
'rt_meta',
|
||||
'rule_actions',
|
||||
'rule_groups',
|
||||
'rule_triggers',
|
||||
'rules',
|
||||
'tag_transaction_journal',
|
||||
'tags',
|
||||
'telemetry',
|
||||
'transaction_currencies',
|
||||
'transaction_groups',
|
||||
'transaction_journals',
|
||||
'transaction_types',
|
||||
'transactions',
|
||||
'users',
|
||||
'webhook_attempts',
|
||||
'webhook_messages',
|
||||
'webhooks',
|
||||
];
|
||||
|
||||
foreach ($tablesToCheck as $tableToCheck) {
|
||||
$this->info(sprintf('Checking the next id sequence for table "%s".', $tableToCheck));
|
||||
|
||||
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
|
||||
$nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first();
|
||||
if(null === $nextId) {
|
||||
$this->line(sprintf('nextval is NULL for table "%s", go to next table.', $tableToCheck));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($nextId->nextval < $highestId->max) {
|
||||
DB::select(sprintf('SELECT setval(\'%s_id_seq\', %d)', $tableToCheck, $highestId->max));
|
||||
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
|
||||
$nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first();
|
||||
if ($nextId->nextval > $highestId->max) {
|
||||
$this->info(sprintf('Table "%s" autoincrement corrected.', $tableToCheck));
|
||||
}
|
||||
if ($nextId->nextval <= $highestId->max) {
|
||||
$this->warn(sprintf('Arff! The nextval sequence is still all screwed up on table "%s".', $tableToCheck));
|
||||
}
|
||||
}
|
||||
if ($nextId->nextval >= $highestId->max) {
|
||||
$this->info(sprintf('Table "%s" autoincrement is correct.', $tableToCheck));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class DecryptDatabase extends Command
|
||||
/**
|
||||
* Tries to decrypt data. Will only throw an exception when the MAC is invalid.
|
||||
*
|
||||
* @param $value
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
|
||||
@@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Console\Commands\VerifiesAccessToken;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
@@ -241,7 +242,7 @@ class ExportData extends Command
|
||||
$accounts = $this->accountRepository->getAccountsByType($types);
|
||||
}
|
||||
// filter accounts,
|
||||
/** @var AccountType $account */
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if (in_array($account->accountType->type, $types, true)) {
|
||||
$final->push($account);
|
||||
|
||||
@@ -64,7 +64,7 @@ class BackToJournals extends Command
|
||||
$this->error('Please run firefly-iii:migrate-to-groups first.');
|
||||
}
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->info('This command has already been executed.');
|
||||
$this->warn('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -144,8 +144,7 @@ class BackToJournals extends Command
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)
|
||||
->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
|
||||
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@ class MigrateToGroups extends Command
|
||||
if ($total > 0) {
|
||||
Log::debug(sprintf('Going to convert %d transaction journals. Please hold..', $total));
|
||||
$this->line(sprintf('Going to convert %d transaction journals. Please hold..', $total));
|
||||
/** @var array $journal */
|
||||
/** @var array $array */
|
||||
foreach ($orphanedJournals as $array) {
|
||||
$this->giveGroup($array);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class UpgradeDatabase extends Command
|
||||
'firefly-iii:fix-recurring-transactions',
|
||||
'firefly-iii:unify-group-accounts',
|
||||
'firefly-iii:fix-transaction-types',
|
||||
'firefly-iii:fix-frontpage-accounts',
|
||||
|
||||
// two report commands
|
||||
'firefly-iii:report-empty-objects',
|
||||
@@ -129,6 +130,11 @@ class UpgradeDatabase extends Command
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
|
||||
$this->line('Fix PostgreSQL sequences.');
|
||||
Artisan::call('firefly-iii:fix-pgsql-sequences');
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
|
||||
$this->line('Now decrypting the database (if necessary)...');
|
||||
Artisan::call('firefly-iii:decrypt-all');
|
||||
$result = Artisan::output();
|
||||
|
||||
@@ -48,38 +48,38 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Exception $exception
|
||||
* @param Throwable $e
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function render($request, Throwable $exception)
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
$route = $request->route();
|
||||
if (null === $route) {
|
||||
return parent::render($request, $exception);
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
$name = $route->getName();
|
||||
if (!auth()->check()) {
|
||||
return parent::render($request, $exception);
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
default:
|
||||
Log::warning(sprintf('GracefulNotFoundHandler cannot handle route with name "%s"', $name));
|
||||
|
||||
return parent::render($request, $exception);
|
||||
return parent::render($request, $e);
|
||||
case 'accounts.show':
|
||||
case 'accounts.show.all':
|
||||
return $this->handleAccount($request, $exception);
|
||||
return $this->handleAccount($request, $e);
|
||||
case 'transactions.show':
|
||||
return $this->handleGroup($request, $exception);
|
||||
return $this->handleGroup($request, $e);
|
||||
case 'attachments.show':
|
||||
case 'attachments.edit':
|
||||
case 'attachments.download':
|
||||
case 'attachments.view':
|
||||
// redirect to original attachment holder.
|
||||
return $this->handleAttachment($request, $exception);
|
||||
return $this->handleAttachment($request, $e);
|
||||
break;
|
||||
case 'bills.show':
|
||||
$request->session()->reflash();
|
||||
@@ -131,7 +131,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
return redirect(route('index'));
|
||||
}
|
||||
|
||||
return parent::render($request, $exception);
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,7 +141,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return Redirector|Response
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleAccount(Request $request, Throwable $exception)
|
||||
{
|
||||
@@ -165,11 +165,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $request
|
||||
* @param Exception $exception
|
||||
* @param Request $request
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return RedirectResponse|\Illuminate\Http\Response|Redirector|Response
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleGroup(Request $request, Throwable $exception)
|
||||
{
|
||||
@@ -209,7 +209,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
* @param Throwable $exception
|
||||
*
|
||||
* @return RedirectResponse|Redirector|Response
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleAttachment(Request $request, Throwable $exception)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ class TransactionCurrencyFactory
|
||||
public function create(array $data): TransactionCurrency
|
||||
{
|
||||
try {
|
||||
/** @var TransactionCurrency $currency */
|
||||
/** @var TransactionCurrency $result */
|
||||
$result = TransactionCurrency::create(
|
||||
[
|
||||
'name' => $data['name'],
|
||||
|
||||
@@ -543,7 +543,7 @@ class TransactionJournalFactory
|
||||
'data' => (string)($data[$field] ?? ''),
|
||||
];
|
||||
|
||||
Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
|
||||
//Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
|
||||
|
||||
/** @var TransactionJournalMetaFactory $factory */
|
||||
$factory = app(TransactionJournalMetaFactory::class);
|
||||
|
||||
@@ -41,7 +41,7 @@ class TransactionJournalMetaFactory
|
||||
*/
|
||||
public function updateOrCreate(array $data): ?TransactionJournalMeta
|
||||
{
|
||||
Log::debug('In updateOrCreate()');
|
||||
//Log::debug('In updateOrCreate()');
|
||||
$value = $data['data'];
|
||||
/** @var TransactionJournalMeta $entry */
|
||||
$entry = $data['journal']->transactionJournalMeta()->where('name', $data['name'])->first();
|
||||
@@ -61,7 +61,7 @@ class TransactionJournalMetaFactory
|
||||
$value = $data['data']->toW3cString();
|
||||
}
|
||||
if ('' === (string)$value) {
|
||||
Log::debug('Is an empty string.');
|
||||
// Log::debug('Is an empty string.');
|
||||
// don't store blank strings.
|
||||
if (null !== $entry) {
|
||||
Log::debug('Will not store empty strings, delete meta value');
|
||||
|
||||
@@ -101,7 +101,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
*/
|
||||
private function getWebhooks(): Collection
|
||||
{
|
||||
return $this->user->webhooks()->where('active', 1)->where('trigger', $this->trigger)->get(['webhooks.*']);
|
||||
return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -207,7 +207,7 @@ trait MetaCollection
|
||||
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
|
||||
}
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
|
||||
$this->query->where('journal_meta.data', '=', sprintf('%s', $externalId));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
->where(
|
||||
static function (EloquentBuilder $q1) {
|
||||
$q1->where('attachments.attachable_type', TransactionJournal::class);
|
||||
$q1->where('attachments.uploaded', 1);
|
||||
$q1->where('attachments.uploaded', true);
|
||||
$q1->orWhereNull('attachments.attachable_type');
|
||||
}
|
||||
);
|
||||
@@ -699,7 +699,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$result = $this->convertToInteger($result);
|
||||
|
||||
$result['reconciled'] = 1 === (int)$result['reconciled'];
|
||||
if (array_key_exists('tag_id', $result)) { // assume the other fields are present as well.
|
||||
if (array_key_exists('tag_id', $result) && null !== $result['tag_id']) { // assume the other fields are present as well.
|
||||
$tagId = (int)$augumentedJournal['tag_id'];
|
||||
$tagDate = null;
|
||||
try {
|
||||
|
||||
@@ -549,8 +549,6 @@ interface GroupCollectorInterface
|
||||
public function withoutCategory(): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
public function withoutNotes(): GroupCollectorInterface;
|
||||
|
||||
@@ -91,7 +91,7 @@ class ReportHelper implements ReportHelperInterface
|
||||
'paid_moments' => [],
|
||||
];
|
||||
|
||||
/** @var Carbon $start */
|
||||
/** @var Carbon $expectedStart */
|
||||
foreach ($expectedDates as $expectedStart) {
|
||||
$expectedEnd = app('navigation')->endOfX($expectedStart, $bill->repeat_freq, null);
|
||||
|
||||
|
||||
@@ -39,9 +39,7 @@ use Log;
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
|
||||
/** @var LinkTypeRepositoryInterface */
|
||||
private $repository;
|
||||
private LinkTypeRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* LinkController constructor.
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Admin;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Update\UpdateTrait;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
@@ -62,8 +63,7 @@ class UpdateController extends Controller
|
||||
* Show page with update options.
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Providers\RouteServiceProvider;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Foundation\Auth\ThrottlesLogins;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -48,7 +49,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*/
|
||||
class LoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
use AuthenticatesUsers, ThrottlesLogins;
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
|
||||
@@ -159,7 +159,6 @@ class IndexController extends Controller
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var TransactionCurrency $currency */
|
||||
$currencyId = $bill['currency_id'];
|
||||
$sums[$groupOrder][$currencyId] = $sums[$groupOrder][$currencyId] ?? [
|
||||
'currency_id' => $currencyId,
|
||||
|
||||
@@ -144,7 +144,7 @@ class ShowController extends Controller
|
||||
*/
|
||||
public function show(Request $request, Budget $budget)
|
||||
{
|
||||
/** @var Carbon $start */
|
||||
/** @var Carbon $allStart */
|
||||
$allStart = session('first', Carbon::now()->startOfYear());
|
||||
$allEnd = today();
|
||||
$page = (int)$request->get('page');
|
||||
|
||||
@@ -108,7 +108,7 @@ class BudgetController extends Controller
|
||||
$currencies = [];
|
||||
$defaultEntries = [];
|
||||
while ($end >= $loopStart) {
|
||||
/** @var Carbon $currentEnd */
|
||||
/** @var Carbon $loopEnd */
|
||||
$loopEnd = app('navigation')->endOfPeriod($loopStart, $step);
|
||||
$spent = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection);
|
||||
$label = trim(app('navigation')->periodShow($loopStart, $step));
|
||||
|
||||
@@ -76,7 +76,7 @@ class CategoryController extends Controller
|
||||
$cache->addProperty('chart.category.all');
|
||||
$cache->addProperty($category->id);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
/** @var CategoryRepositoryInterface $repository */
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
@@ -125,7 +125,7 @@ class CategoryController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.category.frontpage');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
$frontPageGenerator = new FrontpageChartGenerator($start, $end);
|
||||
@@ -270,7 +270,7 @@ class CategoryController extends Controller
|
||||
$cache->addProperty('chart.category.period.no-cat');
|
||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
$data = $this->reportPeriodChart($accounts, $start, $end, null);
|
||||
|
||||
@@ -283,8 +283,8 @@ class CategoryController extends Controller
|
||||
* Chart for a specific period.
|
||||
* TODO test method, for category refactor.
|
||||
*
|
||||
* @param Category $category
|
||||
* @param $date
|
||||
* @param Category $category
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
@@ -294,7 +294,7 @@ class CategoryController extends Controller
|
||||
$start = app('navigation')->startOfPeriod($date, $range);
|
||||
$end = session()->get('end');
|
||||
if ($end < $start) {
|
||||
[$end, $start] = [$start, $end];
|
||||
[$end, $start] = [$start, $end];
|
||||
}
|
||||
|
||||
$cache = new CacheProperties;
|
||||
@@ -303,7 +303,7 @@ class CategoryController extends Controller
|
||||
$cache->addProperty($category->id);
|
||||
$cache->addProperty('chart.category.period-chart');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
/** @var WholePeriodChartGenerator $chartGenerator */
|
||||
|
||||
@@ -140,10 +140,8 @@ class CategoryReportController extends Controller
|
||||
* @param Collection $categories
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param string $others
|
||||
*
|
||||
* @return JsonResponse
|
||||
*
|
||||
*/
|
||||
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
|
||||
@@ -80,9 +80,9 @@ class PiggyBankController extends Controller
|
||||
$locale = app('steam')->getLocale();
|
||||
|
||||
// get first event or start date of piggy bank or today
|
||||
$startDate = $piggyBank->start_date ?? today(config('app.timezone'));
|
||||
$startDate = $piggyBank->startdate ?? today(config('app.timezone'));
|
||||
|
||||
/** @var PiggyBankEvent $first */
|
||||
/** @var PiggyBankEvent $firstEvent */
|
||||
$firstEvent = $set->first();
|
||||
$firstDate = null === $firstEvent ? new Carbon : $firstEvent->date;
|
||||
|
||||
|
||||
@@ -52,12 +52,10 @@ class TransactionController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $objectType
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function budgets(Carbon $start, Carbon $end)
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ class HelpController extends Controller
|
||||
/**
|
||||
* Show help for a route.
|
||||
*
|
||||
* @param $route
|
||||
* @param string $route
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
|
||||
@@ -30,54 +30,9 @@ use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Class AutoCompleteController.
|
||||
*
|
||||
* TODO autocomplete for transaction types.
|
||||
*
|
||||
*/
|
||||
class AutoCompleteController extends Controller
|
||||
{
|
||||
/**
|
||||
* Searches in the titles of all transaction journals.
|
||||
* The result is limited to the top 15 unique results.
|
||||
*
|
||||
* If the query is numeric, it will append the journal with that particular ID.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function allJournalsWithID(Request $request): JsonResponse
|
||||
{
|
||||
$search = (string)$request->get('search');
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
|
||||
/** @var TransactionGroupRepositoryInterface $groupRepos */
|
||||
$groupRepos = app(TransactionGroupRepositoryInterface::class);
|
||||
|
||||
$result = $repository->searchJournalDescriptions($search);
|
||||
$array = [];
|
||||
if (is_numeric($search)) {
|
||||
// search for group, not journal.
|
||||
$firstResult = $groupRepos->find((int)$search);
|
||||
if (null !== $firstResult) {
|
||||
// group may contain multiple journals, each a result:
|
||||
foreach ($firstResult->transactionJournals as $journal) {
|
||||
$array[] = $journal->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
// if not numeric, search ahead!
|
||||
|
||||
// limit and unique
|
||||
$limited = $result->slice(0, 15);
|
||||
$array = array_merge($array, $limited->toArray());
|
||||
foreach ($array as $index => $item) {
|
||||
// give another key for consistency
|
||||
$array[$index]['name'] = sprintf('#%d: %s', $item['transaction_group_id'], $item['description']);
|
||||
}
|
||||
|
||||
return response()->json($array);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ class PreferencesController extends Controller
|
||||
|
||||
// same for locale:
|
||||
if (!auth()->user()->hasRole('demo')) {
|
||||
/** @var Preference $currentLocale */
|
||||
/** @var Preference $locale */
|
||||
$locale = $request->get('locale');
|
||||
app('preferences')->set('locale', $locale);
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ class ProfileController extends Controller
|
||||
$user = auth()->user();
|
||||
$isInternalAuth = $this->internalAuth;
|
||||
$isInternalIdentity = $this->internalIdentity;
|
||||
$count = DB::table('oauth_clients')->where('personal_access_client', 1)->whereNull('user_id')->count();
|
||||
$count = DB::table('oauth_clients')->where('personal_access_client', true)->whereNull('user_id')->count();
|
||||
$subTitle = $user->email;
|
||||
$userId = $user->id;
|
||||
$enabled2FA = null !== $user->mfa_secret;
|
||||
|
||||
@@ -148,13 +148,13 @@ class CreateController extends Controller
|
||||
RecurrenceRepetition::WEEKEND_TO_MONDAY => (string)trans('firefly.jump_to_monday'),
|
||||
];
|
||||
|
||||
/** @var Transaction $source */
|
||||
/** @var Transaction $dest */
|
||||
|
||||
// fill prefilled with journal info
|
||||
$type = strtolower($journal->transactionType->type);
|
||||
|
||||
/** @var Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
/** @var Transaction $dest */
|
||||
$dest = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$category = $journal->categories()->first() ? $journal->categories()->first()->name : '';
|
||||
$budget = $journal->budgets()->first() ? $journal->budgets()->first()->id : 0;
|
||||
|
||||
@@ -122,7 +122,7 @@ class TagController extends Controller
|
||||
foreach ($earned as $currency) {
|
||||
$currencyId = $currency['currency_id'];
|
||||
|
||||
/** @var array $category */
|
||||
/** @var array $tag */
|
||||
foreach ($currency['tags'] as $tag) {
|
||||
foreach ($tag['transaction_journals'] as $journal) {
|
||||
$destinationId = $journal['destination_account_id'];
|
||||
|
||||
@@ -158,6 +158,7 @@ class SelectController extends Controller
|
||||
$rule->ruleTriggers = $triggers;
|
||||
|
||||
// create new rule engine:
|
||||
/** @var RuleEngineInterface $newRuleEngine */
|
||||
$newRuleEngine = app(RuleEngineInterface::class);
|
||||
|
||||
// set rules:
|
||||
|
||||
@@ -64,6 +64,7 @@ class InstallController extends Controller
|
||||
$this->upgradeCommands = [
|
||||
// there are 3 initial commands
|
||||
'migrate' => ['--seed' => true, '--force' => true],
|
||||
'firefly-iii:fix-pgsql-sequences' => [],
|
||||
'firefly-iii:decrypt-all' => [],
|
||||
'firefly-iii:restore-oauth-keys' => [],
|
||||
'generate-keys' => [], // an exception :(
|
||||
@@ -105,6 +106,7 @@ class InstallController extends Controller
|
||||
'firefly-iii:fix-recurring-transactions' => [],
|
||||
'firefly-iii:unify-group-accounts' => [],
|
||||
'firefly-iii:fix-transaction-types' => [],
|
||||
'firefly-iii:fix-frontpage-accounts' => [],
|
||||
|
||||
// final command to set latest version in DB
|
||||
'firefly-iii:set-latest-version' => ['--james-is-cool' => true],
|
||||
|
||||
@@ -77,6 +77,10 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function index(Request $request, string $objectType, Carbon $start = null, Carbon $end = null)
|
||||
{
|
||||
if('transfers' === $objectType) {
|
||||
$objectType = 'transfer';
|
||||
}
|
||||
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.' . $objectType);
|
||||
$types = config('firefly.transactionTypesByType.' . $objectType);
|
||||
$page = (int)$request->get('page');
|
||||
|
||||
@@ -34,7 +34,7 @@ use Illuminate\Http\Request;
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/** @var int The headers to check. */
|
||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
//protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
|
||||
/**
|
||||
* TrustProxies constructor.
|
||||
|
||||
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
@@ -87,6 +88,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static Builder|Account withTrashed()
|
||||
* @method static Builder|Account withoutTrashed()
|
||||
* @mixin Eloquent
|
||||
* @property Carbon $lastActivityDate
|
||||
*/
|
||||
class Account extends Model
|
||||
{
|
||||
|
||||
@@ -74,6 +74,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @method static Builder|Budget withTrashed()
|
||||
* @method static Builder|Budget withoutTrashed()
|
||||
* @mixin Eloquent
|
||||
* @property string $email
|
||||
*/
|
||||
class Budget extends Model
|
||||
{
|
||||
|
||||
@@ -44,6 +44,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property Carbon $lastActivity
|
||||
* @property bool $encrypted
|
||||
* @property-read Collection|\FireflyIII\Models\Attachment[] $attachments
|
||||
* @property-read int|null $attachments_count
|
||||
|
||||
@@ -40,6 +40,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property string $name
|
||||
* @property string $outward
|
||||
* @property string $inward
|
||||
* @property int $journalCount
|
||||
* @property bool $editable
|
||||
* @property-read Collection|\FireflyIII\Models\TransactionJournalLink[] $transactionJournalLinks
|
||||
* @property-read int|null $transaction_journal_links_count
|
||||
|
||||
@@ -83,6 +83,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @method static \Illuminate\Database\Query\Builder|Transaction withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|Transaction withoutTrashed()
|
||||
* @mixin Eloquent
|
||||
* @property int $the_count
|
||||
*/
|
||||
class Transaction extends Model
|
||||
{
|
||||
|
||||
@@ -114,6 +114,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @mixin Eloquent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Location[] $locations
|
||||
* @property-read int|null $locations_count
|
||||
* @property int $the_count
|
||||
*/
|
||||
class TransactionJournal extends Model
|
||||
{
|
||||
|
||||
@@ -123,24 +123,14 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
*/
|
||||
public function findByIbanNull(string $iban, array $types): ?Account
|
||||
{
|
||||
$query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
|
||||
$query = $this->user->accounts()->where('accounts.iban', $iban);
|
||||
|
||||
if (0!==count($types)) {
|
||||
if (0 !== count($types)) {
|
||||
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
$query->whereIn('account_types.type', $types);
|
||||
}
|
||||
|
||||
// TODO a loop like this is no longer necessary
|
||||
|
||||
$accounts = $query->get(['accounts.*']);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->iban === $iban) {
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return $query->first(['accounts.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +259,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
$query->where('active', 1);
|
||||
$query->where('active', true);
|
||||
$query->orderBy('accounts.account_type_id', 'ASC');
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
@@ -650,7 +640,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection
|
||||
{
|
||||
$dbQuery = $this->user->accounts()
|
||||
->where('active', 1)
|
||||
->where('active', true)
|
||||
->orderBy('accounts.order', 'ASC')
|
||||
->orderBy('accounts.account_type_id', 'ASC')
|
||||
->orderBy('accounts.name', 'ASC')
|
||||
@@ -678,8 +668,8 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
public function searchAccountNr(string $query, array $types, int $limit): Collection
|
||||
{
|
||||
$dbQuery = $this->user->accounts()->distinct()
|
||||
->leftJoin('account_meta', 'accounts.id', 'account_meta.account_id')
|
||||
->where('accounts.active', 1)
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('accounts.active', true)
|
||||
->orderBy('accounts.order', 'ASC')
|
||||
->orderBy('accounts.account_type_id', 'ASC')
|
||||
->orderBy('accounts.name', 'ASC')
|
||||
@@ -747,4 +737,29 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
return $service->update($account, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function findByAccountNumber(string $number, array $types): ?Account
|
||||
{
|
||||
$dbQuery = $this->user
|
||||
->accounts()
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('accounts.active', true)
|
||||
->where(
|
||||
function (EloquentBuilder $q1) use ($number) {
|
||||
$json = json_encode($number);
|
||||
$q1->where('account_meta.name', '=', 'account_number');
|
||||
$q1->where('account_meta.data', '=', $json);
|
||||
}
|
||||
);
|
||||
|
||||
if (0 !== count($types)) {
|
||||
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
$dbQuery->whereIn('account_types.type', $types);
|
||||
}
|
||||
|
||||
return $dbQuery->first(['accounts.*']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function count(array $types): int;
|
||||
|
||||
|
||||
/**
|
||||
* Moved here from account CRUD.
|
||||
*
|
||||
@@ -65,6 +66,14 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function expandWithDoubles(Collection $accounts): Collection;
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @param array $types
|
||||
*
|
||||
* @return Account|null
|
||||
*/
|
||||
public function findByAccountNumber(string $number, array $types): ?Account;
|
||||
|
||||
/**
|
||||
* @param string $iban
|
||||
* @param array $types
|
||||
|
||||
@@ -261,6 +261,12 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
$collector->setSourceAccounts($opposing);
|
||||
}
|
||||
}
|
||||
if(TransactionType::TRANSFER === $type) {
|
||||
// supports only accounts, not opposing.
|
||||
if(null !== $accounts) {
|
||||
$collector->setAccounts($accounts);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $currency) {
|
||||
$collector->setCurrency($currency);
|
||||
|
||||
@@ -166,7 +166,7 @@ class BillRepository implements BillRepositoryInterface
|
||||
public function getActiveBills(): Collection
|
||||
{
|
||||
return $this->user->bills()
|
||||
->where('active', 1)
|
||||
->where('active', true)
|
||||
->orderBy('bills.name', 'ASC')
|
||||
->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]);
|
||||
}
|
||||
|
||||
@@ -98,10 +98,10 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$budgets = $this->getBudgets();
|
||||
/** @var Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
DB::table('budget_transaction')->where('budget_id', $budget->id)->delete();
|
||||
DB::table('budget_transaction_journal')->where('budget_id', $budget->id)->delete();
|
||||
RecurrenceTransactionMeta::where('name', 'budget_id')->where('value', $budget->id)->delete();
|
||||
RuleAction::where('action_type', 'set_budget')->where('action_value', $budget->id)->delete();
|
||||
DB::table('budget_transaction')->where('budget_id', (int)$budget->id)->delete();
|
||||
DB::table('budget_transaction_journal')->where('budget_id', (int)$budget->id)->delete();
|
||||
RecurrenceTransactionMeta::where('name', 'budget_id')->where('value', (string)$budget->id)->delete();
|
||||
RuleAction::where('action_type', 'set_budget')->where('action_value', (string)$budget->id)->delete();
|
||||
$budget->delete();
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
*/
|
||||
public function getActiveBudgets(): Collection
|
||||
{
|
||||
return $this->user->budgets()->where('active', 1)
|
||||
return $this->user->budgets()->where('active', true)
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('name', 'ASC')
|
||||
->get();
|
||||
@@ -282,7 +282,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
|
||||
}
|
||||
$search->orderBy('order', 'ASC')
|
||||
->orderBy('name', 'ASC')->where('active', 1);
|
||||
->orderBy('name', 'ASC')->where('active', true);
|
||||
|
||||
return $search->take($limit)->get();
|
||||
}
|
||||
@@ -328,7 +328,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
Log::error($e->getTraceAsString());
|
||||
throw new FireflyException('400002: Could not store budget.', 0, $e);
|
||||
}
|
||||
if (!array_key_exists('auto_budget_type', $data)) {
|
||||
if (!array_key_exists('auto_budget_type', $data) || !array_key_exists('auto_budget_amount', $data) || !array_key_exists('auto_budget_period', $data)) {
|
||||
return $newBudget;
|
||||
}
|
||||
$type = $data['auto_budget_type'];
|
||||
|
||||
@@ -448,7 +448,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
*/
|
||||
public function searchCurrency(string $search, int $limit): Collection
|
||||
{
|
||||
$query = TransactionCurrency::where('enabled', 1);
|
||||
$query = TransactionCurrency::where('enabled', true);
|
||||
if ('' !== $search) {
|
||||
$query->where('name', 'LIKE', sprintf('%%%s%%', $search));
|
||||
}
|
||||
|
||||
@@ -211,8 +211,8 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
{
|
||||
$collection = $this->user->rules()
|
||||
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
|
||||
->where('rules.active', 1)
|
||||
->where('rule_groups.active', 1)
|
||||
->where('rules.active', true)
|
||||
->where('rule_groups.active', true)
|
||||
->orderBy('rule_groups.order', 'ASC')
|
||||
->orderBy('rules.order', 'ASC')
|
||||
->orderBy('rules.id', 'ASC')
|
||||
@@ -238,8 +238,8 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
{
|
||||
$collection = $this->user->rules()
|
||||
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
|
||||
->where('rules.active', 1)
|
||||
->where('rule_groups.active', 1)
|
||||
->where('rules.active', true)
|
||||
->where('rule_groups.active', true)
|
||||
->orderBy('rule_groups.order', 'ASC')
|
||||
->orderBy('rules.order', 'ASC')
|
||||
->orderBy('rules.id', 'ASC')
|
||||
|
||||
@@ -150,7 +150,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
*/
|
||||
public function getActiveGroups(): Collection
|
||||
{
|
||||
return $this->user->ruleGroups()->with(['rules'])->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']);
|
||||
return $this->user->ruleGroups()->with(['rules'])->where('rule_groups.active', true)->orderBy('order', 'ASC')->get(['rule_groups.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
public function getActiveRules(RuleGroup $group): Collection
|
||||
{
|
||||
return $group->rules()
|
||||
->where('rules.active', 1)
|
||||
->where('rules.active', true)
|
||||
->get(['rules.*']);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', 1)
|
||||
->where('rules.active', true)
|
||||
->get(['rules.*']);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'update-journal')
|
||||
->where('rules.active', 1)
|
||||
->where('rules.active', true)
|
||||
->get(['rules.*']);
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
*/
|
||||
public function maxOrder(): int
|
||||
{
|
||||
return (int)$this->user->ruleGroups()->where('active', 1)->max('order');
|
||||
return (int)$this->user->ruleGroups()->where('active', true)->max('order');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,7 +337,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
$this->user->ruleGroups()->where('active', false)->update(['order' => 0]);
|
||||
$set = $this->user
|
||||
->ruleGroups()
|
||||
->where('active', 1)
|
||||
->where('active', true)
|
||||
->whereNull('deleted_at')
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('title', 'DESC')
|
||||
|
||||
@@ -102,7 +102,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
$journals = $group->transactionJournals->pluck('id')->toArray();
|
||||
$set = Attachment::whereIn('attachable_id', $journals)
|
||||
->where('attachable_type', TransactionJournal::class)
|
||||
->where('uploaded', 1)
|
||||
->where('uploaded', true)
|
||||
->whereNull('deleted_at')->get();
|
||||
|
||||
$result = [];
|
||||
|
||||
@@ -79,6 +79,7 @@ trait JournalServiceTrait
|
||||
$result = $this->findAccountById($data, $expectedTypes[$transactionType]);
|
||||
$result = $this->findAccountByName($result, $data, $expectedTypes[$transactionType]);
|
||||
$result = $this->findAccountByIban($result, $data, $expectedTypes[$transactionType]);
|
||||
$result = $this->findAccountByNumber($result, $data, $expectedTypes[$transactionType]);
|
||||
$result = $this->createAccount($result, $data, $expectedTypes[$transactionType][0]);
|
||||
|
||||
return $this->getCashAccount($result, $data, $expectedTypes[$transactionType]);
|
||||
@@ -95,7 +96,7 @@ trait JournalServiceTrait
|
||||
$search = null;
|
||||
// first attempt, find by ID.
|
||||
if (null !== $data['id']) {
|
||||
$search = $this->accountRepository->findNull($data['id']);
|
||||
$search = $this->accountRepository->findNull((int) $data['id']);
|
||||
if (null !== $search && in_array($search->accountType->type, $types, true)) {
|
||||
Log::debug(
|
||||
sprintf('Found "account_id" object: #%d, "%s" of type %s', $search->id, $search->name, $search->accountType->type)
|
||||
@@ -160,6 +161,34 @@ trait JournalServiceTrait
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account|null $account
|
||||
* @param array $data
|
||||
* @param array $types
|
||||
*
|
||||
* @return Account|null
|
||||
*/
|
||||
private function findAccountByNumber(?Account $account, array $data, array $types): ?Account
|
||||
{
|
||||
// third attempt, find by account number
|
||||
if (null === $account && null !== $data['number']) {
|
||||
Log::debug(sprintf('Searching for account number "%s".', $data['number']));
|
||||
// find by preferred type.
|
||||
$source = $this->accountRepository->findByAccountNumber((string) $data['number'], [$types[0]]);
|
||||
|
||||
// or any expected type.
|
||||
$source = $source ?? $this->accountRepository->findByAccountNumber((string) $data['number'], $types);
|
||||
|
||||
if (null !== $source) {
|
||||
Log::debug(sprintf('Found account: #%d, %s', $source->id, $source->name));
|
||||
|
||||
$account = $source;
|
||||
}
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account|null $account
|
||||
* @param array $data
|
||||
|
||||
@@ -136,6 +136,15 @@ class AccountUpdateService
|
||||
$account->account_type_id = $type->id;
|
||||
}
|
||||
}
|
||||
// set liability, alternative method used in v1 layout:
|
||||
|
||||
if ($this->isLiability($account) && array_key_exists('account_type_id', $data)) {
|
||||
$type = AccountType::find((int)$data['account_type_id']);
|
||||
|
||||
if (null !== $type && in_array($type->type, config('firefly.valid_liabilities'), true)) {
|
||||
$account->account_type_id = $type->id;
|
||||
}
|
||||
}
|
||||
|
||||
// update virtual balance (could be set to zero if empty string).
|
||||
if (array_key_exists('virtual_balance', $data) && null !== $data['virtual_balance']) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class BudgetList implements BinderInterface
|
||||
if (auth()->check()) {
|
||||
|
||||
if ('allBudgets' === $value) {
|
||||
return auth()->user()->budgets()->where('active', 1)
|
||||
return auth()->user()->budgets()->where('active', true)
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('name', 'ASC')
|
||||
->get();
|
||||
@@ -63,7 +63,7 @@ class BudgetList implements BinderInterface
|
||||
|
||||
/** @var Collection $collection */
|
||||
$collection = auth()->user()->budgets()
|
||||
->where('active', 1)
|
||||
->where('active', true)
|
||||
->whereIn('id', $list)
|
||||
->get();
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ trait RuleManagement
|
||||
],
|
||||
[
|
||||
'type' => 'from_account_is',
|
||||
'value' => (string)trans('firefly.default_rule_trigger_from_account'),
|
||||
'value' => (string)trans('firefly.default_rule_trigger_source_account'),
|
||||
'stop_processing' => false,
|
||||
|
||||
],
|
||||
|
||||
@@ -153,7 +153,7 @@ class Preferences
|
||||
*/
|
||||
public function getForUser(User $user, string $name, $default = null): ?Preference
|
||||
{
|
||||
$preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
$preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id','user_id', 'name', 'data', 'updated_at', 'created_at']);
|
||||
if (null !== $preference && null === $preference->data) {
|
||||
try {
|
||||
$preference->delete();
|
||||
|
||||
@@ -56,8 +56,8 @@ class SetDestinationAccount implements ActionInterface
|
||||
*/
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$user = User::find($journal['user_id']);
|
||||
$type = $journal['transaction_type_type'];
|
||||
$user = User::find($journal['user_id']);
|
||||
$type = $journal['transaction_type_type'];
|
||||
/** @var TransactionJournal|null $object */
|
||||
$object = $user->transactionJournals()->find((int)$journal['transaction_journal_id']);
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
@@ -108,8 +108,9 @@ class SetDestinationAccount implements ActionInterface
|
||||
}
|
||||
|
||||
// if this is a withdrawal, the new destination account must be a expense account and may be created:
|
||||
// or it is a liability, in which case it must be returned.
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$newAccount = $this->findExpenseAccount();
|
||||
$newAccount = $this->findWithdrawalDestinationAccount();
|
||||
}
|
||||
|
||||
Log::debug(sprintf('New destination account is #%d ("%s").', $newAccount->id, $newAccount->name));
|
||||
@@ -145,9 +146,10 @@ class SetDestinationAccount implements ActionInterface
|
||||
/**
|
||||
* @return Account
|
||||
*/
|
||||
private function findExpenseAccount(): Account
|
||||
private function findWithdrawalDestinationAccount(): Account
|
||||
{
|
||||
$account = $this->repository->findByName($this->action->action_value, [AccountType::EXPENSE]);
|
||||
$allowed = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$account = $this->repository->findByName($this->action->action_value, $allowed);
|
||||
if (null === $account) {
|
||||
$data = [
|
||||
'name' => $this->action->action_value,
|
||||
|
||||
@@ -105,8 +105,9 @@ class SetSourceAccount implements ActionInterface
|
||||
}
|
||||
|
||||
// if this is a deposit, the new source account must be a revenue account and may be created:
|
||||
// or its a liability
|
||||
if (TransactionType::DEPOSIT === $type) {
|
||||
$newAccount = $this->findRevenueAccount();
|
||||
$newAccount = $this->findDepositSourceAccount();
|
||||
}
|
||||
|
||||
Log::debug(sprintf('New source account is #%d ("%s").', $newAccount->id, $newAccount->name));
|
||||
@@ -140,7 +141,7 @@ class SetSourceAccount implements ActionInterface
|
||||
/**
|
||||
* @return Account
|
||||
*/
|
||||
private function findRevenueAccount(): Account
|
||||
private function findDepositSourceAccount(): Account
|
||||
{
|
||||
$allowed = config('firefly.expected_source_types.source.Deposit');
|
||||
$account = $this->repository->findByName($this->action->action_value, $allowed);
|
||||
|
||||
@@ -145,6 +145,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
*/
|
||||
public function setRules(Collection $rules): void
|
||||
{
|
||||
|
||||
Log::debug(__METHOD__);
|
||||
foreach ($rules as $rule) {
|
||||
if ($rule instanceof Rule) {
|
||||
@@ -227,8 +228,16 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
{
|
||||
Log::debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0));
|
||||
$searchArray = [];
|
||||
|
||||
/** @var Collection $triggers */
|
||||
$triggers = $rule->ruleTriggers;
|
||||
|
||||
/** @var RuleTrigger $ruleTrigger */
|
||||
foreach ($rule->ruleTriggers()->where('active', 1)->get() as $ruleTrigger) {
|
||||
foreach ($triggers as $ruleTrigger) {
|
||||
if (false === $ruleTrigger->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if needs no context, value is different:
|
||||
$needsContext = config(sprintf('firefly.search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true;
|
||||
if (false === $needsContext) {
|
||||
@@ -241,6 +250,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add local operators:
|
||||
foreach ($this->operators as $operator) {
|
||||
Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value']));
|
||||
@@ -373,7 +383,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
{
|
||||
Log::debug(sprintf('SearchRuleEngine:: Will now execute actions on transaction journal #%d', $transaction['transaction_journal_id']));
|
||||
/** @var RuleAction $ruleAction */
|
||||
foreach ($rule->ruleActions()->where('active', 1)->get() as $ruleAction) {
|
||||
foreach ($rule->ruleActions()->where('active', true)->get() as $ruleAction) {
|
||||
$break = $this->processRuleAction($ruleAction, $transaction);
|
||||
if (true === $break) {
|
||||
break;
|
||||
@@ -448,8 +458,15 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
// start a search query for individual each trigger:
|
||||
$total = new Collection;
|
||||
$count = 0;
|
||||
|
||||
/** @var Collection $triggers */
|
||||
$triggers = $rule->ruleTriggers;
|
||||
|
||||
/** @var RuleTrigger $ruleTrigger */
|
||||
foreach ($rule->ruleTriggers()->where('active', 1)->get() as $ruleTrigger) {
|
||||
foreach ($triggers as $ruleTrigger) {
|
||||
if (false === $ruleTrigger->active) {
|
||||
continue;
|
||||
}
|
||||
if ('user_action' === $ruleTrigger->trigger_type) {
|
||||
Log::debug('Skip trigger type.');
|
||||
continue;
|
||||
|
||||
@@ -114,7 +114,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'opening_balance' => $openingBalance,
|
||||
'opening_balance_date' => $openingBalanceDate,
|
||||
'liability_type' => $liabilityType,
|
||||
'interest' => (float)$interest,
|
||||
'interest' => $interest,
|
||||
'interest_period' => $interestPeriod,
|
||||
'include_net_worth' => $includeNetWorth,
|
||||
'longitude' => $longitude,
|
||||
|
||||
@@ -48,16 +48,21 @@ trait ValidatesAutoBudgetRequest
|
||||
return;
|
||||
}
|
||||
// basic float check:
|
||||
if (!is_numeric($amount)) {
|
||||
$validator->errors()->add('auto_budget_amount', (string)trans('validation.amount_required_for_auto_budget'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ('' === $amount) {
|
||||
$validator->errors()->add('auto_budget_amount', (string)trans('validation.amount_required_for_auto_budget'));
|
||||
}
|
||||
if (null !== $amount && 1 !== bccomp((string)$amount, '0')) {
|
||||
if (1 !== bccomp((string)$amount, '0')) {
|
||||
$validator->errors()->add('auto_budget_amount', (string)trans('validation.auto_budget_amount_positive'));
|
||||
}
|
||||
if ('' === $period) {
|
||||
$validator->errors()->add('auto_budget_period', (string)trans('validation.auto_budget_period_mandatory'));
|
||||
}
|
||||
if (null !== $amount && null !== $currencyId && null !== $currencyCode && '' === $currencyCode && 0 === $currencyId) {
|
||||
if (null !== $currencyId && null !== $currencyCode && '' === $currencyCode && 0 === $currencyId) {
|
||||
$validator->errors()->add('auto_budget_amount', (string)trans('validation.require_currency_info'));
|
||||
}
|
||||
}
|
||||
|
||||
48
changelog.md
48
changelog.md
@@ -2,6 +2,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 5.5.13 - 2021-07-25
|
||||
|
||||
### Security
|
||||
|
||||
- This version of Firefly III fixes [CVE-2021-3663](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3663)
|
||||
|
||||
## 5.5.12 - 2021-06-03
|
||||
|
||||
⚠️ On July 1st 2021 the Docker tag will change to `fireflyiii/core`. You can already start using the new tag.
|
||||
|
||||
### Security
|
||||
|
||||
- This version of Firefly III fixes a security vulnerability in the export routine. You are advised to upgrade as soon as possible. All credits to the excellent @oomb.
|
||||
|
||||
## 5.5.11 - 2021-05-08
|
||||
|
||||
⚠️ On July 1st 2021 the Docker tag will change to `fireflyiii/core`. You can already start using the new tag.
|
||||
|
||||
### Fixed
|
||||
- [Issue 4707](https://github.com/firefly-iii/firefly-iii/issues/4707) [issue 4732](https://github.com/firefly-iii/firefly-iii/issues/4732) Rule tests were broken, and matching transactions were not visible.
|
||||
- [Issue 4729](https://github.com/firefly-iii/firefly-iii/issues/4729) Top boxes were no longer visible.
|
||||
- [Issue 4730](https://github.com/firefly-iii/firefly-iii/issues/4730) Second split transaction had today's date
|
||||
- [Issue 4734](https://github.com/firefly-iii/firefly-iii/issues/4734) Potential fixes for PostgreSQL and PHP 7.4.18.
|
||||
- [Issue 4739](https://github.com/firefly-iii/firefly-iii/issues/4739) Was not possible to change liability type.
|
||||
|
||||
## 5.5.10 - 2021-05-01
|
||||
|
||||
### Changed
|
||||
- [Issue 4708](https://github.com/firefly-iii/firefly-iii/issues/4708) When searching for the external ID, Firefly III will now only return the exact match.
|
||||
|
||||
### Fixed
|
||||
- [Issue 4545](https://github.com/firefly-iii/firefly-iii/issues/4545) Rare but annoying issue with PostgreSQL increments will be repaired during image boot time. Thanks @jaylenw!
|
||||
- [Issue 4710](https://github.com/firefly-iii/firefly-iii/issues/4710) Some rule actions could not handle liabilities.
|
||||
- [Issue 4715](https://github.com/firefly-iii/firefly-iii/issues/4715) Fixed some titles.
|
||||
- [Issue 4720](https://github.com/firefly-iii/firefly-iii/issues/4720) Could not remove a split in the new layout.
|
||||
|
||||
## 5.5.9 (API 1.5.2) 2021-04-24
|
||||
|
||||
This update fixes some of the more annoying issues in the new experimental v2 layout (see also [GitHub](https://github.com/firefly-iii/firefly-iii/issues/4618)), but some minor other issues as well.
|
||||
|
||||
### Fixed
|
||||
- Dashboard preferences would some times retain old or bad data.
|
||||
|
||||
### API
|
||||
- [Issue 4697](https://github.com/firefly-iii/firefly-iii/issues/4697) Submitting an existing account with an account number only would store it as a new account.
|
||||
- [Issue 4706](https://github.com/firefly-iii/firefly-iii/issues/4706) Account interest was a float and not a string.
|
||||
- Store Budget API call would not properly handle auto budgets.
|
||||
|
||||
## 5.5.8 (API 1.5.2) 2021-04-17
|
||||
|
||||
This update fixes some of the more annoying issues in the new experimental v2 layout (see also [GitHub](https://github.com/firefly-iii/firefly-iii/issues/4618)), but some minor other issues as well.
|
||||
|
||||
@@ -115,8 +115,7 @@
|
||||
"phpstan/phpstan-deprecation-rules": "^0.12.5",
|
||||
"phpunit/phpunit": "^9.2",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"thecodingmachine/phpstan-strict-rules": "^0.12.0",
|
||||
"vimeo/psalm": "^4.1"
|
||||
"thecodingmachine/phpstan-strict-rules": "^0.12.0"
|
||||
},
|
||||
"suggest": {
|
||||
"adldap2/adldap2-laravel": "If you want to login using LDAP.",
|
||||
@@ -155,6 +154,7 @@
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan cache:clear",
|
||||
"@php artisan firefly-iii:fix-pgsql-sequences",
|
||||
"@php artisan firefly-iii:decrypt-all",
|
||||
|
||||
"@php artisan firefly-iii:transaction-identifiers",
|
||||
@@ -192,6 +192,7 @@
|
||||
"@php artisan firefly-iii:fix-recurring-transactions",
|
||||
"@php artisan firefly-iii:unify-group-accounts",
|
||||
"@php artisan firefly-iii:fix-transaction-types",
|
||||
"@php artisan firefly-iii:fix-frontpage-accounts",
|
||||
|
||||
"@php artisan firefly-iii:report-empty-objects",
|
||||
"@php artisan firefly-iii:report-sum",
|
||||
|
||||
1133
composer.lock
generated
1133
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -95,12 +95,12 @@ return [
|
||||
],
|
||||
'feature_flags' => [
|
||||
'export' => true,
|
||||
'telemetry' => true,
|
||||
'telemetry' => false,
|
||||
'webhooks' => false,
|
||||
'handle_debts' => true,
|
||||
],
|
||||
|
||||
'version' => '5.5.8',
|
||||
'version' => '5.5.13',
|
||||
'api_version' => '1.5.2',
|
||||
'db_version' => 16,
|
||||
'maxUploadSize' => 1073741824, // 1 GB
|
||||
@@ -631,8 +631,7 @@ return [
|
||||
'expected_source_types' => [
|
||||
'source' => [
|
||||
TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionTypeModel::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,
|
||||
AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION,],
|
||||
TransactionTypeModel::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionTypeModel::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE,],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"/public/js/accounts/delete.js": "/public/js/accounts/delete.js",
|
||||
"/public/js/accounts/show.js": "/public/js/accounts/show.js",
|
||||
"/public/js/accounts/create.js": "/public/js/accounts/create.js",
|
||||
"/public/js/budgets/index.js": "/public/js/budgets/index.js",
|
||||
"/public/js/transactions/create.js": "/public/js/transactions/create.js",
|
||||
"/public/js/transactions/edit.js": "/public/js/transactions/edit.js",
|
||||
"/public/js/transactions/index.js": "/public/js/transactions/index.js",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"admin-lte": "^3.1.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.21.2",
|
||||
"chart.js": "^3.0.2",
|
||||
"chart.js": "^3.2.0",
|
||||
"icheck-bootstrap": "^3.0.1",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"leaflet": "^1.7.1",
|
||||
|
||||
@@ -133,6 +133,7 @@
|
||||
|
||||
import {mapGetters} from "vuex";
|
||||
import Sortable from "sortablejs";
|
||||
import format from "date-fns/format";
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
@@ -370,9 +371,10 @@ export default {
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
promises.push(axios.get('./api/v1/accounts/' + acct.id + '?date=' + this.start.toISOString().split('T')[0]));
|
||||
promises.push(axios.get('./api/v1/accounts/' + acct.id + '?date=' + this.end.toISOString().split('T')[0]));
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
promises.push(axios.get('./api/v1/accounts/' + acct.id + '?date=' + startStr));
|
||||
promises.push(axios.get('./api/v1/accounts/' + acct.id + '?date=' + endStr));
|
||||
|
||||
Promise.all(promises).then(responses => {
|
||||
let index = responses[0].index;
|
||||
|
||||
114
frontend/src/components/budgets/Index.vue
Normal file
114
frontend/src/components/budgets/Index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<!--
|
||||
- Index.vue
|
||||
- Copyright (c) 2021 james@firefly-iii.org
|
||||
-
|
||||
- This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>
|
||||
<span class="d-block">(all)</span>
|
||||
<span class="d-none d-xl-block">xl</span>
|
||||
<span class="d-none d-lg-block d-xl-none">lg</span>
|
||||
<span class="d-none d-md-block d-lg-none">md</span>
|
||||
<span class="d-none d-sm-block d-md-none">sm</span>
|
||||
<span class="d-block d-sm-none">xs</span>
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-6">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Budgets</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
Budget X<br>
|
||||
Budget Y<br>
|
||||
Budget X<br>
|
||||
Budget Y<br>
|
||||
Budget X<br>
|
||||
Budget Y<br>
|
||||
Budget X<br>
|
||||
Budget Y<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-10 col-lg-8 col-md-8 col-sm-8 col-6">
|
||||
<div class="container-fluid" style="overflow:scroll;">
|
||||
<div class="d-flex flex-row flex-nowrap">
|
||||
<div class="card card-body-budget" v-for="n in 5">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Maand yXz</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
Some text<br>
|
||||
Some text<br>
|
||||
Some text<br>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Index",
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.card-body-budget {
|
||||
min-width: 300px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.holder-titles {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.title-block {
|
||||
border: 1px red solid;
|
||||
}
|
||||
|
||||
.holder-blocks {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.budget-block {
|
||||
border: 1px blue solid;
|
||||
}
|
||||
|
||||
.budget-block-unused {
|
||||
border: 1px green solid;
|
||||
}
|
||||
|
||||
.budget-block-unset {
|
||||
border: 1px purple solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -102,11 +102,9 @@ export default {
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value, index, values) {
|
||||
//return this.getLabelForValue(value);
|
||||
let dateObj = new Date(this.getLabelForValue(value));
|
||||
let options = {year: 'numeric', month: 'long', day: 'numeric'};
|
||||
let str = new Intl.DateTimeFormat(localStorage.locale, options).format(dateObj);
|
||||
return str;
|
||||
let dateObj = new Date(this.getLabelForValue(value).split('T')[0]);
|
||||
return new Intl.DateTimeFormat(localStorage.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(dateObj);
|
||||
//return str;
|
||||
// // //console.log();
|
||||
// // //return self.formatLabel(value, 20);
|
||||
// // return self.formatLabel(str, 20);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<td style="vertical-align: middle">
|
||||
<div class="progress progress active">
|
||||
<div :aria-valuenow="budgetLimit.pctGreen" :style="'width: '+ budgetLimit.pctGreen + '%;'"
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-success progress-bar-striped" role="progressbar">
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-success" role="progressbar">
|
||||
<span v-if="budgetLimit.pctGreen > 35">
|
||||
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
|
||||
<!-- -->
|
||||
@@ -37,14 +37,14 @@
|
||||
</div>
|
||||
|
||||
<div :aria-valuenow="budgetLimit.pctOrange" :style="'width: '+ budgetLimit.pctOrange + '%;'"
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-warning progress-bar-striped" role="progressbar">
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-warning" role="progressbar">
|
||||
<span v-if="budgetLimit.pctRed <= 50 && budgetLimit.pctOrange > 35">
|
||||
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div :aria-valuenow="budgetLimit.pctRed" :style="'width: '+ budgetLimit.pctRed + '%;'"
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-danger progress-bar-striped" role="progressbar">
|
||||
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-danger" role="progressbar">
|
||||
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35" class="text-muted">
|
||||
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
|
||||
</span>
|
||||
|
||||
@@ -79,17 +79,21 @@
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import Vue from "vue";
|
||||
import DatePicker from "v-calendar/lib/components/date-picker.umd";
|
||||
import subDays from 'date-fns/subDays'
|
||||
import addDays from 'date-fns/addDays'
|
||||
import startOfDay from 'date-fns/startOfDay'
|
||||
import endOfDay from 'date-fns/endOfDay'
|
||||
import startOfWeek from 'date-fns/startOfWeek'
|
||||
import endOfWeek from 'date-fns/endOfWeek'
|
||||
import format from 'date-fns/format'
|
||||
import subDays from 'date-fns/subDays';
|
||||
import addDays from 'date-fns/addDays';
|
||||
import addMonths from 'date-fns/addMonths';
|
||||
import startOfDay from 'date-fns/startOfDay';
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
import startOfWeek from 'date-fns/startOfWeek';
|
||||
import endOfWeek from 'date-fns/endOfWeek';
|
||||
import endOfMonth from 'date-fns/endOfMonth';
|
||||
import format from 'date-fns/format';
|
||||
import startOfQuarter from 'date-fns/startOfQuarter';
|
||||
import subMonths from 'date-fns/subMonths';
|
||||
import endOfQuarter from 'date-fns/endOfQuarter';
|
||||
import subQuarters from 'date-fns/subQuarters';
|
||||
import addQuarters from 'date-fns/addQuarters';
|
||||
import startOfMonth from 'date-fns/startOfMonth';
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -182,13 +186,20 @@ export default {
|
||||
},
|
||||
|
||||
generateWeekly: function () {
|
||||
//console.log('weekly');
|
||||
let today = new Date(this.range.start);
|
||||
//console.log('Today is ' + today);
|
||||
let start = startOfDay(startOfWeek(subDays(today, 7), {weekStartsOn: 1}));
|
||||
let end = endOfDay(endOfWeek(subDays(today, 7), {weekStartsOn: 1}));
|
||||
let dateFormat = this.$t('config.week_in_year_fns');
|
||||
//console.log('Date format: "'+dateFormat+'"');
|
||||
let title = format(start, dateFormat);
|
||||
|
||||
// last week
|
||||
// console.log('Last week');
|
||||
// console.log(start);
|
||||
// console.log(end);
|
||||
// console.log(title);
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
@@ -201,6 +212,10 @@ export default {
|
||||
start = startOfDay(startOfWeek(today, {weekStartsOn: 1}));
|
||||
end = endOfDay(endOfWeek(today, {weekStartsOn: 1}));
|
||||
title = format(start, dateFormat);
|
||||
// console.log('This week');
|
||||
// console.log(start);
|
||||
// console.log(end);
|
||||
// console.log(title);
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
@@ -213,6 +228,10 @@ export default {
|
||||
start = startOfDay(startOfWeek(addDays(today, 7), {weekStartsOn: 1}));
|
||||
end = endOfDay(endOfWeek(addDays(today, 7), {weekStartsOn: 1}));
|
||||
title = format(start, dateFormat);
|
||||
// console.log('Next week');
|
||||
// console.log(start);
|
||||
// console.log(end);
|
||||
// console.log(title);
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
@@ -224,35 +243,35 @@ export default {
|
||||
generateMonthly: function () {
|
||||
let today = new Date(this.range.start);
|
||||
// previous month
|
||||
firstDayOfMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||
lastDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 0);
|
||||
let start = startOfDay(startOfMonth(subMonths(today, 1)));
|
||||
let end = endOfDay(endOfMonth(subMonths(today, 1)));
|
||||
this.periods.push(
|
||||
{
|
||||
start: firstDayOfMonth.toDateString(),
|
||||
end: lastDayOfMonth.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(firstDayOfMonth)
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
|
||||
}
|
||||
);
|
||||
|
||||
// this month
|
||||
firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
||||
start = startOfDay(startOfMonth(today));
|
||||
end = endOfDay(endOfMonth(today));
|
||||
this.periods.push(
|
||||
{
|
||||
start: firstDayOfMonth.toDateString(),
|
||||
end: lastDayOfMonth.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(firstDayOfMonth)
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
|
||||
}
|
||||
);
|
||||
|
||||
// next month
|
||||
let firstDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1);
|
||||
let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0);
|
||||
start = startOfDay(startOfMonth(addMonths(today, 1)));
|
||||
end = endOfDay(endOfMonth(addMonths(today, 1)));
|
||||
this.periods.push(
|
||||
{
|
||||
start: firstDayOfMonth.toDateString(),
|
||||
end: lastDayOfMonth.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(firstDayOfMonth)
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
|
||||
}
|
||||
);
|
||||
|
||||
@@ -389,7 +408,7 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
// this year, second half:
|
||||
// this year, current (second) half:
|
||||
start = today;
|
||||
start.setMonth(6);
|
||||
start.setDate(1);
|
||||
|
||||
@@ -33,9 +33,6 @@
|
||||
<div v-if="error" class="text-center">
|
||||
<i class="fas fa-exclamation-triangle text-danger"></i>
|
||||
</div>
|
||||
<div v-if="timezoneDifference" class="text-muted small">
|
||||
{{ $t('firefly.timezone_difference', {local: localTimeZone, system: systemTimeZone}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a class="btn btn-default button-sm" href="./accounts/asset"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_asset_accounts') }}</a>
|
||||
@@ -49,6 +46,7 @@ import DataConverter from "../charts/DataConverter";
|
||||
import DefaultLineOptions from "../charts/DefaultLineOptions";
|
||||
import {mapGetters} from "vuex";
|
||||
import * as ChartJs from 'chart.js'
|
||||
import format from "date-fns/format";
|
||||
|
||||
ChartJs.Chart.register.apply(null, Object.values(ChartJs).filter((chartClass) => (chartClass.id)));
|
||||
|
||||
@@ -65,24 +63,16 @@ export default {
|
||||
dataCollection: {},
|
||||
chartOptions: {},
|
||||
_chart: null,
|
||||
localTimeZone: '',
|
||||
systemTimeZone: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.chartOptions = DefaultLineOptions.methods.getDefaultOptions();
|
||||
this.localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
this.systemTimeZone = this.timezone;
|
||||
this.ready = true;
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('dashboard/index', ['start', 'end']),
|
||||
...mapGetters('root', ['timezone']),
|
||||
'datesReady': function () {
|
||||
return null !== this.start && null !== this.end && this.ready;
|
||||
},
|
||||
timezoneDifference: function () {
|
||||
return this.localTimeZone !== this.systemTimeZone;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -102,8 +92,10 @@ export default {
|
||||
initialiseChart: function () {
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
//let startStr = this.start.toISOString().split('T')[0];
|
||||
//let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
let url = './api/v1/chart/account/overview?start=' + startStr + '&end=' + endStr;
|
||||
axios.get(url)
|
||||
.then(response => {
|
||||
@@ -123,13 +115,14 @@ export default {
|
||||
drawChart: function () {
|
||||
//console.log('drawChart');
|
||||
if ('undefined' !== typeof this._chart) {
|
||||
//console.log('destroy or update!');
|
||||
// console.log('update!');
|
||||
this._chart.data = this.dataCollection;
|
||||
this._chart.update();
|
||||
this.initialised = true;
|
||||
}
|
||||
|
||||
if ('undefined' === typeof this._chart) {
|
||||
//console.log('new!');
|
||||
// console.log('new!');
|
||||
this._chart = new ChartJs.Chart(this.$refs.canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data: this.dataCollection,
|
||||
@@ -140,9 +133,9 @@ export default {
|
||||
}
|
||||
},
|
||||
updateChart: function () {
|
||||
//console.log('updateChart');
|
||||
// console.log('updateChart');
|
||||
if (this.initialised) {
|
||||
//console.log('MUST Update chart!');
|
||||
// console.log('MUST Update chart!');
|
||||
// reset some vars so it wont trigger again:
|
||||
this.initialised = false;
|
||||
this.initialiseChart();
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
|
||||
<script>
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -162,8 +163,10 @@ export default {
|
||||
);
|
||||
},
|
||||
loadTransactions(key, accountId) {
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
axios.get('./api/v1/accounts/' + accountId + '/transactions?page=1&limit=10&start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
this.accounts[key].transactions = response.data.data;
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
export default {
|
||||
@@ -125,8 +126,10 @@ export default {
|
||||
initialiseBills: function () {
|
||||
this.loading = true;
|
||||
this.bills = [];
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
|
||||
axios.get('./api/v1/bills?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<script>
|
||||
import BudgetListGroup from "./BudgetListGroup";
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -136,8 +137,10 @@ export default {
|
||||
other: [],
|
||||
};
|
||||
this.loading = true;
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
axios.get('./api/v1/budgets?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
this.parseBudgets(response.data);
|
||||
@@ -172,8 +175,10 @@ export default {
|
||||
this.getBudgetLimits();
|
||||
},
|
||||
getBudgetLimits() {
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
axios.get('./api/v1/budget-limits?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
this.parseBudgetLimits(response.data);
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('firefly.category') }}</th>
|
||||
<th scope="col">{{ $t('firefly.spent') }}</th>
|
||||
<th scope="col">{{ $t('firefly.spent') }} / {{ $t('firefly.earned') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -90,6 +90,7 @@
|
||||
|
||||
<script>
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -142,8 +143,10 @@ export default {
|
||||
this.spent = 0;
|
||||
this.earned = 0;
|
||||
this.loading = true;
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
this.getCategoryPage(startStr, endStr, 1);
|
||||
},
|
||||
getCategoryPage: function (start, end, page) {
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
<caption style="display:none;">{{ $t('firefly.revenue_accounts') }}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('firefly.category') }}</th>
|
||||
<th scope="col">{{ $t('firefly.spent') }}</th>
|
||||
<th scope="col">{{ $t('firefly.account') }}</th>
|
||||
<th scope="col">{{ $t('firefly.earned') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -51,7 +51,7 @@
|
||||
<td class="align-middle">
|
||||
<div v-if="entry.pct > 0" class="progress">
|
||||
<div :aria-valuenow="entry.pct" :style="{ width: entry.pct + '%'}" aria-valuemax="100"
|
||||
aria-valuemin="0" class="progress-bar progress-bar-striped bg-success"
|
||||
aria-valuemin="0" class="progress-bar bg-success"
|
||||
role="progressbar">
|
||||
<span v-if="entry.pct > 20">
|
||||
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
|
||||
@@ -75,6 +75,7 @@
|
||||
<script>
|
||||
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -126,8 +127,10 @@ export default {
|
||||
this.loading = true;
|
||||
this.income = [];
|
||||
this.error = false;
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
axios.get('./api/v1/insight/income/revenue?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
// do something with response.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<caption style="display:none;">{{ $t('firefly.expense_accounts') }}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('firefly.category') }}</th>
|
||||
<th scope="col">{{ $t('firefly.account') }}</th>
|
||||
<th scope="col">{{ $t('firefly.spent') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -51,7 +51,7 @@
|
||||
<td class="align-middle">
|
||||
<div v-if="entry.pct > 0" class="progress">
|
||||
<div :aria-valuenow="entry.pct" :style="{ width: entry.pct + '%'}" aria-valuemax="100"
|
||||
aria-valuemin="0" class="progress-bar progress-bar-striped bg-danger"
|
||||
aria-valuemin="0" class="progress-bar bg-danger"
|
||||
role="progressbar">
|
||||
<span v-if="entry.pct > 20">
|
||||
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
|
||||
@@ -75,6 +75,7 @@
|
||||
<script>
|
||||
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from "date-fns/format";
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
|
||||
@@ -125,8 +126,10 @@ export default {
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
this.expenses = [];
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
// let startStr = this.start.toISOString().split('T')[0];
|
||||
// let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
axios.get('./api/v1/insight/expense/expense?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
// do something with response.
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<td>
|
||||
<div class="progress-group">
|
||||
<div class="progress progress-sm">
|
||||
<div v-if="piggy.attributes.pct < 100" :style="{'width': piggy.attributes.pct + '%'}" class="progress-bar progress-bar-striped primary"></div>
|
||||
<div v-if="piggy.attributes.pct < 100" :style="{'width': piggy.attributes.pct + '%'}" class="progress-bar primary"></div>
|
||||
<div v-if="100 === piggy.attributes.pct" :style="{'width': piggy.attributes.pct + '%'}"
|
||||
class="progress-bar progress-bar-striped bg-success"></div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col"> <!-- col-md-3 col-sm-6 col-12 -->
|
||||
<div class="col" v-if="0 !== prefCurrencyBalances.length || 0 !== notPrefCurrencyBalances.length">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon"><i class="far fa-bookmark text-info"></i></span>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
|
||||
<!-- balance in preferred currency -->
|
||||
<span v-for="balance in prefCurrencyBalances" :title="balance.sub_title" class="info-box-number">{{ balance.value_parsed }}</span>
|
||||
|
||||
<span v-if="0 === prefCurrencyBalances.length" class="info-box-number"> </span>
|
||||
<div class="progress bg-info">
|
||||
<div class="progress-bar" style="width: 0"></div>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="col" v-if="0!==prefBillsUnpaid.length || 0 !== notPrefBillsUnpaid.length">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon"><i class="far fa-calendar-alt text-teal"></i></span>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- left to spend -->
|
||||
<div class="col">
|
||||
<div class="col" v-if="0 !== prefLeftToSpend.length || 0 !== notPrefLeftToSpend.length">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon"><i class="fas fa-money-bill text-success"></i></span>
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
|
||||
<!-- left to spend in preferred currency -->
|
||||
<span v-for="left in prefLeftToSpend" :title="left.sub_title" class="info-box-number">{{ left.value_parsed }}</span>
|
||||
<span v-if="0 === prefLeftToSpend.length" class="info-box-number"> </span>
|
||||
|
||||
<div class="progress bg-success">
|
||||
<div class="progress-bar" style="width: 0"></div>
|
||||
@@ -96,7 +97,7 @@
|
||||
</div>
|
||||
|
||||
<!-- net worth -->
|
||||
<div class="col">
|
||||
<div class="col" v-if="0 !== notPrefNetWorth.length || 0 !== prefNetWorth.length">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon"><i class="fas fa-money-bill text-success"></i></span>
|
||||
|
||||
@@ -105,7 +106,7 @@
|
||||
<span v-if="loading && !error" class="info-box-text"><i class="fas fa-spinner fa-spin"></i></span>
|
||||
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
|
||||
<span v-for="nw in prefNetWorth" :title="nw.sub_title" class="info-box-number">{{ nw.value_parsed }}</span>
|
||||
|
||||
<span v-if="0===prefNetWorth.length"> </span>
|
||||
<div class="progress bg-success">
|
||||
<div class="progress-bar" style="width: 0"></div>
|
||||
</div>
|
||||
@@ -124,6 +125,7 @@
|
||||
|
||||
<script>
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import format from 'date-fns/format';
|
||||
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
export default {
|
||||
@@ -249,8 +251,12 @@ export default {
|
||||
this.billsUnpaid = [];
|
||||
this.leftToSpend = [];
|
||||
this.netWorth = [];
|
||||
let startStr = this.start.toISOString().split('T')[0];
|
||||
let endStr = this.end.toISOString().split('T')[0];
|
||||
let startStr = format(this.start, 'y-MM-dd');
|
||||
let endStr = format(this.end, 'y-MM-dd');
|
||||
//let startStr = this.start.toISOString().split('T')[0];
|
||||
//let endStr = this.end.toISOString().split('T')[0];
|
||||
//console.log(startStr);
|
||||
//console.log(endStr);
|
||||
axios.get('./api/v1/summary/basic?start=' + startStr + '&end=' + endStr)
|
||||
.then(response => {
|
||||
this.summary = response.data;
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
*/
|
||||
|
||||
// initial state
|
||||
import startOfDay from "date-fns/startOfDay";
|
||||
import endOfDay from 'date-fns/endOfDay'
|
||||
import startOfWeek from 'date-fns/startOfWeek'
|
||||
import endOfWeek from 'date-fns/endOfWeek'
|
||||
import startOfQuarter from 'date-fns/startOfQuarter';
|
||||
import endOfQuarter from 'date-fns/endOfQuarter';
|
||||
import endOfMonth from "date-fns/endOfMonth";
|
||||
import startOfMonth from 'date-fns/startOfMonth';
|
||||
|
||||
const state = () => (
|
||||
{
|
||||
viewRange: 'default',
|
||||
@@ -66,7 +75,7 @@ const actions = {
|
||||
// console.log('View range changed from "' + oldViewRange + '" to "' + viewRange + '"');
|
||||
context.dispatch('setDatesFromViewRange');
|
||||
}
|
||||
if(viewRange === oldViewRange) {
|
||||
if (viewRange === oldViewRange) {
|
||||
// console.log('Restore view range dates');
|
||||
context.dispatch('restoreViewRangeDates');
|
||||
}
|
||||
@@ -77,7 +86,7 @@ const actions = {
|
||||
});
|
||||
|
||||
},
|
||||
restoreViewRangeDates: function(context) {
|
||||
restoreViewRangeDates: function (context) {
|
||||
// check local storage first?
|
||||
if (localStorage.viewRangeStart) {
|
||||
// console.log('view range start set from local storage.');
|
||||
@@ -111,82 +120,63 @@ const actions = {
|
||||
let start;
|
||||
let end;
|
||||
let viewRange = context.getters.viewRange;
|
||||
let today = new Date;
|
||||
// console.log('Will recreate view range on ' + viewRange);
|
||||
switch (viewRange) {
|
||||
case '1D':
|
||||
// one day:
|
||||
start = new Date;
|
||||
end = new Date(start.getTime());
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
// today:
|
||||
start = startOfDay(today);
|
||||
end = endOfDay(today);
|
||||
break;
|
||||
case '1W':
|
||||
// this week:
|
||||
start = new Date;
|
||||
end = new Date(start.getTime());
|
||||
// start of week
|
||||
let diff = start.getDate() - start.getDay() + (start.getDay() === 0 ? -6 : 1);
|
||||
start.setDate(diff);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// end of week
|
||||
let lastday = end.getDate() - (end.getDay() - 1) + 6;
|
||||
end.setDate(lastday);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
start = startOfDay(startOfWeek(today, {weekStartsOn: 1}));
|
||||
end = endOfDay(endOfWeek(today, {weekStartsOn: 1}));
|
||||
break;
|
||||
case '1M':
|
||||
// this month:
|
||||
start = new Date;
|
||||
start = new Date(start.getFullYear(), start.getMonth(), 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
start = startOfDay(startOfMonth(today));
|
||||
end = endOfDay(endOfMonth(today));
|
||||
break;
|
||||
case '3M':
|
||||
// this quarter
|
||||
start = new Date;
|
||||
end = new Date;
|
||||
let quarter = Math.floor((start.getMonth() + 3) / 3) - 1;
|
||||
// start and end months? I'm sure this could be better:
|
||||
let startMonths = [0, 3, 6, 9];
|
||||
let endMonths = [2, 5, 8, 11];
|
||||
// set start to the correct month, day one:
|
||||
start = new Date(start.getFullYear(), startMonths[quarter], 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// set end to the correct month, day one
|
||||
end = new Date(end.getFullYear(), endMonths[quarter], 1);
|
||||
// then to the last day of the month:
|
||||
end = new Date(end.getFullYear(), end.getMonth() + 1, 0);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
start = startOfDay(startOfQuarter(today));
|
||||
end = endOfDay(endOfQuarter(today));
|
||||
break;
|
||||
case '6M':
|
||||
// this half-year
|
||||
start = new Date;
|
||||
end = new Date;
|
||||
let half = start.getMonth() <= 5 ? 0 : 1;
|
||||
|
||||
let startHalf = [0, 6];
|
||||
let endHalf = [5, 11];
|
||||
// set start to the correct month, day one:
|
||||
start = new Date(start.getFullYear(), startHalf[half], 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// set end to the correct month, day one
|
||||
end = new Date(end.getFullYear(), endHalf[half], 1);
|
||||
// then to the last day of the month:
|
||||
end = new Date(end.getFullYear(), end.getMonth() + 1, 0);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
if (today.getMonth() <= 5) {
|
||||
start = new Date(today);
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = new Date(today);
|
||||
end.setMonth(5);
|
||||
end.setDate(30);
|
||||
end = endOfDay(start);
|
||||
}
|
||||
if (today.getMonth() > 5) {
|
||||
start = new Date(today);
|
||||
start.setMonth(6);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = new Date(today);
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(start);
|
||||
}
|
||||
break;
|
||||
case '1Y':
|
||||
// this year
|
||||
start = new Date;
|
||||
end = new Date;
|
||||
start = new Date(start.getFullYear(), 0, 1);
|
||||
start = new Date(today);
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
|
||||
end = new Date(end.getFullYear(), 11, 31);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
end = new Date(today);
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
break;
|
||||
}
|
||||
// console.log('Range is ' + viewRange);
|
||||
|
||||
@@ -25,21 +25,26 @@
|
||||
<Alert :message="warningMessage" type="warning"/>
|
||||
|
||||
<form @submit="submitTransaction" autocomplete="off">
|
||||
<SplitPills :transactions="transactions"/>
|
||||
<SplitPills
|
||||
:transactions="transactions"
|
||||
:count="transactions.length"
|
||||
|
||||
/>
|
||||
|
||||
<div class="tab-content">
|
||||
<SplitForm
|
||||
v-for="(transaction, index) in this.transactions"
|
||||
v-bind:key="index"
|
||||
:count="transactions.length"
|
||||
:index="index"
|
||||
v-bind:key="transaction.transaction_journal_id"
|
||||
:key="transaction.transaction_journal_id"
|
||||
:transaction="transaction"
|
||||
:date="date"
|
||||
:count="transactions.length"
|
||||
:transaction-type="transactionType"
|
||||
:source-allowed-types="sourceAllowedTypes"
|
||||
:allowed-opposing-types="allowedOpposingTypes"
|
||||
:custom-fields="customFields"
|
||||
:date="date"
|
||||
:index="index"
|
||||
:transaction-type="transactionType"
|
||||
:destination-allowed-types="destinationAllowedTypes"
|
||||
:source-allowed-types="sourceAllowedTypes"
|
||||
:allow-switch="false"
|
||||
v-on:uploaded-attachments="uploadedAttachment($event)"
|
||||
v-on:set-marker-location="storeLocation($event)"
|
||||
@@ -122,61 +127,105 @@ const lodashClonedeep = require('lodash.clonedeep');
|
||||
export default {
|
||||
name: "Edit",
|
||||
created() {
|
||||
// console.log('Created');
|
||||
let parts = window.location.pathname.split('/');
|
||||
this.groupId = parseInt(parts[parts.length - 1]);
|
||||
|
||||
this.transactions = [];
|
||||
this.getTransactionGroup();
|
||||
this.getAllowedOpposingTypes();
|
||||
this.getCustomFields();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
successMessage: '',
|
||||
errorMessage: '',
|
||||
warningMessage: '',
|
||||
successMessage: {type: String, default: ''},
|
||||
errorMessage: {type: String, default: ''},
|
||||
warningMessage: {type: String, default: ''},
|
||||
|
||||
// transaction props
|
||||
transactions: [],
|
||||
originalTransactions: [],
|
||||
groupTitle: '',
|
||||
originalGroupTitle: '',
|
||||
transactionType: 'any',
|
||||
groupId: 0,
|
||||
transactions: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
originalTransactions: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
groupTitle: {type: String, default: ''},
|
||||
originalGroupTitle: {type: String, default: ''},
|
||||
transactionType: {type: String, default: 'any'},
|
||||
groupId: {type: Number, default: 0},
|
||||
|
||||
// errors in the group title:
|
||||
groupTitleErrors: [],
|
||||
groupTitleErrors: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
// which custom fields to show
|
||||
customFields: {},
|
||||
customFields: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
// group ID + title once submitted:
|
||||
returnedGroupId: 0,
|
||||
returnedGroupTitle: '',
|
||||
returnedGroupId: {type: Number, default: 0},
|
||||
returnedGroupTitle: {type: String, default: ''},
|
||||
|
||||
// date and time of the transaction,
|
||||
date: '',
|
||||
originalDate: '',
|
||||
date: {type: String, default: ''},
|
||||
originalDate: {type: String, default: ''},
|
||||
|
||||
// things the process is done working on (3 phases):
|
||||
submittedTransaction: false,
|
||||
submittedTransaction: {type: Boolean, default: false},
|
||||
// submittedLinks: false,
|
||||
submittedAttachments: -1, // -1 = no attachments, 0 = uploading, 1 = uploaded
|
||||
inError: false,
|
||||
submittedAttachments: {type: Number, default: -1}, // -1 = no attachments, 0 = uploading, 1 = uploaded
|
||||
inError: {type: Boolean, default: false},
|
||||
|
||||
// number of uploaded attachments
|
||||
// its an object because we count per transaction journal (which can have multiple attachments)
|
||||
// and array doesn't work right.
|
||||
submittedAttCount: {},
|
||||
submittedAttCount: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
// meta data for accounts
|
||||
allowedOpposingTypes: {},
|
||||
destinationAllowedTypes: [],
|
||||
sourceAllowedTypes: [],
|
||||
allowedOpposingTypes: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
destinationAllowedTypes: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
sourceAllowedTypes: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
// states for the form (makes sense right)
|
||||
enableSubmit: true,
|
||||
stayHere: false,
|
||||
|
||||
// force the submission (in case of deletes)
|
||||
forceTransactionSubmission: false
|
||||
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -195,16 +244,17 @@ export default {
|
||||
methods: {
|
||||
...mapMutations('transactions/create', ['updateField',]),
|
||||
/**
|
||||
* Grap transaction group from URL and submit GET.
|
||||
* Grab transaction group from URL and submit GET.
|
||||
*/
|
||||
getTransactionGroup: function () {
|
||||
// console.log('getTransactionGroup');
|
||||
axios.get('./api/v1/transactions/' + this.groupId)
|
||||
.then(response => {
|
||||
this.parseTransactionGroup(response.data);
|
||||
}
|
||||
).catch(error => {
|
||||
console.log('I failed :(');
|
||||
console.log(error);
|
||||
//console.log('I failed :(');
|
||||
//console.log(error);
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -219,12 +269,16 @@ export default {
|
||||
this.groupTitle = attributes.group_title;
|
||||
this.originalGroupTitle = attributes.group_title;
|
||||
|
||||
this.transactions = [];
|
||||
this.originalTransactions = [];
|
||||
|
||||
|
||||
//this.returnedGroupId = parseInt(response.data.id);
|
||||
this.returnedGroupTitle = null === this.originalGroupTitle ? response.data.attributes.transactions[0].description : this.originalGroupTitle;
|
||||
|
||||
|
||||
for (let i in transactions) {
|
||||
if (transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
|
||||
// console.log('Parsing transaction nr ' + i);
|
||||
let result = this.parseTransaction(parseInt(i), transactions[i]);
|
||||
this.transactions.push(result);
|
||||
this.originalTransactions.push(lodashClonedeep(result));
|
||||
@@ -431,16 +485,31 @@ export default {
|
||||
|
||||
},
|
||||
removeTransaction: function (payload) {
|
||||
//console.log('removeTransaction()');
|
||||
//console.log(payload);
|
||||
//console.log('length: ' + this.transactions.length);
|
||||
this.transactions.splice(payload.index, 1);
|
||||
//console.log('length: ' + this.transactions.length);
|
||||
// console.log('removeTransaction()');
|
||||
// console.log('length : ' + this.transactions.length);
|
||||
// console.log('Remove index: ' + payload.index);
|
||||
|
||||
let index = 0;
|
||||
for (let i in this.transactions) {
|
||||
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
|
||||
// console.log('Now at index: ' + i);
|
||||
if (index === payload.index) {
|
||||
this.forceTransactionSubmission = true;
|
||||
//console.log('Delete!');
|
||||
this.transactions.splice(index, 1);
|
||||
//console.log(delete this.transactions[i]);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
$('#transactionTabs li:first-child a').tab('show');
|
||||
// console.log(this.transactions);
|
||||
// this.transactions.splice(payload.index, 1);
|
||||
// console.log('length: ' + this.transactions.length);
|
||||
|
||||
//this.originalTransactions.splice(payload.index, 1);
|
||||
// this.originalTransactions.splice(payload.index, 1);
|
||||
// this kills the original transactions.
|
||||
this.originalTransactions = [];
|
||||
//this.originalTransactions = [];
|
||||
},
|
||||
storeGroupTitle: function (payload) {
|
||||
this.groupTitle = payload;
|
||||
@@ -465,7 +534,9 @@ export default {
|
||||
this.transactions.push(newTransaction);
|
||||
},
|
||||
submitTransaction: function (event) {
|
||||
// console.log('submitTransaction()');
|
||||
event.preventDefault();
|
||||
this.enableSubmit = false;
|
||||
let submission = {transactions: []};
|
||||
|
||||
// parse data to see if we should submit anything at all:
|
||||
@@ -487,9 +558,11 @@ export default {
|
||||
}
|
||||
|
||||
// loop each transaction (edited by the user):
|
||||
// console.log('Start of loop submitTransaction');
|
||||
for (let i in this.transactions) {
|
||||
// console.log('Index ' + i);
|
||||
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
|
||||
|
||||
// console.log('Has index ' + i);
|
||||
// original transaction present:
|
||||
let currentTransaction = this.transactions[i];
|
||||
let originalTransaction = this.originalTransactions.hasOwnProperty(i) ? this.originalTransactions[i] : {};
|
||||
@@ -519,19 +592,28 @@ export default {
|
||||
for (let ii in basicFields) {
|
||||
if (basicFields.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
|
||||
let fieldName = basicFields[ii];
|
||||
// console.log('Now at field ' + fieldName);
|
||||
let submissionFieldName = fieldName;
|
||||
|
||||
// if the original is undefined and the new one is null, just skip it.
|
||||
if (currentTransaction[fieldName] === null && 'undefined' === typeof originalTransaction[fieldName]) {
|
||||
// console.log('Will continue and skip');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentTransaction[fieldName] !== originalTransaction[fieldName]) {
|
||||
if (currentTransaction[fieldName] !== originalTransaction[fieldName] || true === this.forceTransactionSubmission) {
|
||||
// console.log('we continue');
|
||||
// some fields are ignored:
|
||||
if ('foreign_amount' === submissionFieldName && '' === currentTransaction[fieldName]) {
|
||||
// console.log('we skip!');
|
||||
continue;
|
||||
}
|
||||
if ('foreign_currency_id' === submissionFieldName && 0 === currentTransaction[fieldName]) {
|
||||
if ('foreign_currency_id' === submissionFieldName && 0 === currentTransaction[fieldName] ) {
|
||||
// console.log('we skip!');
|
||||
continue;
|
||||
}
|
||||
if ('foreign_currency_id' === submissionFieldName && '0' === currentTransaction[fieldName] ) {
|
||||
// console.log('we skip!');
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -552,6 +634,7 @@ export default {
|
||||
// otherwise save them and remember them for submission:
|
||||
diff[submissionFieldName] = currentTransaction[fieldName];
|
||||
shouldSubmit = true;
|
||||
//console.log('Should submit is now TRUE');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,6 +667,10 @@ export default {
|
||||
if (typeof currentTransaction.selectedAttachments !== 'undefined' && true === currentTransaction.selectedAttachments) {
|
||||
shouldUpload = true;
|
||||
}
|
||||
if(true === shouldSubmit) {
|
||||
// set the date to whatever the date is:
|
||||
diff.date = this.date;
|
||||
}
|
||||
|
||||
if (this.date !== this.originalDate) {
|
||||
shouldSubmit = true;
|
||||
@@ -601,15 +688,18 @@ export default {
|
||||
submission.transactions.push(lodashClonedeep(diff));
|
||||
shouldSubmit = true;
|
||||
}
|
||||
// console.log('Should submit index ' + i + '?');
|
||||
// console.log(shouldSubmit);
|
||||
}
|
||||
}
|
||||
this.submitUpdate(submission, shouldSubmit, shouldLinks, shouldUpload);
|
||||
},
|
||||
|
||||
submitData: function (shouldSubmit, submission) {
|
||||
//console.log('submitData');
|
||||
// console.log('submitData');
|
||||
// console.log(submission);
|
||||
if (!shouldSubmit) {
|
||||
//console.log('No need!');
|
||||
// console.log('No need to submit transaction.');
|
||||
return new Promise((resolve) => {
|
||||
resolve({});
|
||||
});
|
||||
@@ -662,9 +752,10 @@ export default {
|
||||
return this.deleteAllOriginalLinks().then(() => this.submitNewLinks());
|
||||
},
|
||||
submitAttachments: function (shouldSubmit, response) {
|
||||
//console.log('submitAttachments');
|
||||
// console.log('submitAttachments');
|
||||
if (!shouldSubmit) {
|
||||
//console.log('no need!');
|
||||
// console.log('no need!');
|
||||
this.submittedAttachments = 1;
|
||||
return new Promise((resolve) => {
|
||||
resolve({});
|
||||
});
|
||||
@@ -697,17 +788,20 @@ export default {
|
||||
}
|
||||
},
|
||||
finaliseSubmission: function () {
|
||||
//console.log('finaliseSubmission');
|
||||
// console.log('finaliseSubmission (' + this.submittedAttachments + ')');
|
||||
if (0 === this.submittedAttachments) {
|
||||
return;
|
||||
}
|
||||
//console.log('continue (' + this.submittedAttachments + ')');
|
||||
// console.log('continue (' + this.submittedAttachments + ')');
|
||||
// console.log(this.stayHere);
|
||||
// console.log(this.inError);
|
||||
if (true === this.stayHere && false === this.inError) {
|
||||
//console.log('no error + no changes + no redirect');
|
||||
// show message:
|
||||
this.errorMessage = '';
|
||||
this.warningMessage = '';
|
||||
this.successMessage = this.$t('firefly.transaction_updated_link', {ID: this.groupId, title: this.returnedGroupTitle});
|
||||
|
||||
}
|
||||
// no error + changes + redirect
|
||||
if (false === this.stayHere && false === this.inError) {
|
||||
@@ -728,7 +822,7 @@ export default {
|
||||
}
|
||||
},
|
||||
submitUpdate: function (submission, shouldSubmit, shouldLinks, shouldUpload) {
|
||||
//console.log('submitUpdate()');
|
||||
// console.log('submitUpdate()');
|
||||
this.inError = false;
|
||||
|
||||
this.submitData(shouldSubmit, submission)
|
||||
@@ -843,23 +937,23 @@ export default {
|
||||
}
|
||||
return JSON.stringify(compare);
|
||||
},
|
||||
uploadAttachments: function (result) {
|
||||
//console.log('TODO, upload attachments.');
|
||||
if (0 === Object.keys(result).length) {
|
||||
|
||||
for (let i in this.transactions) {
|
||||
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
|
||||
|
||||
//console.log('updateField(' + i + ', transaction_journal_id, ' + result[i].transaction_journal_id + ')');
|
||||
this.updateField({index: i, field: 'transaction_journal_id', value: result[i].transaction_journal_id});
|
||||
}
|
||||
}
|
||||
//console.log('Transactions not changed, use original objects.');
|
||||
} else {
|
||||
//console.log('Transactions changed!');
|
||||
}
|
||||
this.submittedAttachments = true;
|
||||
},
|
||||
// uploadAttachments: function (result) {
|
||||
// //console.log('TODO, upload attachments.');
|
||||
// if (0 === Object.keys(result).length) {
|
||||
//
|
||||
// for (let i in this.transactions) {
|
||||
// if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
|
||||
//
|
||||
// //console.log('updateField(' + i + ', transaction_journal_id, ' + result[i].transaction_journal_id + ')');
|
||||
// this.updateField({index: i, field: 'transaction_journal_id', value: result[i].transaction_journal_id});
|
||||
// }
|
||||
// }
|
||||
// //console.log('Transactions not changed, use original objects.');
|
||||
// } else {
|
||||
// //console.log('Transactions changed!');
|
||||
// }
|
||||
// this.submittedAttachments = 0;
|
||||
// },
|
||||
|
||||
parseErrors: function (errors) {
|
||||
for (let i in this.transactions) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :id="'split_' + index" :class="'tab-pane' + (0===index ? ' active' : '')">
|
||||
<div :id="'split_' + index" :class="'tab-pane' + (0 === index ? ' active' : '')">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
@@ -32,6 +32,7 @@
|
||||
<button type="button" class="btn btn-danger btn-xs" @click="removeTransaction"><i class="fas fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- start of body -->
|
||||
<div class="row">
|
||||
@@ -330,7 +331,7 @@ export default {
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
required: false
|
||||
required: true
|
||||
},
|
||||
customFields: {
|
||||
type: Object,
|
||||
@@ -351,21 +352,28 @@ export default {
|
||||
sourceAllowedTypes: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: []
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
}, // allowed source account types.
|
||||
destinationAllowedTypes: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: []
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// allow switch?
|
||||
allowSwitch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
default: false
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
// console.log('SplitForm(' + this.index + ')');
|
||||
},
|
||||
methods: {
|
||||
removeTransaction: function () {
|
||||
// console.log('Will remove transaction ' + this.index);
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
<div v-if="transactions.length > 1" class="row">
|
||||
<div class="col">
|
||||
<!-- tabs -->
|
||||
<ul class="nav nav-pills ml-auto p-2">
|
||||
<li v-for="(transaction, index) in this.transactions" class="nav-item"><a :class="'nav-link' + (0===index ? ' active' : '')" :href="'#split_' + index"
|
||||
data-toggle="tab">
|
||||
<ul class="nav nav-pills ml-auto p-2" id="transactionTabs">
|
||||
<li v-for="(transaction, index) in this.transactions" class="nav-item"><a :class="'nav-link' + (0 === index ? ' active' : '')"
|
||||
:href="'#split_' + index"
|
||||
data-toggle="pill">
|
||||
<span v-if="'' !== transaction.description">{{ transaction.description }}</span>
|
||||
<span v-if="'' === transaction.description">Split {{ index + 1 }}</span>
|
||||
</a></li>
|
||||
@@ -36,6 +37,18 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "SplitPills",
|
||||
props: ['transactions']
|
||||
props: {
|
||||
transactions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -106,10 +106,12 @@
|
||||
"revenue_accounts": "\u0421\u043c\u0435\u0442\u043a\u0438 \u0437\u0430 \u043f\u0440\u0438\u0445\u043e\u0434\u0438",
|
||||
"add_another_split": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0434\u0440\u0443\u0433 \u0440\u0430\u0437\u0434\u0435\u043b",
|
||||
"actions": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
|
||||
"earned": "\u0421\u043f\u0435\u0447\u0435\u043b\u0435\u043d\u0438",
|
||||
"empty": "(\u043f\u0440\u0430\u0437\u043d\u043e)",
|
||||
"edit": "\u041f\u0440\u043e\u043c\u0435\u043d\u0438",
|
||||
"never": "\u041d\u0438\u043a\u043e\u0433\u0430",
|
||||
"account_type_Loan": "\u0417\u0430\u0435\u043c",
|
||||
"account_type_Mortgage": "\u0418\u043f\u043e\u0442\u0435\u043a\u0430",
|
||||
"timezone_difference": "Your browser reports time zone \"{local}\". Firefly III is configured for time zone \"{system}\". This chart may drift.",
|
||||
"stored_new_account_js": "New account \"<a href=\"accounts\/show\/{ID}\">{name}<\/a>\" stored!",
|
||||
"account_type_Debt": "\u0414\u044a\u043b\u0433",
|
||||
"delete": "\u0418\u0437\u0442\u0440\u0438\u0439",
|
||||
@@ -127,6 +129,12 @@
|
||||
"interest_calc_yearly": "\u0413\u043e\u0434\u0438\u0448\u043d\u043e",
|
||||
"liability_direction_credit": "I am owed this debt",
|
||||
"liability_direction_debit": "I owe this debt to somebody else",
|
||||
"liability_direction_credit_short": "(firefly.liability_direction_credit_short)",
|
||||
"liability_direction_debit_short": "(firefly.liability_direction_debit_short)",
|
||||
"account_type_debt": "(firefly.account_type_debt)",
|
||||
"account_type_loan": "(firefly.account_type_loan)",
|
||||
"left_in_debt": "(firefly.left_in_debt)",
|
||||
"account_type_mortgage": "(firefly.account_type_mortgage)",
|
||||
"save_transactions_by_moving_js": "No transactions|Save this transaction by moving it to another account. |Save these transactions by moving them to another account.",
|
||||
"none_in_select_list": "(\u043d\u0438\u0449\u043e)"
|
||||
},
|
||||
@@ -134,15 +142,21 @@
|
||||
"piggy_bank": "\u041a\u0430\u0441\u0438\u0447\u043a\u0430",
|
||||
"percentage": "%",
|
||||
"amount": "\u0421\u0443\u043c\u0430",
|
||||
"lastActivity": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442",
|
||||
"name": "\u0418\u043c\u0435",
|
||||
"role": "\u041f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0438",
|
||||
"iban": "IBAN",
|
||||
"interest": "\u041b\u0438\u0445\u0432\u0430",
|
||||
"interest_period": "\u043b\u0438\u0445\u0432\u0435\u043d \u043f\u0435\u0440\u0438\u043e\u0434",
|
||||
"liability_type": "\u0412\u0438\u0434 \u043d\u0430 \u0437\u0430\u0434\u044a\u043b\u0436\u0435\u043d\u0438\u0435\u0442\u043e",
|
||||
"liability_direction": "(list.liability_direction)",
|
||||
"currentBalance": "\u0422\u0435\u043a\u0443\u0449 \u0431\u0430\u043b\u0430\u043d\u0441",
|
||||
"next_expected_match": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449o \u043e\u0447\u0430\u043a\u0432\u0430\u043do \u0441\u044a\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435"
|
||||
},
|
||||
"config": {
|
||||
"html_language": "bg",
|
||||
"week_in_year_fns": "'Week' I, yyyy",
|
||||
"week_in_year_fns": "'Week' w, yyyy",
|
||||
"month_and_day_fns": "(config.month_and_day_fns)",
|
||||
"quarter_fns": "'Q'Q, yyyy",
|
||||
"half_year_fns": "'H{half}', yyyy"
|
||||
},
|
||||
|
||||
@@ -106,10 +106,12 @@
|
||||
"revenue_accounts": "P\u0159\u00edjmov\u00e9 \u00fa\u010dty",
|
||||
"add_another_split": "P\u0159idat dal\u0161\u00ed roz\u00fa\u010dtov\u00e1n\u00ed",
|
||||
"actions": "Akce",
|
||||
"earned": "Vyd\u011bl\u00e1no",
|
||||
"empty": "(pr\u00e1zdn\u00e9)",
|
||||
"edit": "Upravit",
|
||||
"never": "Nikdy",
|
||||
"account_type_Loan": "P\u016fj\u010dka",
|
||||
"account_type_Mortgage": "Hypot\u00e9ka",
|
||||
"timezone_difference": "Your browser reports time zone \"{local}\". Firefly III is configured for time zone \"{system}\". This chart may drift.",
|
||||
"stored_new_account_js": "New account \"<a href=\"accounts\/show\/{ID}\">{name}<\/a>\" stored!",
|
||||
"account_type_Debt": "Dluh",
|
||||
"delete": "Odstranit",
|
||||
@@ -127,6 +129,12 @@
|
||||
"interest_calc_yearly": "Za rok",
|
||||
"liability_direction_credit": "I am owed this debt",
|
||||
"liability_direction_debit": "I owe this debt to somebody else",
|
||||
"liability_direction_credit_short": "(firefly.liability_direction_credit_short)",
|
||||
"liability_direction_debit_short": "(firefly.liability_direction_debit_short)",
|
||||
"account_type_debt": "(firefly.account_type_debt)",
|
||||
"account_type_loan": "(firefly.account_type_loan)",
|
||||
"left_in_debt": "(firefly.left_in_debt)",
|
||||
"account_type_mortgage": "(firefly.account_type_mortgage)",
|
||||
"save_transactions_by_moving_js": "No transactions|Save this transaction by moving it to another account. |Save these transactions by moving them to another account.",
|
||||
"none_in_select_list": "(\u017e\u00e1dn\u00e9)"
|
||||
},
|
||||
@@ -134,15 +142,21 @@
|
||||
"piggy_bank": "Pokladni\u010dka",
|
||||
"percentage": "%",
|
||||
"amount": "\u010c\u00e1stka",
|
||||
"lastActivity": "Posledn\u00ed aktivita",
|
||||
"name": "Jm\u00e9no",
|
||||
"role": "Role",
|
||||
"iban": "IBAN",
|
||||
"interest": "\u00darok",
|
||||
"interest_period": "\u00farokov\u00e9 obdob\u00ed",
|
||||
"liability_type": "Typ z\u00e1vazku",
|
||||
"liability_direction": "(list.liability_direction)",
|
||||
"currentBalance": "Aktu\u00e1ln\u00ed z\u016fstatek",
|
||||
"next_expected_match": "Dal\u0161\u00ed o\u010dek\u00e1van\u00e1 shoda"
|
||||
},
|
||||
"config": {
|
||||
"html_language": "cs",
|
||||
"week_in_year_fns": "'Week' I, yyyy",
|
||||
"week_in_year_fns": "'Week' w, yyyy",
|
||||
"month_and_day_fns": "(config.month_and_day_fns)",
|
||||
"quarter_fns": "'Q'Q, yyyy",
|
||||
"half_year_fns": "'H{half}', yyyy"
|
||||
},
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
"categories": "Kategorien",
|
||||
"go_to_budgets": "Budgets anzeigen",
|
||||
"income": "Einnahmen \/ Einkommen",
|
||||
"go_to_deposits": "Zu Einlagen wechseln",
|
||||
"go_to_deposits": "Einnahmen anzeigen",
|
||||
"go_to_categories": "Kategorien anzeigen",
|
||||
"expense_accounts": "Ausgabekonten",
|
||||
"go_to_expenses": "Zu Ausgaben wechseln",
|
||||
"go_to_expenses": "Ausgaben anzeigen",
|
||||
"go_to_bills": "Rechnungen anzeigen",
|
||||
"bills": "Rechnungen",
|
||||
"last_thirty_days": "Letzte 30 Tage",
|
||||
@@ -106,10 +106,12 @@
|
||||
"revenue_accounts": "Einnahmekonten",
|
||||
"add_another_split": "Eine weitere Aufteilung hinzuf\u00fcgen",
|
||||
"actions": "Aktionen",
|
||||
"earned": "Eingenommen",
|
||||
"empty": "(leer)",
|
||||
"edit": "Bearbeiten",
|
||||
"never": "Nie",
|
||||
"account_type_Loan": "Darlehen",
|
||||
"account_type_Mortgage": "Hypothek",
|
||||
"timezone_difference": "Ihr Browser meldet die Zeitzone \u201e{local}\u201d. Firefly III ist aber f\u00fcr die Zeitzone \u201e{system}\u201d konfiguriert. Diese Karte kann deshalb abweichen.",
|
||||
"stored_new_account_js": "Neues Konto \"<a href=\"accounts\/show\/{ID}\">\u201e{name}\u201d<\/a>\" gespeichert!",
|
||||
"account_type_Debt": "Schuld",
|
||||
"delete": "L\u00f6schen",
|
||||
@@ -127,6 +129,12 @@
|
||||
"interest_calc_yearly": "J\u00e4hrlich",
|
||||
"liability_direction_credit": "Mir wird dies geschuldet",
|
||||
"liability_direction_debit": "Ich schulde dies jemandem",
|
||||
"liability_direction_credit_short": "(firefly.liability_direction_credit_short)",
|
||||
"liability_direction_debit_short": "(firefly.liability_direction_debit_short)",
|
||||
"account_type_debt": "(firefly.account_type_debt)",
|
||||
"account_type_loan": "(firefly.account_type_loan)",
|
||||
"left_in_debt": "(firefly.left_in_debt)",
|
||||
"account_type_mortgage": "(firefly.account_type_mortgage)",
|
||||
"save_transactions_by_moving_js": "Keine Buchungen|Speichern Sie diese Buchung, indem Sie sie auf ein anderes Konto verschieben. |Speichern Sie diese Buchungen, indem Sie sie auf ein anderes Konto verschieben.",
|
||||
"none_in_select_list": "(Keine)"
|
||||
},
|
||||
@@ -134,16 +142,22 @@
|
||||
"piggy_bank": "Sparschwein",
|
||||
"percentage": "%",
|
||||
"amount": "Betrag",
|
||||
"lastActivity": "Letzte Aktivit\u00e4t",
|
||||
"name": "Name",
|
||||
"role": "Rolle",
|
||||
"iban": "IBAN",
|
||||
"interest": "Zinsen",
|
||||
"interest_period": "Verzinsungszeitraum",
|
||||
"liability_type": "Verbindlichkeitsart",
|
||||
"liability_direction": "(list.liability_direction)",
|
||||
"currentBalance": "Aktueller Kontostand",
|
||||
"next_expected_match": "N\u00e4chste erwartete \u00dcbereinstimmung"
|
||||
},
|
||||
"config": {
|
||||
"html_language": "de",
|
||||
"week_in_year_fns": "'Woche' I, yyyy",
|
||||
"quarter_fns": "'Q'Q, yyyy",
|
||||
"week_in_year_fns": "'Woche' ww\/yyyy",
|
||||
"month_and_day_fns": "(config.month_and_day_fns)",
|
||||
"quarter_fns": "'Q'QQQ, yyyy",
|
||||
"half_year_fns": "'H{half}', yyyy"
|
||||
},
|
||||
"form": {
|
||||
|
||||
@@ -106,10 +106,12 @@
|
||||
"revenue_accounts": "\u0388\u03c3\u03bf\u03b4\u03b1",
|
||||
"add_another_split": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03b1\u03ba\u03cc\u03bc\u03b1 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd",
|
||||
"actions": "\u0395\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b5\u03c2",
|
||||
"earned": "\u039a\u03b5\u03c1\u03b4\u03ae\u03b8\u03b7\u03ba\u03b1\u03bd",
|
||||
"empty": "(\u03ba\u03b5\u03bd\u03cc)",
|
||||
"edit": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1",
|
||||
"never": "\u03a0\u03bf\u03c4\u03ad",
|
||||
"account_type_Loan": "\u0394\u03ac\u03bd\u03b5\u03b9\u03bf",
|
||||
"account_type_Mortgage": "\u03a5\u03c0\u03bf\u03b8\u03ae\u03ba\u03b7",
|
||||
"timezone_difference": "Your browser reports time zone \"{local}\". Firefly III is configured for time zone \"{system}\". This chart may drift.",
|
||||
"stored_new_account_js": "New account \"<a href=\"accounts\/show\/{ID}\">{name}<\/a>\" stored!",
|
||||
"account_type_Debt": "\u03a7\u03c1\u03ad\u03bf\u03c2",
|
||||
"delete": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae",
|
||||
@@ -127,6 +129,12 @@
|
||||
"interest_calc_yearly": "\u0391\u03bd\u03ac \u03ad\u03c4\u03bf\u03c2",
|
||||
"liability_direction_credit": "I am owed this debt",
|
||||
"liability_direction_debit": "I owe this debt to somebody else",
|
||||
"liability_direction_credit_short": "(firefly.liability_direction_credit_short)",
|
||||
"liability_direction_debit_short": "(firefly.liability_direction_debit_short)",
|
||||
"account_type_debt": "(firefly.account_type_debt)",
|
||||
"account_type_loan": "(firefly.account_type_loan)",
|
||||
"left_in_debt": "(firefly.left_in_debt)",
|
||||
"account_type_mortgage": "(firefly.account_type_mortgage)",
|
||||
"save_transactions_by_moving_js": "No transactions|Save this transaction by moving it to another account. |Save these transactions by moving them to another account.",
|
||||
"none_in_select_list": "(\u03c4\u03af\u03c0\u03bf\u03c4\u03b1)"
|
||||
},
|
||||
@@ -134,15 +142,21 @@
|
||||
"piggy_bank": "\u039a\u03bf\u03c5\u03bc\u03c0\u03b1\u03c1\u03ac\u03c2",
|
||||
"percentage": "pct.",
|
||||
"amount": "\u03a0\u03bf\u03c3\u03cc",
|
||||
"lastActivity": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b1 \u03b4\u03c1\u03b1\u03c3\u03c4\u03b7\u03c1\u03b9\u03cc\u03c4\u03b7\u03c4\u03b1",
|
||||
"name": "\u038c\u03bd\u03bf\u03bc\u03b1",
|
||||
"role": "\u03a1\u03cc\u03bb\u03bf\u03c2",
|
||||
"iban": "IBAN",
|
||||
"interest": "\u03a4\u03cc\u03ba\u03bf\u03c2",
|
||||
"interest_period": "\u03c4\u03bf\u03ba\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03c0\u03b5\u03c1\u03af\u03bf\u03b4\u03bf\u03c2",
|
||||
"liability_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c5\u03c0\u03bf\u03c7\u03c1\u03ad\u03c9\u03c3\u03b7\u03c2",
|
||||
"liability_direction": "(list.liability_direction)",
|
||||
"currentBalance": "\u03a4\u03c1\u03ad\u03c7\u03bf\u03bd \u03c5\u03c0\u03cc\u03bb\u03bf\u03b9\u03c0\u03bf",
|
||||
"next_expected_match": "\u0395\u03c0\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b1\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7"
|
||||
},
|
||||
"config": {
|
||||
"html_language": "el",
|
||||
"week_in_year_fns": "'Week' I, yyyy",
|
||||
"week_in_year_fns": "'Week' w, yyyy",
|
||||
"month_and_day_fns": "(config.month_and_day_fns)",
|
||||
"quarter_fns": "'Q'Q, yyyy",
|
||||
"half_year_fns": "'H{half}', yyyy"
|
||||
},
|
||||
|
||||
@@ -106,10 +106,12 @@
|
||||
"revenue_accounts": "Revenue accounts",
|
||||
"add_another_split": "Add another split",
|
||||
"actions": "Actions",
|
||||
"earned": "Earned",
|
||||
"empty": "(empty)",
|
||||
"edit": "Edit",
|
||||
"never": "Never",
|
||||
"account_type_Loan": "Loan",
|
||||
"account_type_Mortgage": "Mortgage",
|
||||
"timezone_difference": "Your browser reports time zone \"{local}\". Firefly III is configured for time zone \"{system}\". This chart may drift.",
|
||||
"stored_new_account_js": "New account \"<a href=\"accounts\/show\/{ID}\">{name}<\/a>\" stored!",
|
||||
"account_type_Debt": "Debt",
|
||||
"delete": "Delete",
|
||||
@@ -127,6 +129,12 @@
|
||||
"interest_calc_yearly": "Per year",
|
||||
"liability_direction_credit": "I am owed this debt",
|
||||
"liability_direction_debit": "I owe this debt to somebody else",
|
||||
"liability_direction_credit_short": "(firefly.liability_direction_credit_short)",
|
||||
"liability_direction_debit_short": "(firefly.liability_direction_debit_short)",
|
||||
"account_type_debt": "(firefly.account_type_debt)",
|
||||
"account_type_loan": "(firefly.account_type_loan)",
|
||||
"left_in_debt": "(firefly.left_in_debt)",
|
||||
"account_type_mortgage": "(firefly.account_type_mortgage)",
|
||||
"save_transactions_by_moving_js": "No transactions|Save this transaction by moving it to another account. |Save these transactions by moving them to another account.",
|
||||
"none_in_select_list": "(none)"
|
||||
},
|
||||
@@ -134,15 +142,21 @@
|
||||
"piggy_bank": "Piggy bank",
|
||||
"percentage": "pct.",
|
||||
"amount": "Amount",
|
||||
"lastActivity": "Last activity",
|
||||
"name": "Name",
|
||||
"role": "Role",
|
||||
"iban": "IBAN",
|
||||
"interest": "Interest",
|
||||
"interest_period": "interest period",
|
||||
"liability_type": "Type of liability",
|
||||
"liability_direction": "(list.liability_direction)",
|
||||
"currentBalance": "Current balance",
|
||||
"next_expected_match": "Next expected match"
|
||||
},
|
||||
"config": {
|
||||
"html_language": "en-gb",
|
||||
"week_in_year_fns": "'Week' I, yyyy",
|
||||
"week_in_year_fns": "'Week' w, yyyy",
|
||||
"month_and_day_fns": "(config.month_and_day_fns)",
|
||||
"quarter_fns": "'Q'Q, yyyy",
|
||||
"half_year_fns": "'H{half}', yyyy"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user