Compare commits

..

17 Commits

Author SHA1 Message Date
github-actions
746f1fd300 Auto commit for release 'develop' on 2024-04-02 2024-04-02 07:47:24 +02:00
James Cole
9e5faf919f Add space 2024-04-02 07:40:40 +02:00
James Cole
cc6cbe6605 Add something to changelog before I forget. 2024-04-02 07:01:13 +02:00
James Cole
dc6d708897 Replace frontpageAccounts variable name. 2024-04-02 06:59:40 +02:00
James Cole
6189d24b98 Replace variable names. 2024-04-01 20:26:02 +02:00
James Cole
f6e28dc88f Fine tune preferences to handle multi-administration options. 2024-04-01 19:59:21 +02:00
James Cole
75ea035630 Preferences can be administration specific. Not yet really working. 2024-04-01 18:18:48 +02:00
James Cole
4cdb14301d Add administration specific thing to preferences. 2024-04-01 18:03:43 +02:00
James Cole
9f95221ba3 Various code cleanup. 2024-04-01 17:43:31 +02:00
James Cole
e3a67be412 Clear user groups when loading 2024-04-01 16:07:14 +02:00
James Cole
5749b642ce Wrong response code. 2024-04-01 16:07:03 +02:00
James Cole
baff7c67f9 Expand user group views and translations 2024-04-01 15:41:16 +02:00
James Cole
ccc005942f Expand user group pages. 2024-04-01 15:40:53 +02:00
James Cole
5b83c33039 Turns out it's pointless to add "default_administration", you can force that already through the owner role (which is already in place). 2024-04-01 14:15:35 +02:00
James Cole
cc32578c5f Add new strings. 2024-04-01 14:04:36 +02:00
James Cole
80f410835b Expand models, user groups need more properties. 2024-04-01 14:04:22 +02:00
James Cole
b537a3145d Expand financial administrations views. 2024-04-01 14:01:52 +02:00
129 changed files with 85470 additions and 84448 deletions

View File

@@ -9,16 +9,16 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "composer/pcre", "name": "composer/pcre",
"version": "3.1.2", "version": "3.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/pcre.git", "url": "https://github.com/composer/pcre.git",
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -60,7 +60,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/composer/pcre/issues", "issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.1.2" "source": "https://github.com/composer/pcre/tree/3.1.3"
}, },
"funding": [ "funding": [
{ {
@@ -76,20 +76,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-03-07T15:38:35+00:00" "time": "2024-03-19T10:26:25+00:00"
}, },
{ {
"name": "composer/xdebug-handler", "name": "composer/xdebug-handler",
"version": "3.0.3", "version": "3.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/xdebug-handler.git", "url": "https://github.com/composer/xdebug-handler.git",
"reference": "ced299686f41dce890debac69273b47ffe98a40c" "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
"reference": "ced299686f41dce890debac69273b47ffe98a40c", "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -100,7 +100,7 @@
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.0", "phpstan/phpstan": "^1.0",
"phpstan/phpstan-strict-rules": "^1.1", "phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^6.0" "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@@ -124,9 +124,9 @@
"performance" "performance"
], ],
"support": { "support": {
"irc": "irc://irc.freenode.org/composer", "irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/xdebug-handler/issues", "issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/3.0.3" "source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
}, },
"funding": [ "funding": [
{ {
@@ -142,7 +142,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-02-25T21:32:43+00:00" "time": "2024-03-26T18:29:49+00:00"
}, },
{ {
"name": "pdepend/pdepend", "name": "pdepend/pdepend",

View File

@@ -83,17 +83,17 @@ class AccountController extends Controller
// user's preferences // user's preferences
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray(); $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();
/** @var Preference $frontPage */ /** @var Preference $frontpage */
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$default = app('amount')->getDefaultCurrency(); $default = app('amount')->getDefaultCurrency();
if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) { if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
$frontPage->data = $defaultSet; $frontpage->data = $defaultSet;
$frontPage->save(); $frontpage->save();
} }
// get accounts: // get accounts:
$accounts = $this->repository->getAccountsById($frontPage->data); $accounts = $this->repository->getAccountsById($frontpage->data);
$chartData = []; $chartData = [];
/** @var Account $account */ /** @var Account $account */

View File

@@ -98,14 +98,14 @@ class AccountController extends Controller
// user's preferences // user's preferences
if (0 === $accounts->count()) { if (0 === $accounts->count()) {
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray(); $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) { if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
$frontPage->data = $defaultSet; $frontpage->data = $defaultSet;
$frontPage->save(); $frontpage->save();
} }
$accounts = $this->repository->getAccountsById($frontPage->data); $accounts = $this->repository->getAccountsById($frontpage->data);
} }
// both options are overruled by "preselected" // both options are overruled by "preselected"

View File

@@ -54,6 +54,14 @@ class UpdateController extends Controller
); );
} }
public function useUserGroup(UserGroup $userGroup): JsonResponse
{
// group validation is already in place, so can just update the user.
$this->repository->useUserGroup($userGroup);
return response()->json([], 204);
}
public function update(UpdateRequest $request, UserGroup $userGroup): JsonResponse public function update(UpdateRequest $request, UserGroup $userGroup): JsonResponse
{ {
$all = $request->getAll(); $all = $request->getAll();

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V2\Request\UserGroup;
use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
use FireflyIII\Rules\IsDefaultUserGroupName;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@@ -53,7 +54,7 @@ class UpdateRequest extends FormRequest
$userGroup = $this->route()->parameter('userGroup'); $userGroup = $this->route()->parameter('userGroup');
return [ return [
'title' => sprintf('required|min:1|max:255|unique:user_groups,title,%d', $userGroup->id), 'title' => ['required', 'min:1', 'max:255', sprintf('unique:user_groups,title,%d', $userGroup->id), new IsDefaultUserGroupName($userGroup)],
]; ];
} }
} }

View File

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

View File

@@ -50,7 +50,7 @@ class FixFrontpageAccounts extends Command
/** @var User $user */ /** @var User $user */
foreach ($users as $user) { foreach ($users as $user) {
$preference = app('preferences')->getForUser($user, 'frontPageAccounts'); $preference = app('preferences')->getForUser($user, 'frontpageAccounts');
if (null !== $preference) { if (null !== $preference) {
$this->fixPreference($preference); $this->fixPreference($preference);
} }
@@ -83,6 +83,6 @@ class FixFrontpageAccounts extends Command
} }
} }
} }
app('preferences')->setForUser($preference->user, 'frontPageAccounts', $fixed); app('preferences')->setForUser($preference->user, 'frontpageAccounts', $fixed);
} }
} }

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* MigratePreferences.php
* Copyright (c) 2024 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\Console\Commands\Correction;
use FireflyIII\Models\Preference;
use FireflyIII\User;
use Illuminate\Console\Command;
use Symfony\Component\Console\Command\Command as CommandAlias;
class MigratePreferences extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:migrate-preferences';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Give Firefly III preferences a user group ID so they can be made administration specific.';
/**
* Execute the console command.
*/
public function handle(): int
{
$items = config('firefly.admin_specific_prefs');
$users = User::get();
/** @var User $user */
foreach ($users as $user) {
$count = 0;
foreach ($items as $item) {
$preference = Preference::where('name', $item)->where('user_id', $user->id)->first();
if (null === $preference) {
continue;
}
if (null !== $preference->user_group_id) {
$preference->user_group_id = $user->user_group_id;
$preference->save();
++$count;
}
}
if ($count > 0) {
$this->info(sprintf('Migrated %d preference(s) for user #%d ("%s").', $count, $user->id, $user->email));
}
}
return CommandAlias::SUCCESS;
}
}

