mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-17 17:57:09 +00:00
Compare commits
115 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 | ||
|
adc52f7b63 | ||
|
f8cf02bda1 | ||
|
4b14ad9770 | ||
|
2a630b0a50 | ||
|
1a311664e8 | ||
|
0a4e3edf43 | ||
|
9b0b80d1d4 | ||
|
dcd123a9ec | ||
|
e06452d97c | ||
|
e5b4e7afe0 | ||
|
0ea22269a0 | ||
|
577dcfa938 | ||
|
99f08da4df | ||
|
2f9724e7ca | ||
|
8cda89569c | ||
|
5421e30293 | ||
|
a2deff0f7a | ||
|
bff661fe69 | ||
|
f6b890e284 | ||
|
acb8fa522b |
@@ -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
|
||||
@@ -175,6 +175,10 @@ MAP_DEFAULT_ZOOM=6
|
||||
# For full instructions on these settings please visit:
|
||||
# https://docs.firefly-iii.org/advanced-installation/authentication
|
||||
# If you use Docker or similar, you can set this variable from a file by appending it with _FILE
|
||||
#
|
||||
# If you enable 'ldap' AND you run Docker, the Docker image will contact packagist.org
|
||||
# This is necessary to download the required packages.
|
||||
#
|
||||
LOGIN_PROVIDER=eloquent
|
||||
|
||||
# It's also possible to change the way users are authenticated. You could use Authelia for example.
|
||||
|
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;
|
||||
@@ -162,9 +163,8 @@ class ExportData extends Command
|
||||
*/
|
||||
private function parseOptions(): array
|
||||
{
|
||||
$start = $this->getDateParameter('start');
|
||||
$end = $this->getDateParameter('end');
|
||||
exit;
|
||||
$start = $this->getDateParameter('start');
|
||||
$end = $this->getDateParameter('end');
|
||||
$accounts = $this->getAccountsParameter();
|
||||
$export = $this->getExportDirectory();
|
||||
|
||||
@@ -242,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.
|
||||
|
@@ -59,7 +59,7 @@ class TwoFactorController extends Controller
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = Preferences::get('mfa_history', [])->data;
|
||||
$mfaCode = $request->get('one_time_password');
|
||||
$mfaCode = (string)$request->get('one_time_password');
|
||||
|
||||
// is in history? then refuse to use it.
|
||||
if ($this->inMFAHistory($mfaCode, $mfaHistory)) {
|
||||
|
@@ -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,
|
||||
|
@@ -135,16 +135,21 @@ class BudgetLimitController extends Controller
|
||||
if (null === $currency || null === $budget) {
|
||||
throw new FireflyException('No valid currency or budget.');
|
||||
}
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
$amount = (string)$request->get('amount');
|
||||
$start->startOfDay();
|
||||
$end->startOfDay();
|
||||
|
||||
if ('' === $amount) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
$limit = $this->blRepository->find($budget, $currency, $start, $end);
|
||||
if (null !== $limit) {
|
||||
$limit->amount = $request->get('amount');
|
||||
$limit->amount = $amount;
|
||||
$limit->save();
|
||||
}
|
||||
if (null === $limit) {
|
||||
@@ -154,7 +159,7 @@ class BudgetLimitController extends Controller
|
||||
'currency_id' => (int)$request->get('transaction_currency_id'),
|
||||
'start_date' => $start,
|
||||
'end_date' => $end,
|
||||
'amount' => $request->get('amount'),
|
||||
'amount' => $amount,
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -176,7 +181,7 @@ class BudgetLimitController extends Controller
|
||||
return response()->json($array);
|
||||
}
|
||||
|
||||
return redirect(route('budgets.index', [$start->format('Y-m-d'), $end->format('Y-m-d')]));
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +192,10 @@ class BudgetLimitController extends Controller
|
||||
*/
|
||||
public function update(Request $request, BudgetLimit $budgetLimit): JsonResponse
|
||||
{
|
||||
$amount = $request->get('amount');
|
||||
$amount = (string)$request->get('amount');
|
||||
if ('' === $amount) {
|
||||
$amount = '0';
|
||||
}
|
||||
|
||||
$limit = $this->blRepository->update($budgetLimit, ['amount' => $amount]);
|
||||
$array = $limit->toArray();
|
||||
|
@@ -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'];
|
||||
|
@@ -73,12 +73,10 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->createDefaultRuleGroup();
|
||||
$this->createDefaultRule();
|
||||
$this->ruleGroupRepos->resetOrder();
|
||||
$ruleGroups = $this->ruleGroupRepos->getRuleGroupsWithRules(null);
|
||||
$ruleGroups = $this->ruleGroupRepos->getAllRuleGroupsWithRules(null);
|
||||
|
||||
return prefixView('rules.index', compact('ruleGroups'));
|
||||
}
|
||||
|
@@ -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],
|
||||
|
@@ -100,7 +100,7 @@ class CreateController extends Controller
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$cash = $repository->getCashAccount();
|
||||
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
|
||||
$subTitle = (string)trans('breadcrumbs.create_new_transaction');
|
||||
$subTitle = (string)trans(sprintf('breadcrumbs.create_%s', strtolower((string)$objectType)));
|
||||
$subTitleIcon = 'fa-plus';
|
||||
$optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
|
||||
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
|
||||
|
@@ -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.
|
||||
|
@@ -65,7 +65,7 @@ class BudgetFormStoreRequest extends FormRequest
|
||||
'active' => 'numeric|between:0,1',
|
||||
'auto_budget_type' => 'numeric|between:0,2',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
];
|
||||
}
|
||||
|
@@ -75,7 +75,7 @@ class BudgetFormUpdateRequest extends FormRequest
|
||||
'active' => 'numeric|between:0,1',
|
||||
'auto_budget_type' => 'numeric|between:0,2',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
];
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -22,7 +22,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -33,13 +32,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
/**
|
||||
* FireflyIII\Models\Preference
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property int|string|array|null $data
|
||||
* @property-read User $user
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property int|string|array|null $data
|
||||
* @property-read User $user
|
||||
* @method static Builder|Preference newModelQuery()
|
||||
* @method static Builder|Preference newQuery()
|
||||
* @method static Builder|Preference query()
|
||||
@@ -73,19 +72,29 @@ class Preference extends Model
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @throws NotFoundHttpException
|
||||
* @return Preference
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public static function routeBinder(string $value): Preference
|
||||
{
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
/** @var Preference $preference */
|
||||
/** @var Preference|null $preference */
|
||||
$preference = $user->preferences()->where('name', $value)->first();
|
||||
if (null !== $preference) {
|
||||
return $preference;
|
||||
}
|
||||
$default = config('firefly.default_preferences');
|
||||
if (array_key_exists($value, $default)) {
|
||||
$preference = new Preference;
|
||||
$preference->name = $value;
|
||||
$preference->data = $default[$value];
|
||||
$preference->user_id = $user->id;
|
||||
$preference->save();
|
||||
|
||||
return $preference;
|
||||
}
|
||||
}
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
@@ -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.*']);
|
||||
}
|
||||
|
||||
@@ -258,6 +258,58 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $filter
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAllRuleGroupsWithRules(?string $filter): Collection
|
||||
{
|
||||
$groups = $this->user->ruleGroups()
|
||||
->orderBy('order', 'ASC')
|
||||
->with(
|
||||
[
|
||||
'rules' => static function (HasMany $query) {
|
||||
$query->orderBy('order', 'ASC');
|
||||
},
|
||||
'rules.ruleTriggers' => static function (HasMany $query) {
|
||||
$query->orderBy('order', 'ASC');
|
||||
},
|
||||
'rules.ruleActions' => static function (HasMany $query) {
|
||||
$query->orderBy('order', 'ASC');
|
||||
},
|
||||
]
|
||||
)->get();
|
||||
if (null === $filter) {
|
||||
return $groups;
|
||||
}
|
||||
Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
|
||||
|
||||
return $groups->map(
|
||||
function (RuleGroup $group) use ($filter) {
|
||||
Log::debug(sprintf('Now filtering group #%d', $group->id));
|
||||
// filter the rules in the rule group:
|
||||
$group->rules = $group->rules->filter(
|
||||
function (Rule $rule) use ($filter) {
|
||||
Log::debug(sprintf('Now filtering rule #%d', $rule->id));
|
||||
foreach ($rule->ruleTriggers as $trigger) {
|
||||
if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) {
|
||||
Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
return $group;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RuleGroup $group
|
||||
*
|
||||
@@ -274,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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,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')
|
||||
|
@@ -114,6 +114,15 @@ interface RuleGroupRepositoryInterface
|
||||
*/
|
||||
public function getRuleGroupsWithRules(?string $filter): Collection;
|
||||
|
||||
/**
|
||||
* Also inactive groups.
|
||||
*
|
||||
* @param string|null $filter
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAllRuleGroupsWithRules(?string $filter): Collection;
|
||||
|
||||
/**
|
||||
* @param RuleGroup $group
|
||||
*
|
||||
|
@@ -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']) {
|
||||
|
@@ -292,7 +292,7 @@ class JournalUpdateService
|
||||
$validator->setTransactionType($expectedType);
|
||||
$validator->setUser($this->transactionJournal->user);
|
||||
$validator->source = $this->getValidSourceAccount();
|
||||
$result = $validator->validateDestination($destId, $destName, null);
|
||||
$result = $validator->validateDestination($destId, $destName, null);
|
||||
Log::debug(sprintf('hasValidDestinationAccount(%d, "%s") will return %s', $destId, $destName, var_export($result, true)));
|
||||
|
||||
// TODO typeOverrule: the account validator may have another opinion on the transaction type.
|
||||
@@ -527,6 +527,10 @@ class JournalUpdateService
|
||||
Log::debug('Will update budget.');
|
||||
$this->storeBudget($this->transactionJournal, new NullArrayObject($this->data));
|
||||
}
|
||||
// is transfer? remove budget
|
||||
if (TransactionType::TRANSFER === $this->transactionJournal->transactionType->type) {
|
||||
$this->transactionJournal->budgets()->sync([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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) {
|
||||
@@ -174,6 +175,11 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
private function fireRule(Rule $rule): bool
|
||||
{
|
||||
Log::debug(sprintf('Now going to fire rule #%d', $rule->id));
|
||||
if (false === $rule->active) {
|
||||
Log::debug(sprintf('Rule #%d is not active!', $rule->id));
|
||||
|
||||
return false;
|
||||
}
|
||||
if (true === $rule->strict) {
|
||||
Log::debug(sprintf('Rule #%d is a strict rule.', $rule->id));
|
||||
|
||||
@@ -222,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) {
|
||||
@@ -236,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']));
|
||||
@@ -368,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;
|
||||
@@ -443,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'));
|
||||
}
|
||||
}
|
||||
|
58
changelog.md
58
changelog.md
@@ -2,6 +2,64 @@
|
||||
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.
|
||||
|
||||
### Fixed
|
||||
- [Issue 4656](https://github.com/firefly-iii/firefly-iii/issues/4656) [issue 4660](https://github.com/firefly-iii/firefly-iii/issues/4660) Various fixes in the v2 layout.
|
||||
- [Issue 4663](https://github.com/firefly-iii/firefly-iii/issues/4663) It was possible to assign a budget to a transfer.
|
||||
- [Issue 4664](https://github.com/firefly-iii/firefly-iii/issues/4664) Null pointer in bulk editor
|
||||
- [Issue 4668](https://github.com/firefly-iii/firefly-iii/issues/4668) Inactive rule groups would not be listed.
|
||||
|
||||
## 5.5.7 (API 1.5.2) 2021-04-11
|
||||
|
||||
### Added
|
||||
|
@@ -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",
|
||||
|
1174
composer.lock
generated
1174
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.7',
|
||||
'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,],
|
||||
@@ -850,4 +849,11 @@ return [
|
||||
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
||||
'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
|
||||
'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
|
||||
'default_preferences' => [
|
||||
'frontPageAccounts' => [],
|
||||
'listPageSize' => 50,
|
||||
'currencyPreference' => 'EUR',
|
||||
'language' => 'en_US',
|
||||
'locale' => 'equal',
|
||||
],
|
||||
];
|
||||
|
@@ -47,7 +47,7 @@ yarn prod
|
||||
# mv public/css ../public/v2
|
||||
|
||||
# also copy fonts
|
||||
cp -r fonts ../public
|
||||
#cp -r fonts ../public/v2/css
|
||||
|
||||
# remove built stuff
|
||||
rm -rf public
|
||||
rm -rf public/
|
||||
|
@@ -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",
|
||||
|
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.21",
|
||||
"date-fns": "^2.21.1",
|
||||
"laravel-mix": "^6.0.6",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
@@ -29,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>
|
||||
|
@@ -46,7 +46,8 @@
|
||||
class="btn btn-secondary"
|
||||
@click="resetDate"
|
||||
><i class="fas fa-history"></i></button>
|
||||
<button id="dropdownMenuButton" :title="$t('firefly.select_period')" aria-expanded="false" aria-haspopup="true" class="btn btn-secondary dropdown-toggle"
|
||||
<button id="dropdownMenuButton" :title="$t('firefly.select_period')" aria-expanded="false" aria-haspopup="true"
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
type="button">
|
||||
<i class="fas fa-list"></i>
|
||||
@@ -78,8 +79,23 @@
|
||||
import {createNamespacedHelpers} from "vuex";
|
||||
import Vue from "vue";
|
||||
import DatePicker from "v-calendar/lib/components/date-picker.umd";
|
||||
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
|
||||
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')
|
||||
|
||||
Vue.component('date-picker', DatePicker)
|
||||
|
||||
@@ -130,50 +146,400 @@ export default {
|
||||
this.generatePeriods()
|
||||
return false;
|
||||
},
|
||||
generatePeriods: function () {
|
||||
this.periods = [];
|
||||
// create periods.
|
||||
let today;
|
||||
let end;
|
||||
|
||||
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);
|
||||
generateDaily: function () {
|
||||
let today = new Date(this.range.start);
|
||||
// yesterday
|
||||
this.periods.push(
|
||||
{
|
||||
start: firstDayOfMonth.toDateString(),
|
||||
end: lastDayOfMonth.toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(firstDayOfMonth)
|
||||
start: startOfDay(subDays(today, 1)).toDateString(),
|
||||
end: endOfDay(subDays(today, 1)).toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(subDays(today, 1))
|
||||
}
|
||||
);
|
||||
|
||||
// today
|
||||
this.periods.push(
|
||||
{
|
||||
start: startOfDay(today).toDateString(),
|
||||
end: endOfDay(today).toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(today)
|
||||
}
|
||||
);
|
||||
|
||||
// tomorrow:
|
||||
this.periods.push(
|
||||
{
|
||||
start: startOfDay(addDays(today, 1)).toDateString(),
|
||||
end: endOfDay(addDays(today, 1)).toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(addDays(today, 1))
|
||||
}
|
||||
);
|
||||
|
||||
// The Day After Tomorrow dun-dun-dun!
|
||||
this.periods.push(
|
||||
{
|
||||
start: startOfDay(addDays(today, 2)).toDateString(),
|
||||
end: endOfDay(addDays(today, 2)).toDateString(),
|
||||
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(addDays(today, 2))
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
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(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// this week
|
||||
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(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// next week
|
||||
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(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
},
|
||||
generateMonthly: function () {
|
||||
let today = new Date(this.range.start);
|
||||
// previous month
|
||||
let start = startOfDay(startOfMonth(subMonths(today, 1)));
|
||||
let end = endOfDay(endOfMonth(subMonths(today, 1)));
|
||||
this.periods.push(
|
||||
{
|
||||
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)
|
||||
}
|
||||
);
|
||||
|
||||
},
|
||||
generateQuarterly: function () {
|
||||
let today = new Date(this.range.start);
|
||||
|
||||
// last quarter
|
||||
let start = startOfDay(startOfQuarter(subQuarters(today, 1)));
|
||||
let end = endOfDay(endOfQuarter(subQuarters(today, 1)));
|
||||
let dateFormat = this.$t('config.quarter_fns');
|
||||
let title = format(start, dateFormat);
|
||||
|
||||
// last week
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// this quarter
|
||||
start = startOfDay(startOfQuarter(today));
|
||||
end = endOfDay(endOfQuarter(today));
|
||||
title = format(start, dateFormat);
|
||||
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
// next quarter
|
||||
start = startOfDay(startOfQuarter(addQuarters(today, 1)));
|
||||
end = endOfDay(endOfQuarter(addQuarters(today, 1)));
|
||||
title = format(start, dateFormat);
|
||||
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
},
|
||||
generateHalfYearly: function () {
|
||||
let today = new Date(this.range.start);
|
||||
let start;
|
||||
let end;
|
||||
let title = 'todo';
|
||||
let half = 1;
|
||||
|
||||
|
||||
// its currently first half of year:
|
||||
if (today.getMonth() <= 5) {
|
||||
// previous year, last half:
|
||||
start = today;
|
||||
start.setFullYear(start.getFullYear() - 1);
|
||||
start.setMonth(6);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = start;
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
half = 2;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// this year, first half:
|
||||
start = today;
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = today;
|
||||
end.setMonth(5);
|
||||
end.setDate(30);
|
||||
end = endOfDay(start);
|
||||
half = 1;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// this year, second half:
|
||||
start = today;
|
||||
start.setMonth(6);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = start;
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
half = 2;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
// this year, first half:
|
||||
start = today;
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = start;
|
||||
end.setMonth(5);
|
||||
end.setDate(30);
|
||||
end = endOfDay(end);
|
||||
half = 1;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// this year, current (second) half:
|
||||
start = today;
|
||||
start.setMonth(6);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = today;
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(start);
|
||||
half = 2;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
|
||||
// next year, first half:
|
||||
start = today;
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
end = start;
|
||||
end.setMonth(5);
|
||||
end.setDate(30);
|
||||
end = endOfDay(end);
|
||||
half = 1;
|
||||
title = format(start, this.$t('config.half_year_fns', {half: half}));
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: title
|
||||
}
|
||||
);
|
||||
},
|
||||
generateYearly: function () {
|
||||
let today = new Date(this.range.start);
|
||||
let start;
|
||||
let end;
|
||||
let title;
|
||||
|
||||
// last year
|
||||
start = new Date(today);
|
||||
start.setFullYear(start.getFullYear() - 1);
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
|
||||
end = new Date(today);
|
||||
end.setFullYear(end.getFullYear() - 1);
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: start.getFullYear()
|
||||
}
|
||||
);
|
||||
|
||||
// this year
|
||||
start = new Date(today);
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
|
||||
end = new Date(today);
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: start.getFullYear()
|
||||
}
|
||||
);
|
||||
// next year
|
||||
start = new Date(today);
|
||||
start.setFullYear(start.getFullYear() + 1);
|
||||
start.setMonth(0);
|
||||
start.setDate(1);
|
||||
start = startOfDay(start);
|
||||
|
||||
end = new Date(today);
|
||||
end.setFullYear(end.getFullYear() + 1);
|
||||
end.setMonth(11);
|
||||
end.setDate(31);
|
||||
end = endOfDay(end);
|
||||
|
||||
this.periods.push(
|
||||
{
|
||||
start: start.toDateString(),
|
||||
end: end.toDateString(),
|
||||
title: start.getFullYear()
|
||||
}
|
||||
);
|
||||
},
|
||||
generatePeriods: function () {
|
||||
this.periods = [];
|
||||
//console.log('The view range is "' + this.viewRange + '".');
|
||||
switch (this.viewRange) {
|
||||
case '1D':
|
||||
this.generateDaily();
|
||||
break;
|
||||
case '1W':
|
||||
this.generateWeekly();
|
||||
break;
|
||||
case '1M':
|
||||
this.generateMonthly();
|
||||
break;
|
||||
case '3M':
|
||||
this.generateQuarterly();
|
||||
break;
|
||||
case '6M':
|
||||
this.generateHalfYearly();
|
||||
break;
|
||||
case '1Y':
|
||||
this.generateYearly();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// last 7 days
|
||||
today = new Date;
|
||||
end = new Date;
|
||||
let today = new Date;
|
||||
let end = new Date;
|
||||
end.setDate(end.getDate() - 7);
|
||||
this.periods.push(
|
||||
{
|
||||
|
@@ -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,12 +46,11 @@ 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)));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
name: "MainAccount",
|
||||
components: {}, // MainAccountChart
|
||||
@@ -63,27 +59,20 @@ export default {
|
||||
loading: true,
|
||||
error: false,
|
||||
ready: false,
|
||||
initialised: false,
|
||||
dataCollection: {},
|
||||
chartOptions: {},
|
||||
_chart: null,
|
||||
localTimeZone: '',
|
||||
systemTimeZone: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.ready = true;
|
||||
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']),
|
||||
...mapGetters('dashboard/index', ['start', 'end']),
|
||||
'datesReady': function () {
|
||||
return null !== this.start && null !== this.end && this.ready;
|
||||
},
|
||||
timezoneDifference: function() {
|
||||
return this.localTimeZone !== this.systemTimeZone;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -93,18 +82,20 @@ export default {
|
||||
}
|
||||
},
|
||||
start: function () {
|
||||
//this.initialiseChart();
|
||||
this.updateChart();
|
||||
},
|
||||
end: function () {
|
||||
//this.initialiseChart();
|
||||
this.updateChart();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
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 => {
|
||||
@@ -116,18 +107,39 @@ export default {
|
||||
this.drawChart();
|
||||
})
|
||||
.catch(error => {
|
||||
// console.log('Has error!');
|
||||
// console.log(error);
|
||||
console.log('Has error!');
|
||||
console.log(error);
|
||||
this.error = true;
|
||||
});
|
||||
},
|
||||
drawChart: function () {
|
||||
this._chart = new ChartJs.Chart(this.$refs.canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data: this.dataCollection,
|
||||
options: this.chartOptions
|
||||
}
|
||||
);
|
||||
//console.log('drawChart');
|
||||
if ('undefined' !== typeof this._chart) {
|
||||
// console.log('update!');
|
||||
this._chart.data = this.dataCollection;
|
||||
this._chart.update();
|
||||
this.initialised = true;
|
||||
}
|
||||
|
||||
if ('undefined' === typeof this._chart) {
|
||||
// console.log('new!');
|
||||
this._chart = new ChartJs.Chart(this.$refs.canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data: this.dataCollection,
|
||||
options: this.chartOptions
|
||||
}
|
||||
);
|
||||
this.initialised = true;
|
||||
}
|
||||
},
|
||||
updateChart: function () {
|
||||
// console.log('updateChart');
|
||||
if (this.initialised) {
|
||||
// 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')
|
||||
|
||||
@@ -123,7 +124,7 @@ export default {
|
||||
initialiseList: function () {
|
||||
this.loading = true;
|
||||
this.accounts = [];
|
||||
axios.get('./api/v1/preferences/frontpageAccounts')
|
||||
axios.get('./api/v1/preferences/frontPageAccounts')
|
||||
.then(response => {
|
||||
this.loadAccounts(response);
|
||||
}
|
||||
@@ -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>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user