View File

@@ -80,7 +80,7 @@ class CreateGroupMemberships extends Command
// check if membership exists // check if membership exists
$userGroup = UserGroup::where('title', $user->email)->first(); $userGroup = UserGroup::where('title', $user->email)->first();
if (null === $userGroup) { if (null === $userGroup) {
$userGroup = UserGroup::create(['title' => $user->email]); $userGroup = UserGroup::create(['title' => $user->email, 'default_administration' => true]);
} }
$userRole = UserRole::where('title', UserRoleEnum::OWNER->value)->first(); $userRole = UserRole::where('title', UserRoleEnum::OWNER->value)->first();

View File

@@ -69,6 +69,7 @@ class UpgradeDatabase extends Command
'firefly-iii:create-group-memberships', 'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information', 'firefly-iii:upgrade-group-information',
'firefly-iii:upgrade-currency-preferences', 'firefly-iii:upgrade-currency-preferences',
'firefly-iii:correct-database',
]; ];
$args = []; $args = [];
if ($this->option('force')) { if ($this->option('force')) {

View File

@@ -145,13 +145,13 @@ class CreateController extends Controller
Log::channel('audit')->info('Stored new account.', $data); Log::channel('audit')->info('Stored new account.', $data);
// update preferences if necessary: // update preferences if necessary:
$frontPage = app('preferences')->get('frontPageAccounts', [])->data; $frontpage = app('preferences')->get('frontpageAccounts', [])->data;
if (!is_array($frontPage)) { if (!is_array($frontpage)) {
$frontPage = []; $frontpage = [];
} }
if (AccountType::ASSET === $account->accountType->type) { if (AccountType::ASSET === $account->accountType->type) {
$frontPage[] = $account->id; $frontpage[] = $account->id;
app('preferences')->set('frontPageAccounts', $frontPage); app('preferences')->set('frontpageAccounts', $frontpage);
} }
// store attachment(s): // store attachment(s):

View File

@@ -301,14 +301,14 @@ class AccountController extends Controller
$end = clone session('end', today(config('app.timezone'))->endOfMonth()); $end = clone session('end', today(config('app.timezone'))->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray(); $defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
app('log')->debug('Default set is ', $defaultSet); app('log')->debug('Default set is ', $defaultSet);
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontPageArray = !is_array($frontPage->data) ? [] : $frontPage->data; $frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
app('log')->debug('Frontpage preference set is ', $frontPageArray); app('log')->debug('Frontpage preference set is ', $frontpageArray);
if (0 === count($frontPageArray)) { if (0 === count($frontpageArray)) {
app('preferences')->set('frontPageAccounts', $defaultSet); app('preferences')->set('frontpageAccounts', $defaultSet);
app('log')->debug('frontpage set is empty!'); app('log')->debug('frontpage set is empty!');
} }
$accounts = $repository->getAccountsById($frontPageArray); $accounts = $repository->getAccountsById($frontpageArray);
return response()->json($this->accountBalanceChart($accounts, $start, $end)); return response()->json($this->accountBalanceChart($accounts, $start, $end));
} }

View File

@@ -116,8 +116,8 @@ class CategoryController extends Controller
return response()->json($cache->get()); return response()->json($cache->get());
} }
$frontPageGenerator = new FrontpageChartGenerator($start, $end); $frontpageGenerator = new FrontpageChartGenerator($start, $end);
$chartData = $frontPageGenerator->generate(); $chartData = $frontpageGenerator->generate();
$data = $this->generator->multiSet($chartData); $data = $this->generator->multiSet($chartData);
$cache->store($data); $cache->store($data);

View File

@@ -129,10 +129,10 @@ class HomeController extends Controller
} }
$subTitle = (string)trans('firefly.welcome_back'); $subTitle = (string)trans('firefly.welcome_back');
$transactions = []; $transactions = [];
$frontPage = app('preferences')->getFresh('frontPageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray()); $frontpage = app('preferences')->getFresh('frontpageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray());
$frontPageArray = $frontPage->data; $frontpageArray = $frontpage->data;
if (!is_array($frontPageArray)) { if (!is_array($frontpageArray)) {
$frontPageArray = []; $frontpageArray = [];
} }
/** @var Carbon $start */ /** @var Carbon $start */
@@ -140,13 +140,13 @@ class HomeController extends Controller
/** @var Carbon $end */ /** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
$accounts = $repository->getAccountsById($frontPageArray); $accounts = $repository->getAccountsById($frontpageArray);
$today = today(config('app.timezone')); $today = today(config('app.timezone'));
// sort frontpage accounts by order // sort frontpage accounts by order
$accounts = $accounts->sortBy('order'); $accounts = $accounts->sortBy('order');
app('log')->debug('Frontpage accounts are ', $frontPageArray); app('log')->debug('Frontpage accounts are ', $frontpageArray);
/** @var BillRepositoryInterface $billRepository */ /** @var BillRepositoryInterface $billRepository */
$billRepository = app(BillRepositoryInterface::class); $billRepository = app(BillRepositoryInterface::class);

View File

@@ -115,7 +115,7 @@ class NewUserController extends Controller
// store frontpage preferences: // store frontpage preferences:
$accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray(); $accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();
app('preferences')->set('frontPageAccounts', $accounts); app('preferences')->set('frontpageAccounts', $accounts);
// mark. // mark.
app('preferences')->mark(); app('preferences')->mark();

View File

@@ -89,10 +89,10 @@ class PreferencesController extends Controller
/** @var array<int, int> $accountIds */ /** @var array<int, int> $accountIds */
$accountIds = $accounts->pluck('id')->toArray(); $accountIds = $accounts->pluck('id')->toArray();
$viewRange = app('navigation')->getViewRange(false); $viewRange = app('navigation')->getViewRange(false);
$frontPageAccountsPref = app('preferences')->get('frontPageAccounts', $accountIds); $frontpageAccountsPref = app('preferences')->get('frontpageAccounts', $accountIds);
$frontPageAccounts = $frontPageAccountsPref->data; $frontpageAccounts = $frontpageAccountsPref->data;
if (!is_array($frontPageAccounts)) { if (!is_array($frontpageAccounts)) {
$frontPageAccounts = $accountIds; $frontpageAccounts = $accountIds;
} }
$language = app('steam')->getLanguage(); $language = app('steam')->getLanguage();
$languages = config('firefly.languages'); $languages = config('firefly.languages');
@@ -128,8 +128,8 @@ class PreferencesController extends Controller
$locales = ['equal' => (string)trans('firefly.equal_to_language')] + $locales; $locales = ['equal' => (string)trans('firefly.equal_to_language')] + $locales;
// an important fallback is that the frontPageAccount array gets refilled automatically // an important fallback is that the frontPageAccount array gets refilled automatically
// when it turns up empty. // when it turns up empty.
if (0 === count($frontPageAccounts)) { if (0 === count($frontpageAccounts)) {
$frontPageAccounts = $accountIds; $frontpageAccounts = $accountIds;
} }
// for the demo user, the slackUrl is automatically emptied. // for the demo user, the slackUrl is automatically emptied.
@@ -139,7 +139,7 @@ class PreferencesController extends Controller
$slackUrl = ''; $slackUrl = '';
} }
return view('preferences.index', compact('language', 'groupedAccounts', 'isDocker', 'frontPageAccounts', 'languages', 'darkMode', 'availableDarkModes', 'notifications', 'slackUrl', 'locales', 'locale', 'tjOptionalFields', 'viewRange', 'customFiscalYear', 'listPageSize', 'fiscalYearStart')); return view('preferences.index', compact('language', 'groupedAccounts', 'isDocker', 'frontpageAccounts', 'languages', 'darkMode', 'availableDarkModes', 'notifications', 'slackUrl', 'locales', 'locale', 'tjOptionalFields', 'viewRange', 'customFiscalYear', 'listPageSize', 'fiscalYearStart'));
} }
/** /**
@@ -155,12 +155,12 @@ class PreferencesController extends Controller
public function postIndex(Request $request) public function postIndex(Request $request)
{ {
// front page accounts // front page accounts
$frontPageAccounts = []; $frontpageAccounts = [];
if (is_array($request->get('frontPageAccounts')) && count($request->get('frontPageAccounts')) > 0) { if (is_array($request->get('frontpageAccounts')) && count($request->get('frontpageAccounts')) > 0) {
foreach ($request->get('frontPageAccounts') as $id) { foreach ($request->get('frontpageAccounts') as $id) {
$frontPageAccounts[] = (int)$id; $frontpageAccounts[] = (int)$id;
} }
app('preferences')->set('frontPageAccounts', $frontPageAccounts); app('preferences')->set('frontpageAccounts', $frontpageAccounts);
} }
// extract notifications: // extract notifications:

View File

@@ -36,7 +36,7 @@ class CreateController extends Controller
public function create() public function create()
{ {
$title = (string)trans('firefly.administrations_page_title'); $title = (string)trans('firefly.administrations_page_title');
$subTitle = (string)trans('firefly.administrations_page_sub_title'); $subTitle = (string)trans('firefly.administrations_page_create_sub_title');
$mainTitleIcon = 'fa-book'; $mainTitleIcon = 'fa-book';
app('log')->debug(sprintf('Now at %s', __METHOD__)); app('log')->debug(sprintf('Now at %s', __METHOD__));

View File

@@ -0,0 +1,46 @@
<?php
/*
* CreateController.php
* Copyright (c) 2024 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\Http\Controllers\UserGroup;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\UserGroup;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Application;
class EditController extends Controller
{
/**
* @return Application|Factory|\Illuminate\Contracts\Foundation\Application|View
*/
public function edit(UserGroup $userGroup)
{
$title = (string)trans('firefly.administrations_page_title');
$subTitle = (string)trans('firefly.administrations_page_edit_sub_title', ['title' => $userGroup->title]);
$mainTitleIcon = 'fa-book';
app('log')->debug(sprintf('Now at %s', __METHOD__));
return view('administrations.edit')->with(compact('title', 'subTitle', 'mainTitleIcon'));
}
}

View File

@@ -110,6 +110,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static EloquentBuilder|Account whereUserGroupId($value) * @method static EloquentBuilder|Account whereUserGroupId($value)
* *
* @property null|UserGroup $userGroup * @property null|UserGroup $userGroup
* @property mixed $account_id
* *
* @mixin Eloquent * @mixin Eloquent
*/ */

View File

@@ -61,6 +61,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|InvitedUser whereUpdatedAt($value) * @method static Builder|InvitedUser whereUpdatedAt($value)
* @method static Builder|InvitedUser whereUserId($value) * @method static Builder|InvitedUser whereUserId($value)
* *
* @property mixed $user_group_id
*
* @mixin Eloquent * @mixin Eloquent
*/ */
class InvitedUser extends Model class InvitedUser extends Model

View File

@@ -54,6 +54,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|Preference whereUpdatedAt($value) * @method static Builder|Preference whereUpdatedAt($value)
* @method static Builder|Preference whereUserId($value) * @method static Builder|Preference whereUserId($value)
* *
* @property mixed $user_group_id
*
* @mixin Eloquent * @mixin Eloquent
*/ */
class Preference extends Model class Preference extends Model
@@ -79,22 +81,39 @@ class Preference extends Model
{ {
if (auth()->check()) { if (auth()->check()) {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
// some preferences do not have an administration ID.
// some need it, to make sure the correct one is selected.
$userGroupId = (int)$user->user_group_id;
$userGroupId = 0 === $userGroupId ? null : $userGroupId;
/** @var null|Preference $preference */ /** @var null|Preference $preference */
$preference = $user->preferences()->where('name', $value)->first(); $preference = null;
$items = config('firefly.admin_specific_prefs');
if (null !== $userGroupId && in_array($value, $items, true)) {
// find a preference with a specific user_group_id
$preference = $user->preferences()->where('user_group_id', $userGroupId)->where('name', $value)->first();
}
if (!in_array($value, $items, true)) {
// find any one.
$preference = $user->preferences()->where('name', $value)->first();
}
// try again with ID, but this time don't care about the preferred user_group_id
if (null === $preference) { if (null === $preference) {
$preference = $user->preferences()->where('id', (int)$value)->first(); $preference = $user->preferences()->where('id', (int)$value)->first();
} }
if (null !== $preference) { if (null !== $preference) {
return $preference; return $preference;
} }
$default = config('firefly.default_preferences'); $default = config('firefly.default_preferences');
if (array_key_exists($value, $default)) { if (array_key_exists($value, $default)) {
$preference = new self(); $preference = new self();
$preference->name = $value; $preference->name = $value;
$preference->data = $default[$value]; $preference->data = $default[$value];
$preference->user_id = (int)$user->id; $preference->user_id = (int)$user->id;
$preference->user_group_id = in_array($value, $items, true) ? $userGroupId : null;
$preference->save(); $preference->save();
return $preference; return $preference;

View File

@@ -81,6 +81,7 @@ use Illuminate\Database\Query\Builder;
* @method static \Illuminate\Database\Eloquent\Builder|RecurrenceTransaction whereTransactionTypeId($value) * @method static \Illuminate\Database\Eloquent\Builder|RecurrenceTransaction whereTransactionTypeId($value)
* *
* @property null|TransactionType $transactionType * @property null|TransactionType $transactionType
* @property mixed $user_id
* *
* @mixin Eloquent * @mixin Eloquent
*/ */

View File

@@ -98,7 +98,7 @@ class UserGroup extends Model
{ {
use ReturnsIntegerIdTrait; use ReturnsIntegerIdTrait;
protected $fillable = ['title']; protected $fillable = ['title', 'default_administration'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -392,4 +392,28 @@ class UserRepository implements UserRepositoryInterface
return null !== $invitee; return null !== $invitee;
} }
#[\Override]
public function getUserGroups(User $user): Collection
{
$memberships = $user->groupMemberships()->get();
$set = [];
$collection = new Collection();
/** @var GroupMembership $membership */
foreach ($memberships as $membership) {
/** @var null|UserGroup $group */
$group = $membership->userGroup()->first();
if (null !== $group) {
$groupId = (int)$group->id;
if (in_array($groupId, $set, true)) {
continue;
}
$set[$groupId] = $group;
}
}
$collection->push(...$set);
return $collection;
}
} }

View File

@@ -59,6 +59,8 @@ interface UserRepositoryInterface
public function changeStatus(User $user, bool $isBlocked, string $code): bool; public function changeStatus(User $user, bool $isBlocked, string $code): bool;
public function getUserGroups(User $user): Collection;
/** /**
* Returns a count of all users. * Returns a count of all users.
*/ */

View File

@@ -288,4 +288,11 @@ class UserGroupRepository implements UserGroupRepositoryInterface
return $roles; return $roles;
} }
#[\Override]
public function useUserGroup(UserGroup $userGroup): void
{
$this->user->user_group_id = $userGroup->id;
$this->user->save();
}
} }

View File

@@ -38,6 +38,8 @@ interface UserGroupRepositoryInterface
public function get(): Collection; public function get(): Collection;
public function useUserGroup(UserGroup $userGroup): void;
public function getAll(): Collection; public function getAll(): Collection;
public function setUser(null|Authenticatable|User $user): void; public function setUser(null|Authenticatable|User $user): void;

View File

@@ -74,5 +74,7 @@ interface AccountRepositoryInterface
public function setUserGroup(UserGroup $userGroup): void; public function setUserGroup(UserGroup $userGroup): void;
public function getUserGroup(): UserGroup;
public function update(Account $account, array $data): Account; public function update(Account $account, array $data): Account;
} }

View File

@@ -0,0 +1,63 @@
<?php
/**
* IsTransferAccount.php
* Copyright (c) 2020 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\Rules;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Contracts\Validation\ValidationRule;
/**
* Class IsDefaultUserGroupName
*/
class IsDefaultUserGroupName implements ValidationRule
{
private UserGroup $userGroup;
public function __construct(UserGroup $userGroup)
{
$this->userGroup = $userGroup;
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
app('log')->debug(sprintf('Now in %s(%s)', __METHOD__, $value));
// are you owner of this group and the name is the same? fail.
/** @var User $user */
$user = auth()->user();
/** @var UserRepositoryInterface $userRepos */
$userRepos = app(UserRepositoryInterface::class);
$roles = $userRepos->getRolesInGroup($user, $this->userGroup->id);
if ($this->userGroup->title === $user->email && in_array('owner', $roles, true)) {
$fail('validation.administration_owner_rename')->translate();
}
}
}

View File

@@ -26,7 +26,10 @@ namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
/** /**
* Class Preferences. * Class Preferences.
@@ -40,20 +43,17 @@ class Preferences
return new Collection(); return new Collection();
} }
return Preference::where('user_id', $user->id)->get(); return Preference::where('user_id', $user->id)
->where(function (Builder $q) use ($user): void {
$q->whereNull('user_group_id');
$q->orWhere('user_group_id', $user->user_group_id);
})
->get()
;
} }
/** public function get(string $name, null|array|bool|int|string $default = null): ?Preference
* @param mixed $default
*
* @throws FireflyException
*/
public function get(string $name, $default = null): ?Preference
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
/** @var null|User $user */ /** @var null|User $user */
$user = auth()->user(); $user = auth()->user();
if (null === $user) { if (null === $user) {
@@ -66,15 +66,12 @@ class Preferences
return $this->getForUser($user, $name, $default); return $this->getForUser($user, $name, $default);
} }
/**
* @throws FireflyException
*/
public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference
{ {
if ('currencyPreference' === $name) { // don't care about user group ID, except for some specific preferences.
throw new FireflyException('No longer supports "currencyPreference", please refactor me.'); $userGroupId = $this->getUserGroupId($user, $name);
} $preference = Preference::where('user_group_id', $userGroupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'user_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) { if (null !== $preference && null === $preference->data) {
$preference->delete(); $preference->delete();
$preference = null; $preference = null;
@@ -92,17 +89,11 @@ class Preferences
return $this->setForUser($user, $name, $default); return $this->setForUser($user, $name, $default);
} }
/**
* @throws FireflyException
*/
public function delete(string $name): bool public function delete(string $name): bool
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
$fullName = sprintf('preference%s%s', auth()->user()->id, $name); $fullName = sprintf('preference%s%s', auth()->user()->id, $name);
if (\Cache::has($fullName)) { if (Cache::has($fullName)) {
\Cache::forget($fullName); Cache::forget($fullName);
} }
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete(); Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
@@ -111,29 +102,19 @@ class Preferences
public function forget(User $user, string $name): void public function forget(User $user, string $name): void
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
$key = sprintf('preference%s%s', $user->id, $name); $key = sprintf('preference%s%s', $user->id, $name);
\Cache::forget($key); Cache::forget($key);
\Cache::put($key, '', 5); Cache::put($key, '', 5);
} }
/** public function setForUser(User $user, string $name, null|array|bool|int|string $value): Preference
* @param mixed $value
*
* @throws FireflyException
*/
public function setForUser(User $user, string $name, $value): Preference
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
$fullName = sprintf('preference%s%s', $user->id, $name); $fullName = sprintf('preference%s%s', $user->id, $name);
\Cache::forget($fullName); $groupId = $this->getUserGroupId($user, $name);
Cache::forget($fullName);
/** @var null|Preference $pref */ /** @var null|Preference $pref */
$pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); $pref = Preference::where('user_group_id', $groupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
if (null !== $pref && null === $value) { if (null !== $pref && null === $value) {
$pref->delete(); $pref->delete();
@@ -144,18 +125,14 @@ class Preferences
return new Preference(); return new Preference();
} }
if (null === $pref) { if (null === $pref) {
$pref = new Preference(); $pref = new Preference();
$pref->user_id = (int)$user->id; $pref->user_id = (int)$user->id;
$pref->name = $name; $pref->user_group_id = $groupId;
$pref->name = $name;
} }
$pref->data = $value; $pref->data = $value;
$pref->save();
try { Cache::forever($fullName, $pref);
$pref->save();
} catch (\PDOException $e) {
throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e);
}
\Cache::forever($fullName, $pref);
return $pref; return $pref;
} }
@@ -165,19 +142,25 @@ class Preferences
return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get(); return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get();
} }
/**
* Find by name, has no user ID in it, because the method is called from an unauthenticated route any way.
*/
public function findByName(string $name): Collection public function findByName(string $name): Collection
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
return Preference::where('name', $name)->get(); return Preference::where('name', $name)->get();
} }
public function getArrayForUser(User $user, array $list): array public function getArrayForUser(User $user, array $list): array
{ {
$result = []; $result = [];
$preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']); $preferences = Preference::where('user_id', $user->id)
->where(function (Builder $q) use ($user): void {
$q->whereNull('user_group_id');
$q->orWhere('user_group_id', $user->user_group_id);
})
->whereIn('name', $list)
->get(['id', 'name', 'data'])
;
/** @var Preference $preference */ /** @var Preference $preference */
foreach ($preferences as $preference) { foreach ($preferences as $preference) {
@@ -192,17 +175,8 @@ class Preferences
return $result; return $result;
} }
/** public function getFresh(string $name, null|array|bool|int|string $default = null): ?Preference
* @param mixed $default
*
* @throws FireflyException
*/
public function getFresh(string $name, $default = null): ?Preference
{ {
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
/** @var null|User $user */ /** @var null|User $user */
$user = auth()->user(); $user = auth()->user();
if (null === $user) { if (null === $user) {
@@ -212,22 +186,6 @@ class Preferences
return $preference; return $preference;
} }
return $this->getFreshForUser($user, $name, $default);
}
/**
* TODO remove me.
*
* @param null $default
*
* @throws FireflyException
*/
public function getFreshForUser(User $user, string $name, $default = null): ?Preference
{
if ('currencyPreference' === $name) {
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
return $this->getForUser($user, $name, $default); return $this->getForUser($user, $name, $default);
} }
@@ -252,19 +210,12 @@ class Preferences
public function mark(): void public function mark(): void
{ {
$this->set('lastActivity', microtime()); $this->set('lastActivity', microtime());
\Session::forget('first'); Session::forget('first');
} }
/** public function set(string $name, null|array|bool|int|string $value): Preference
* @param mixed $value
*
* @throws FireflyException
*/
public function set(string $name, $value): Preference
{ {
if ('currencyPreference' === $name) { /** @var User $user */
throw new FireflyException('No longer supports "currencyPreference", please refactor me.');
}
$user = auth()->user(); $user = auth()->user();
if (null === $user) { if (null === $user) {
// make new preference, return it: // make new preference, return it:
@@ -275,6 +226,17 @@ class Preferences
return $pref; return $pref;
} }
return $this->setForUser(auth()->user(), $name, $value); return $this->setForUser($user, $name, $value);
}
private function getUserGroupId(User $user, string $preferenceName): ?int
{
$groupId = null;
$items = config('firefly.admin_specific_prefs');
if (in_array($preferenceName, $items, true)) {
$groupId = (int)$user->user_group_id;
}
return $groupId;
} }
} }

View File

@@ -35,12 +35,15 @@ class PreferenceTransformer extends AbstractTransformer
*/ */
public function transform(Preference $preference): array public function transform(Preference $preference): array
{ {
$userGroupId = 0 === $preference->user_group_id ? null : $preference->user_group_id;
return [ return [
'id' => $preference->id, 'id' => $preference->id,
'created_at' => $preference->created_at->toAtomString(), 'created_at' => $preference->created_at->toAtomString(),
'updated_at' => $preference->updated_at->toAtomString(), 'updated_at' => $preference->updated_at->toAtomString(),
'name' => $preference->name, 'user_group_id' => $userGroupId,
'data' => $preference->data, 'name' => $preference->name,
'data' => $preference->data,
]; ];
} }
} }

View File

@@ -84,13 +84,13 @@ class UserGroupTransformer extends AbstractTransformer
public function transform(UserGroup $userGroup): array public function transform(UserGroup $userGroup): array
{ {
return [ return [
'id' => $userGroup->id, 'id' => $userGroup->id,
'created_at' => $userGroup->created_at->toAtomString(), 'created_at' => $userGroup->created_at->toAtomString(),
'updated_at' => $userGroup->updated_at->toAtomString(), 'updated_at' => $userGroup->updated_at->toAtomString(),
'in_use' => $this->inUse[$userGroup->id] ?? false, 'in_use' => $this->inUse[$userGroup->id] ?? false,
'title' => $userGroup->title, 'title' => $userGroup->title,
'can_see_members' => $this->membershipsVisible[$userGroup->id] ?? false, 'can_see_members' => $this->membershipsVisible[$userGroup->id] ?? false,
'members' => $this->memberships[$userGroup->id] ?? [], 'members' => $this->memberships[$userGroup->id] ?? [],
]; ];
// if the user has a specific role in this group, then collect the memberships. // if the user has a specific role in this group, then collect the memberships.
} }

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## 6.1.14 - 2024-xx-xx
### Changed
- You will have to define again which asset accounts you want to see on the dashboard. Sorry about that.
## 6.1.13 - 2024-04-01 ## 6.1.13 - 2024-04-01
### Added ### Added

36
composer.lock generated
View File

@@ -8897,23 +8897,23 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "barryvdh/laravel-debugbar", "name": "barryvdh/laravel-debugbar",
"version": "v3.12.3", "version": "v3.13.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git", "url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "aac8f08b73af8c5d2ab6595c8823ddb26d1453f1" "reference": "354a42f3e0b083cdd6f9da5a9d1c0c63b074547a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/aac8f08b73af8c5d2ab6595c8823ddb26d1453f1", "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/354a42f3e0b083cdd6f9da5a9d1c0c63b074547a",
"reference": "aac8f08b73af8c5d2ab6595c8823ddb26d1453f1", "reference": "354a42f3e0b083cdd6f9da5a9d1c0c63b074547a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/routing": "^9|^10|^11", "illuminate/routing": "^9|^10|^11",
"illuminate/session": "^9|^10|^11", "illuminate/session": "^9|^10|^11",
"illuminate/support": "^9|^10|^11", "illuminate/support": "^9|^10|^11",
"maximebf/debugbar": "~1.21.0", "maximebf/debugbar": "~1.22.0",
"php": "^8.0", "php": "^8.0",
"symfony/finder": "^6|^7" "symfony/finder": "^6|^7"
}, },
@@ -8926,7 +8926,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.10-dev" "dev-master": "3.13-dev"
}, },
"laravel": { "laravel": {
"providers": [ "providers": [
@@ -8965,7 +8965,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues", "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.12.3" "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.0"
}, },
"funding": [ "funding": [
{ {
@@ -8977,7 +8977,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-31T18:35:30+00:00" "time": "2024-04-01T16:39:30+00:00"
}, },
{ {
"name": "barryvdh/laravel-ide-helper", "name": "barryvdh/laravel-ide-helper",
@@ -9606,25 +9606,27 @@
}, },
{ {
"name": "maximebf/debugbar", "name": "maximebf/debugbar",
"version": "v1.21.3", "version": "v1.22.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/maximebf/php-debugbar.git", "url": "https://github.com/maximebf/php-debugbar.git",
"reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b" "reference": "d7b6e1dc2dc85c01ed63ab158b00a7f46abdebcc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0b407703b08ea0cf6ebc61e267cc96ff7000911b", "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d7b6e1dc2dc85c01ed63ab158b00a7f46abdebcc",
"reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b", "reference": "d7b6e1dc2dc85c01ed63ab158b00a7f46abdebcc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.1|^8", "php": "^7.2|^8",
"psr/log": "^1|^2|^3", "psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7" "symfony/var-dumper": "^4|^5|^6|^7"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": ">=7.5.20 <10.0", "dbrekelmans/bdi": "^1",
"phpunit/phpunit": "^8|^9",
"symfony/panther": "^1|^2.1",
"twig/twig": "^1.38|^2.7|^3.0" "twig/twig": "^1.38|^2.7|^3.0"
}, },
"suggest": { "suggest": {
@@ -9635,7 +9637,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.21-dev" "dev-master": "1.22-dev"
} }
}, },
"autoload": { "autoload": {
@@ -9666,9 +9668,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/maximebf/php-debugbar/issues", "issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.21.3" "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.1"
}, },
"time": "2024-03-12T14:23:07+00:00" "time": "2024-04-01T10:44:20+00:00"
}, },
{ {
"name": "mockery/mockery", "name": "mockery/mockery",

View File

@@ -117,9 +117,9 @@ return [
'expression_engine' => false, 'expression_engine' => false,
// see cer.php for exchange rates feature flag. // see cer.php for exchange rates feature flag.
], ],
'version' => '6.1.13', 'version' => 'develop/2024-04-02',
'api_version' => '2.0.13', 'api_version' => '2.0.13',
'db_version' => 23, 'db_version' => 24,
// generic settings // generic settings
'maxUploadSize' => 1073741824, // 1 GB 'maxUploadSize' => 1073741824, // 1 GB
@@ -215,11 +215,14 @@ return [
'zoom_level' => env('MAP_DEFAULT_ZOOM', '6'), 'zoom_level' => env('MAP_DEFAULT_ZOOM', '6'),
], ],
// administration specific preferences
'admin_specific_prefs' => ['frontpageAccounts', 'lastActivity'],
// default user-related values // default user-related values
'darkMode' => 'browser', 'darkMode' => 'browser',
'list_length' => 10, // to be removed if v1 is cancelled. 'list_length' => 10, // to be removed if v1 is cancelled.
'default_preferences' => [ 'default_preferences' => [
'frontPageAccounts' => [], 'frontpageAccounts' => [],
'listPageSize' => 50, 'listPageSize' => 50,
'currencyPreference' => 'EUR', 'currencyPreference' => 'EUR',
'language' => 'en_US', 'language' => 'en_US',

View File

@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Schema;
return new class () extends Migration { return new class () extends Migration {
private const string QUERY_ERROR = 'Could not execute query (table "%s", field "%s"): %s'; private const string QUERY_ERROR = 'Could not execute query (table "%s", field "%s"): %s';
private const string EXPL = 'If the index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.'; private const string EXPL = 'If the index already exists (see error), or if MySQL can\'t do it, this is not an problem. Otherwise, please open a GitHub discussion.';
/** /**
* Run the migrations. * Run the migrations.

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
try {
Schema::table(
'preferences',
static function (Blueprint $table): void {
if (!Schema::hasColumn('preferences', 'user_group_id')) {
$table->bigInteger('user_group_id', false, true)->nullable()->after('user_id');
$table->foreign('user_group_id', 'preferences_to_ugi')->references('id')->on('user_groups')->onDelete('set null')->onUpdate('cascade');
}
}
);
} catch (QueryException $e) {
app('log')->error(sprintf('Could not execute query: %s', $e->getMessage()));
app('log')->error('If the column or index already exists (see error), this is not an problem. Otherwise, please open a GitHub discussion.');
}
}
/**
* Reverse the migrations.
*/
public function down(): void {}
};

View File

@@ -3,7 +3,7 @@
"html_language": "nl", "html_language": "nl",
"date_time_fns": "d MMMM yyyy @ HH:mm:ss", "date_time_fns": "d MMMM yyyy @ HH:mm:ss",
"month_and_day_fns": "d MMMM y", "month_and_day_fns": "d MMMM y",
"date_time_fns_short": "MMMM do, yyyy @ HH:mm" "date_time_fns_short": "d MMMM yyyy @ HH:mm"
}, },
"validation": { "validation": {
"bad_type_source": "Firefly III kan het transactietype niet bepalen op basis van deze bronrekening.", "bad_type_source": "Firefly III kan het transactietype niet bepalen op basis van deze bronrekening.",

View File

@@ -3,7 +3,7 @@
"html_language": "nl", "html_language": "nl",
"date_time_fns": "d MMMM yyyy @ HH:mm:ss", "date_time_fns": "d MMMM yyyy @ HH:mm:ss",
"month_and_day_fns": "d MMMM y", "month_and_day_fns": "d MMMM y",
"date_time_fns_short": "MMMM do, yyyy @ HH:mm" "date_time_fns_short": "d MMMM yyyy @ HH:mm"
}, },
"validation": { "validation": {
"bad_type_source": "Firefly III kan het transactietype niet bepalen op basis van deze bronrekening.", "bad_type_source": "Firefly III kan het transactietype niet bepalen op basis van deze bronrekening.",

View File

@@ -23,15 +23,15 @@ import format from "date-fns/format";
export default class Get { export default class Get {
// /** /**
// * *
// * @param identifier * @param identifier
// * @param params * @param params
// * @returns {Promise<AxiosResponse<any>>} * @returns {Promise<AxiosResponse<any>>}
// */ */
// show(identifier, params) { show(identifier, params) {
// return api.get('/api/v2/accounts/' + identifier, {params: params}); return api.get('/api/v2/user-groups/' + identifier, {params: params});
// } }
/** /**
* *

View File

@@ -0,0 +1,33 @@
/*
* post.js
* Copyright (c) 2024 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/.
*/
import {api} from "../../../../boot/axios";
export default class Post {
post(submission) {
let url = './api/v2/user-groups';
return api.post(url, submission);
}
use(groupId) {
let url = './api/v2/user-groups/' + groupId + '/use';
return api.post(url, {});
}
}

View File

@@ -0,0 +1,28 @@
/*
* post.js
* Copyright (c) 2023 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/>.
*/
import {api} from "../../../../boot/axios";
export default class Put {
put(submission, params) {
let url = '/api/v2/user-groups/' + parseInt(params.id);
return api.put(url, submission);
}
}

View File

@@ -0,0 +1,111 @@
/*
* template.js
* Copyright (c) 2024 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/.
*/
import '../../boot/bootstrap.js';
import dates from "../shared/dates.js";
import Post from "../../api/v2/model/user-group/post.js";
import i18next from "i18next";
let administrations = function () {
return {
title: '',
errors: {
title: []
},
// notifications
notifications: {
error: {
show: false, text: '', url: '',
}, success: {
show: false, text: '', url: '',
}, wait: {
show: false, text: '',
}
},
// state of the form is stored in formState:
formStates: {
isSubmitting: false,
returnHereButton: false,
saveAsNewButton: false, // edit form only
resetButton: false,
},
// form behaviour
formBehaviour: {
formType: 'create', // or 'update'
},
changedTitle() {
},
pageProperties: {},
submitForm() {
this.errors.title = [];
(new Post()).post({title: this.title}).then(response => {
if (this.formStates.returnHereButton) {
this.notifications.success.show = true;
this.notifications.success.text = i18next.t('firefly.new_administration_created', {title: response.data.data.attributes.title});
this.notifications.success.url = './administrations';
}
if (this.formStates.resetButton) {
this.title = '';
}
if (!this.formStates.returnHereButton) {
window.location.href = './administrations?user_group_id=' + parseInt(response.data.data.id) + '&message=created';
}
}).catch(error => {
this.errors.title = error.response.data.errors.title;
});
},
cancelForm() {
window.location.href = './administrations';
},
init() {
}
}
}
let comps = {administrations, dates};
function loadPage() {
Object.keys(comps).forEach(comp => {
console.log(`Loading page component "${comp}"`);
let data = comps[comp]();
Alpine.data(comp, () => data);
});
Alpine.start();
}
// wait for load until bootstrapped event is received.
document.addEventListener('firefly-iii-bootstrapped', () => {
console.log('Loaded through event listener.');
loadPage();
});
// or is bootstrapped before event is triggered.
if (window.bootstrapped) {
console.log('Loaded through window variable.');
loadPage();
}

View File

@@ -0,0 +1,121 @@
/*
* template.js
* Copyright (c) 2024 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/.
*/
import '../../boot/bootstrap.js';
import dates from "../shared/dates.js";
import Post from "../../api/v2/model/user-group/post.js";
import i18next from "i18next";
import Get from "../../api/v2/model/user-group/get.js";
import Put from "../../api/v2/model/user-group/put.js";
let administrations = function () {
return {
title: '',
id: 0,
errors: {
title: []
},
// notifications
notifications: {
error: {
show: false, text: '', url: '',
}, success: {
show: false, text: '', url: '',
}, wait: {
show: false, text: '',
}
},
// state of the form is stored in formState:
formStates: {
isSubmitting: false,
returnHereButton: false,
saveAsNewButton: false, // edit form only
resetButton: false,
},
// form behaviour
formBehaviour: {
formType: 'update', // or 'update'
},
changedTitle() {
},
pageProperties: {},
submitForm() {
console.log('submitForm');
(new Put()).put({title: this.title}, {id: this.id}).then(response => {
if (this.formStates.returnHereButton) {
this.notifications.success.show = true;
this.notifications.success.text = i18next.t('firefly.updated_administration', {title: response.data.data.attributes.title});
// TODO needs a better redirect.
this.notifications.success.url = './administrations';
}
if (this.formStates.resetButton) {
this.title = '';
}
if (!this.formStates.returnHereButton) {
// TODO needs a better redirect.
window.location.href = './administrations?user_group_id=' + parseInt(response.data.data.id) + '&message=updated';
}
}).catch(error => {
this.errors.title = error.response.data.errors.title;
});
},
cancelForm() {
window.location.href = './administrations';
},
init() {
const page = window.location.href.split('/');
const groupId = parseInt(page[page.length - 1]);
(new Get()).show(groupId, {}).then(response => {
this.title = response.data.data.attributes.title;
this.id = parseInt(response.data.data.id);
});
}
}
}
let comps = {administrations, dates};
function loadPage() {
Object.keys(comps).forEach(comp => {
console.log(`Loading page component "${comp}"`);
let data = comps[comp]();
Alpine.data(comp, () => data);
});
Alpine.start();
}
// wait for load until bootstrapped event is received.
document.addEventListener('firefly-iii-bootstrapped', () => {
console.log('Loaded through event listener.');
loadPage();
});
// or is bootstrapped before event is triggered.
if (window.bootstrapped) {
console.log('Loaded through window variable.');
loadPage();
}

View File

@@ -27,6 +27,7 @@ import '@ag-grid-community/styles/ag-grid.css';
import '@ag-grid-community/styles/ag-theme-alpine.css'; import '@ag-grid-community/styles/ag-theme-alpine.css';
import '../../css/grid-ff3-theme.css'; import '../../css/grid-ff3-theme.css';
import Get from "../../api/v2/model/user-group/get.js"; import Get from "../../api/v2/model/user-group/get.js";
import Post from "../../api/v2/model/user-group/post.js";
let index = function () { let index = function () {
return { return {
@@ -53,8 +54,16 @@ let index = function () {
this.notifications.wait.text = i18next.t('firefly.wait_loading_data') this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
this.loadAdministrations(); this.loadAdministrations();
}, },
useAdministration(id) {
let groupId = parseInt(id);
// try to post "use", then reload administrations.
(new Post()).use(groupId).then(response => {
this.loadAdministrations();
});
},
loadAdministrations() { loadAdministrations() {
this.userGroups = [];
this.notifications.wait.show = true; this.notifications.wait.show = true;
this.notifications.wait.text = i18next.t('firefly.wait_loading_data') this.notifications.wait.text = i18next.t('firefly.wait_loading_data')
this.accounts = []; this.accounts = [];
@@ -73,16 +82,13 @@ let index = function () {
membersVisible: current.attributes.can_see_members, membersVisible: current.attributes.can_see_members,
members: [], members: [],
}; };
console.log('Processing group #' + group.id + ' (' + group.title + ')' );
let memberships = {}; let memberships = {};
for (let j = 0; j < current.attributes.members.length; j++) { for (let j = 0; j < current.attributes.members.length; j++) {
let member = current.attributes.members[j]; let member = current.attributes.members[j];
console.log('Found member ' + member.user_email, member.you, member.role);
if ('owner' === member.role) { if ('owner' === member.role) {
group.owner = i18next.t('firefly.administration_owner', {email: member.user_email}); group.owner = i18next.t('firefly.administration_owner', {email: member.user_email});
} }
if (true === member.you && 'owner' === member.role) { if (true === member.you && 'owner' === member.role) {
console.log('You are owner of group ' + group.title );
group.isOwner = true; group.isOwner = true;
} }
if (true === member.you) { if (true === member.you) {

View File

@@ -25,7 +25,6 @@ import formatMoney from "../../util/format-money.js";
import Get from "../../api/v2/model/account/get.js"; import Get from "../../api/v2/model/account/get.js";
import {Chart} from 'chart.js'; import {Chart} from 'chart.js';
import {getDefaultChartSettings} from "../../support/default-chart-settings.js"; import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
import {getColors} from "../../support/get-colors.js";
import {getCacheKey} from "../../support/get-cache-key.js"; import {getCacheKey} from "../../support/get-cache-key.js";
import {getConfiguration} from "../../store/get-configuration.js"; import {getConfiguration} from "../../store/get-configuration.js";

View File

@@ -35,6 +35,7 @@ export default () => ({
getFreshData() { getFreshData() {
const start = new Date(window.store.get('start')); const start = new Date(window.store.get('start'));
const end = new Date(window.store.get('end')); const end = new Date(window.store.get('end'));
// needs user data.
const cacheKey = getCacheKey(PIGGY_CACHE_KEY, start, end); const cacheKey = getCacheKey(PIGGY_CACHE_KEY, start, end);
const cacheValid = window.store.get('cacheValid'); const cacheValid = window.store.get('cacheValid');

View File

@@ -24,6 +24,33 @@ import dates from "./shared/dates.js";
let somethings = function() { let somethings = function() {
return { return {
// notifications
// TODO duplicate code
notifications: {
error: {
show: false, text: '', url: '',
}, success: {
show: false, text: '', url: '',
}, wait: {
show: false, text: '',
}
},
// state of the form is stored in formState:
// TODO duplicate code
formStates: {
isSubmitting: false,
returnHereButton: false,
saveAsNewButton: false, // edit form only
resetButton: false,
},
// form behaviour
// TODO duplicate code
formBehaviour: {
formType: 'create', // or 'update'
},
pageProperties: {}, pageProperties: {},
functionName() { functionName() {

View File

@@ -73,7 +73,8 @@ let transactions = function () {
// form behaviour during transaction // form behaviour during transaction
formBehaviour: { formBehaviour: {
formType: 'create', foreignCurrencyEnabled: true, formType: 'create',
foreignCurrencyEnabled: true,
}, },
// form data (except transactions) is stored in formData // form data (except transactions) is stored in formData

View File

@@ -19,9 +19,11 @@
*/ */
import {format} from "date-fns"; import {format} from "date-fns";
import store from "store";
function getCacheKey(string, start, end) { function getCacheKey(string, start, end) {
const cacheKey = format(start, 'y-MM-dd') + '_' + format(end, 'y-MM-dd') + '_' + string; const localValue = store.get('lastActivity');
const cacheKey = format(start, 'y-MM-dd') + '_' + format(end, 'y-MM-dd') + '_' + string + localValue;
console.log('getCacheKey: ' + cacheKey); console.log('getCacheKey: ' + cacheKey);
return String(cacheKey); return String(cacheKey);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'No tens accés a aquesta administració.', 'no_access_user_group' => 'No tens accés a aquesta administració.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Du har ikke de korrekte adgangsrettigheder for denne administration.', 'no_access_user_group' => 'Du har ikke de korrekte adgangsrettigheder for denne administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Für diese Verwaltung haben Sie nicht die erforderlichen Zugriffsrechte.', 'no_access_user_group' => 'Für diese Verwaltung haben Sie nicht die erforderlichen Zugriffsrechte.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Δεν έχετε τα σωστά δικαιώματα πρόσβασης για αυτή τη διαχείριση.', 'no_access_user_group' => 'Δεν έχετε τα σωστά δικαιώματα πρόσβασης για αυτή τη διαχείριση.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -264,6 +264,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
// Ignore this comment // Ignore this comment

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'No tiene permisos para esta administración.', 'no_access_user_group' => 'No tiene permisos para esta administración.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Vous n\'avez pas les droits d\'accès corrects pour cette administration.', 'no_access_user_group' => 'Vous n\'avez pas les droits d\'accès corrects pour cette administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'You do not have the correct access rights for this administration.', 'no_access_user_group' => 'You do not have the correct access rights for this administration.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Non hai i diritti di accesso corretti per questa amministrazione.', 'no_access_user_group' => 'Non hai i diritti di accesso corretti per questa amministrazione.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'この管理のための適切なアクセス権がありません。', 'no_access_user_group' => 'この管理のための適切なアクセス権がありません。',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => '이 관리에 대한 올바른 액세스 권한이 없습니다.', 'no_access_user_group' => '이 관리에 대한 올바른 액세스 권한이 없습니다.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Du har ikke rettigheter til denne handlingen.', 'no_access_user_group' => 'Du har ikke rettigheter til denne handlingen.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

View File

@@ -64,7 +64,7 @@ return [
// 'date_time' => '%B %e, %Y, @ %T', // 'date_time' => '%B %e, %Y, @ %T',
'date_time_js' => 'D MMMM YYYY @ HH:mm:ss', 'date_time_js' => 'D MMMM YYYY @ HH:mm:ss',
'date_time_fns' => 'd MMMM yyyy @ HH:mm:ss', 'date_time_fns' => 'd MMMM yyyy @ HH:mm:ss',
'date_time_fns_short' => 'MMMM do, yyyy @ HH:mm', 'date_time_fns_short' => 'd MMMM yyyy @ HH:mm',
// 'specific_day' => '%e %B %Y', // 'specific_day' => '%e %B %Y',
'specific_day_js' => 'D MMMM YYYY', 'specific_day_js' => 'D MMMM YYYY',

View File

@@ -42,7 +42,7 @@ return [
'fatal_error' => 'Er is een fatale fout opgetreden. Controleer de logbestanden in "storage/logs" of gebruik "docker logs -f [container]" om te zien wat er gebeurde.', 'fatal_error' => 'Er is een fatale fout opgetreden. Controleer de logbestanden in "storage/logs" of gebruik "docker logs -f [container]" om te zien wat er gebeurde.',
'maintenance_mode' => 'Firefly III is in onderhoudsmodus.', 'maintenance_mode' => 'Firefly III is in onderhoudsmodus.',
'be_right_back' => 'Zo terug!', 'be_right_back' => 'Zo terug!',
'check_back' => 'Firefly III is down for some necessary maintenance. Please check back in a second. If you happen to see this message on the demo site, just wait a few minutes. The database is reset every few hours.', 'check_back' => 'Firefly III is offline voor hoognodig onderhoud. Kom later terug. Als je dit bericht op de demo site ziet, wacht dan een paar minuten. De database wordt elke paar uur gereset.',
'error_occurred' => 'Oeps! Er is een fout opgetreden.', 'error_occurred' => 'Oeps! Er is een fout opgetreden.',
'db_error_occurred' => 'Oeps! Er is een database-fout opgetreden.', 'db_error_occurred' => 'Oeps! Er is een database-fout opgetreden.',
'error_not_recoverable' => 'Helaas was deze fout niet te herstellen :(. Firefly III is stuk. De fout is:', 'error_not_recoverable' => 'Helaas was deze fout niet te herstellen :(. Firefly III is stuk. De fout is:',

File diff suppressed because it is too large Load Diff

View File

@@ -70,5 +70,5 @@ return [
'cannot_find_budget' => 'Firefly III kan budget ":name" niet vinden', 'cannot_find_budget' => 'Firefly III kan budget ":name" niet vinden',
'cannot_find_category' => 'Firefly III kan categorie ":name" niet vinden', 'cannot_find_category' => 'Firefly III kan categorie ":name" niet vinden',
'cannot_set_budget' => 'Firefly III kan budget ":name" niet instellen op een transactie van het type ":type"', 'cannot_set_budget' => 'Firefly III kan budget ":name" niet instellen op een transactie van het type ":type"',
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.', 'journal_invalid_amount' => 'Firefly III kan bedrag ":amount" niet instellen omdat dit geen geldig getal is.',
]; ];

View File

@@ -55,11 +55,11 @@ return [
'reconciled_forbidden_field' => 'Deze transactie is al afgestemd, dus je kan ":field" niet wijzigen', 'reconciled_forbidden_field' => 'Deze transactie is al afgestemd, dus je kan ":field" niet wijzigen',
'deleted_user' => 'Je kan je niet registreren met dit e-mailadres.', 'deleted_user' => 'Je kan je niet registreren met dit e-mailadres.',
'rule_trigger_value' => 'Deze waarde is niet geldig voor de geselecteerde trigger.', 'rule_trigger_value' => 'Deze waarde is niet geldig voor de geselecteerde trigger.',
'rule_action_expression' => 'Invalid expression. :error', 'rule_action_expression' => 'Ongeldige expressie (foutmelding in het Engels): :error',
'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.', 'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.',
'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.', 'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.',
'file_attached' => 'Bestand ":name" is succesvol geüpload.', 'file_attached' => 'Bestand ":name" is succesvol geüpload.',
'file_zero' => 'The file is zero bytes in size.', 'file_zero' => 'Het bestand is nul bytes.',
'must_exist' => 'Het ID in veld :attribute bestaat niet.', 'must_exist' => 'Het ID in veld :attribute bestaat niet.',
'all_accounts_equal' => 'Alle rekeningen in dit veld moeten gelijk zijn.', 'all_accounts_equal' => 'Alle rekeningen in dit veld moeten gelijk zijn.',
'group_title_mandatory' => 'Een groepstitel is verplicht wanneer er meer dan één transactie is.', 'group_title_mandatory' => 'Een groepstitel is verplicht wanneer er meer dan één transactie is.',
@@ -197,7 +197,7 @@ return [
* *
*/ */
'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://bit.ly/FF3-password', 'secure_password' => 'Dit is geen veilig wachtwoord. Probeer het nog een keer. Zie ook: https://bit.ly/FF3-password',
'valid_recurrence_rep_type' => 'Dit is geen geldige herhaling voor periodieke transacties.', 'valid_recurrence_rep_type' => 'Dit is geen geldige herhaling voor periodieke transacties.',
'valid_recurrence_rep_moment' => 'Ongeldig herhaalmoment voor dit type herhaling.', 'valid_recurrence_rep_moment' => 'Ongeldig herhaalmoment voor dit type herhaling.',
'invalid_account_info' => 'Ongeldige rekeninginformatie.', 'invalid_account_info' => 'Ongeldige rekeninginformatie.',
@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Je hebt niet de juiste toegangsrechten voor deze administratie.', 'no_access_user_group' => 'Je hebt niet de juiste toegangsrechten voor deze administratie.',
'administration_owner_rename' => 'Je kan je standaardgrootboek niet hernoemen.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Du har ikkje rettigheter til denne handlinga.', 'no_access_user_group' => 'Du har ikkje rettigheter til denne handlinga.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Nie masz odpowiednich praw dostępu dla tej administracji.', 'no_access_user_group' => 'Nie masz odpowiednich praw dostępu dla tej administracji.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Você não direitos de acesso suficientes para esta administração.', 'no_access_user_group' => 'Você não direitos de acesso suficientes para esta administração.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Não tem as permissões de acesso necessárias para esta administração.', 'no_access_user_group' => 'Não tem as permissões de acesso necessárias para esta administração.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

View File

@@ -300,6 +300,7 @@ return [
// no access to administration: // no access to administration:
'no_access_user_group' => 'Nu aveți drepturile de acces corecte pentru această administrare.', 'no_access_user_group' => 'Nu aveți drepturile de acces corecte pentru această administrare.',
'administration_owner_rename' => 'You can\'t rename your standard administration.',
]; ];
/* /*

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More