From b5e4ac00385037dfd0fa22c6678090b38d54f977 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 21 Oct 2023 07:58:24 +0200 Subject: [PATCH 01/17] Add label actions things. --- .github/label-actions.yml | 45 +++++++++++++++++++++++++++++ .github/workflows/label-actions.yml | 21 ++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 .github/label-actions.yml create mode 100644 .github/workflows/label-actions.yml diff --git a/.github/label-actions.yml b/.github/label-actions.yml new file mode 100644 index 0000000000..d804ebeeef --- /dev/null +++ b/.github/label-actions.yml @@ -0,0 +1,45 @@ +# Configuration for Label Actions - https://github.com/dessant/label-actions + +# The `feature` label is added to issues +feature: + issues: + # Post a comment, `{issue-author}` is an optional placeholder + comment: > + Hi there! + + This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III + or the data importer in due course. + + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or + "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to + this issue to get updates. + + Thank you for your contributions. + +enhancement: + issues: + # Post a comment, `{issue-author}` is an optional placeholder + comment: > + Hi there! + + This issue has been marked as an enhancement. The requested (new) feature will become a part of Firefly III + or the data importer in due course. + + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or + "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to + this issue to get updates. + + Thank you for your contributions. + +# The `solved` label is added to discussions +triage: + issues: + # Post a comment, `{issue-author}` is an optional placeholder + comment: > + Hi there! + + This issue has been marked as being in triage. The root cause is not known yet, or the issue needs more + investigation. You can help by sharing debug information (from `/debug`) if you also have this issue or + when you haven't already done so. + + Thank you for your contributions. diff --git a/.github/workflows/label-actions.yml b/.github/workflows/label-actions.yml new file mode 100644 index 0000000000..caf1a31cc1 --- /dev/null +++ b/.github/workflows/label-actions.yml @@ -0,0 +1,21 @@ +name: 'Label Actions' + +on: + issues: + types: [labeled, unlabeled] + pull_request_target: + types: [labeled, unlabeled] + discussion: + types: [labeled, unlabeled] + +permissions: + contents: read + issues: write + pull-requests: write + discussions: write + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/label-actions@v3 From 2a1e53f32ac35a0a432d9843f0b54234fd9d1b8a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 21 Oct 2023 08:00:24 +0200 Subject: [PATCH 02/17] Slight changes in yaml config. --- .github/label-actions.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/label-actions.yml b/.github/label-actions.yml index d804ebeeef..92c0fec26e 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -4,7 +4,7 @@ feature: issues: # Post a comment, `{issue-author}` is an optional placeholder - comment: > + comment: | Hi there! This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III @@ -19,7 +19,7 @@ feature: enhancement: issues: # Post a comment, `{issue-author}` is an optional placeholder - comment: > + comment: | Hi there! This issue has been marked as an enhancement. The requested (new) feature will become a part of Firefly III @@ -35,7 +35,7 @@ enhancement: triage: issues: # Post a comment, `{issue-author}` is an optional placeholder - comment: > + comment: | Hi there! This issue has been marked as being in triage. The root cause is not known yet, or the issue needs more From d97581325d04d3e56e85dc0cc9c649afd0e4460a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 21 Oct 2023 08:02:43 +0200 Subject: [PATCH 03/17] Expand YAML once more. --- .github/label-actions.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 92c0fec26e..29ab39a9c6 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -5,14 +5,11 @@ feature: issues: # Post a comment, `{issue-author}` is an optional placeholder comment: | - Hi there! + Hi there! This is an automatic reply. `Share and enjoy` - This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III - or the data importer in due course. + This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III or the data importer in due course. - If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or - "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to - this issue to get updates. + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to this issue to get updates. Thank you for your contributions. @@ -20,14 +17,11 @@ enhancement: issues: # Post a comment, `{issue-author}` is an optional placeholder comment: | - Hi there! + Hi there! This is an automatic reply. `Share and enjoy` - This issue has been marked as an enhancement. The requested (new) feature will become a part of Firefly III - or the data importer in due course. + This issue has been marked as an enhancement. The requested (new) feature will become a part of Firefly III or the data importer in due course. - If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or - "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to - this issue to get updates. + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to this issue to get updates. Thank you for your contributions. @@ -36,10 +30,8 @@ triage: issues: # Post a comment, `{issue-author}` is an optional placeholder comment: | - Hi there! + Hi there! This is an automatic reply. `Share and enjoy` - This issue has been marked as being in triage. The root cause is not known yet, or the issue needs more - investigation. You can help by sharing debug information (from `/debug`) if you also have this issue or - when you haven't already done so. + This issue has been marked as being in triage. The root cause is not known yet, or the issue needs more investigation. You can help by sharing debug information (from `/debug`) if you also have this issue or when you haven't already done so. Thank you for your contributions. From eb5ee4d147e3c839974438cfc7943cf97b954edb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 21 Oct 2023 10:39:38 +0200 Subject: [PATCH 04/17] Slightly better text. --- .github/label-actions.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/label-actions.yml b/.github/label-actions.yml index 29ab39a9c6..761dbee3ca 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -9,7 +9,7 @@ feature: This issue has been marked as a feature request. The requested (new) feature will become a part of Firefly III or the data importer in due course. - If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to this issue to get updates. + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. Thank you for your contributions. @@ -19,9 +19,9 @@ enhancement: comment: | Hi there! This is an automatic reply. `Share and enjoy` - This issue has been marked as an enhancement. The requested (new) feature will become a part of Firefly III or the data importer in due course. + This issue has been marked as an enhancement. The requested enhancement to an existing feature will become a part of Firefly III or the data importer in due course. - If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful and will be :skull: deleted. You can subscribe to this issue to get updates. + If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. Thank you for your contributions. From 97dfdd5c5d565af65c19ac02c915fa98bbbe40d7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 06:56:46 +0200 Subject: [PATCH 05/17] Link currency to user and user group. --- app/Models/TransactionCurrency.php | 37 ++++++++++++ app/Models/UserGroup.php | 11 ++++ app/User.php | 11 ++++ ...10_21_113213_add_currency_pivot_tables.php | 60 +++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 database/migrations/2023_10_21_113213_add_currency_pivot_tables.php diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 5d34e44419..713bbbb32f 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -24,8 +24,10 @@ declare(strict_types=1); namespace FireflyIII\Models; use Eloquent; +use FireflyIII\User; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Query\Builder; @@ -40,6 +42,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon|null $updated_at * @property Carbon|null $deleted_at * @property bool $enabled + * @property bool $userDefault + * @property bool $userEnabled * @property string $code * @property string $name * @property string $symbol @@ -107,6 +111,39 @@ class TransactionCurrency extends Model throw new NotFoundHttpException(); } + /** + * @param User $user + * + * @return void + */ + public function refreshForUser(User $user) + { + $current = $user->currencies()->where('transaction_currencies.id', $this->id)->first(); + $default = app('amount')->getDefaultCurrencyByUser($user); + $this->userDefault = (int)$default->id === (int)$this->id; + $this->userEnabled = null !== $current; + } + + /** + * Link to users + * + * @return BelongsToMany + */ + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class)->withTimestamps()->withPivot('default'); + } + + /** + * Link to user groups + * + * @return BelongsToMany + */ + public function userGroups(): BelongsToMany + { + return $this->belongsToMany(UserGroup::class)->withTimestamps()->withPivot('default'); + } + /** * @return HasMany */ diff --git a/app/Models/UserGroup.php b/app/Models/UserGroup.php index 5be2e89e81..ac8b3c8320 100644 --- a/app/Models/UserGroup.php +++ b/app/Models/UserGroup.php @@ -30,6 +30,7 @@ use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Support\Carbon; @@ -129,6 +130,16 @@ class UserGroup extends Model return $this->hasMany(Account::class); } + /** + * Link to currencies + * + * @return BelongsToMany + */ + public function currencies(): BelongsToMany + { + return $this->belongsToMany(TransactionCurrency::class); + } + /** * Link to attachments. * diff --git a/app/User.php b/app/User.php index d4cfe96cf3..dc9af881bf 100644 --- a/app/User.php +++ b/app/User.php @@ -46,6 +46,7 @@ use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\UserGroup; @@ -231,6 +232,16 @@ class User extends Authenticatable return $this->hasMany(Account::class); } + /** + * Link to currencies + * + * @return BelongsToMany + */ + public function currencies(): BelongsToMany + { + return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('default'); + } + /** * Link to attachments * diff --git a/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php b/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php new file mode 100644 index 0000000000..c20b4b1173 --- /dev/null +++ b/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php @@ -0,0 +1,60 @@ +id(); + $table->timestamps(); + $table->integer('user_id', false, true); + $table->integer('transaction_currency_id', false, true); + $table->boolean('default')->default(false); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->unique(['user_id', 'transaction_currency_id'],'unique_combo'); + }); + } catch (QueryException $e) { + app('log')->error(sprintf('Could not create table "transaction_currency_user": %s', $e->getMessage())); + app('log')->error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + } + } + + // transaction_currency_user_group + if (!Schema::hasTable('transaction_currency_user_group')) { + try { + Schema::create('transaction_currency_user_group', function (Blueprint $table) { + $table->id(); + $table->timestamps(); + $table->bigInteger('user_group_id', false, true); + $table->integer('transaction_currency_id', false, true); + $table->boolean('default')->default(false); + $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->unique(['user_group_id', 'transaction_currency_id'],'unique_combo'); + }); + } catch (QueryException $e) { + app('log')->error(sprintf('Could not create table "transaction_currency_user_group": %s', $e->getMessage())); + app('log')->error('If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('transaction_currency_user'); + Schema::dropIfExists('transaction_currency_user_group'); + } +}; From 9d5b028a5fc197c858adcd5b96ff54f6ec4c9b5e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 06:57:15 +0200 Subject: [PATCH 06/17] Remove routes that enable/disable/default a currency. --- routes/web.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/routes/web.php b/routes/web.php index c11017e924..0c8e87b468 100644 --- a/routes/web.php +++ b/routes/web.php @@ -333,19 +333,16 @@ Route::group( * Currency Controller. */ Route::group( - ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'currencies', 'as' => 'currencies.'], + ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\TransactionCurrency', 'prefix' => 'currencies', 'as' => 'currencies.'], static function () { - Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']); - Route::get('create', ['uses' => 'CurrencyController@create', 'as' => 'create']); - Route::get('edit/{currency}', ['uses' => 'CurrencyController@edit', 'as' => 'edit']); - Route::get('delete/{currency}', ['uses' => 'CurrencyController@delete', 'as' => 'delete']); - Route::post('default', ['uses' => 'CurrencyController@defaultCurrency', 'as' => 'default']); - Route::post('enable', ['uses' => 'CurrencyController@enableCurrency', 'as' => 'enable']); - Route::post('disable', ['uses' => 'CurrencyController@disableCurrency', 'as' => 'disable']); + Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']); + Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']); + Route::get('edit/{currency}', ['uses' => 'EditController@edit', 'as' => 'edit']); + Route::get('delete/{currency}', ['uses' => 'DeleteController@delete', 'as' => 'delete']); - Route::post('store', ['uses' => 'CurrencyController@store', 'as' => 'store']); - Route::post('update/{currency}', ['uses' => 'CurrencyController@update', 'as' => 'update']); - Route::post('destroy/{currency}', ['uses' => 'CurrencyController@destroy', 'as' => 'destroy']); + Route::post('store', ['uses' => 'CreateController@store', 'as' => 'store']); + Route::post('update/{currency}', ['uses' => 'EditController@update', 'as' => 'update']); + Route::post('destroy/{currency}', ['uses' => 'EditController@destroy', 'as' => 'destroy']); } ); From 2923d1b4493e8e4e9e81a343fc4b36a2b4a44b20 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 06:57:59 +0200 Subject: [PATCH 07/17] Remove unused methods from currency controller --- app/Http/Controllers/CurrencyController.php | 132 -------------------- 1 file changed, 132 deletions(-) diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index 5834d3e547..0d3b332109 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -100,34 +100,7 @@ class CurrencyController extends Controller return view('currencies.create', compact('subTitleIcon', 'subTitle')); } - /** - * Make currency the default currency. - * - * @param Request $request - * - * @return RedirectResponse|Redirector - * @throws FireflyException - */ - public function defaultCurrency(Request $request) - { - $currencyId = (int)$request->get('id'); - if ($currencyId > 0) { - // valid currency? - $currency = $this->repository->find($currencyId); - if (null !== $currency) { - app('preferences')->set('currencyPreference', $currency->code); - app('preferences')->mark(); - Log::channel('audit')->info(sprintf('Make %s the default currency.', $currency->code)); - $this->repository->enable($currency); - $request->session()->flash('success', (string)trans('firefly.new_default_currency', ['name' => $currency->name])); - - return redirect(route('currencies.index')); - } - } - - return redirect(route('currencies.index')); - } /** * Deletes a currency. @@ -206,57 +179,6 @@ class CurrencyController extends Controller return redirect($this->getPreviousUrl('currencies.delete.url')); } - /** - * @param Request $request - * - * @return JsonResponse - * @throws FireflyException - */ - public function disableCurrency(Request $request): JsonResponse - { - $currencyId = (int)$request->get('id'); - $currency = $this->repository->find($currencyId); - - // valid currency? - if (null === $currency) { - return response()->json([]); - } - - app('preferences')->mark(); - - // user must be "owner" - /** @var User $user */ - $user = auth()->user(); - if (!$this->userRepository->hasRole($user, 'owner')) { - $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); - Log::channel('audit')->info(sprintf('Tried to disable currency %s but is not site owner.', $currency->code)); - return response()->json([]); - } - - // currency cannot be in use. - if ($this->repository->currencyInUse($currency)) { - $location = $this->repository->currencyInUseAt($currency); - $message = (string)trans(sprintf('firefly.cannot_disable_currency_%s', $location), ['name' => e($currency->name)]); - - $request->session()->flash('error', $message); - Log::channel('audit')->info(sprintf('Tried to disable currency %s but is in use.', $currency->code)); - return response()->json([]); - } - - // currency disabled! - $this->repository->disable($currency); - Log::channel('audit')->info(sprintf('Disabled currency %s.', $currency->code)); - - $this->repository->ensureMinimalEnabledCurrencies(); - - // extra warning - if ('EUR' === $currency->code) { - session()->flash('warning', (string)trans('firefly.disable_EUR_side_effects')); - } - - session()->flash('success', (string)trans('firefly.currency_is_now_disabled', ['name' => $currency->name])); - return response()->json([]); - } /** * Edit a currency. @@ -299,60 +221,6 @@ class CurrencyController extends Controller return view('currencies.edit', compact('currency', 'subTitle', 'subTitleIcon')); } - /** - * @param Request $request - * - * @return JsonResponse - */ - public function enableCurrency(Request $request): JsonResponse - { - $currencyId = (int)$request->get('id'); - if ($currencyId > 0) { - // valid currency? - $currency = $this->repository->find($currencyId); - if (null !== $currency) { - app('preferences')->mark(); - - $this->repository->enable($currency); - session()->flash('success', (string)trans('firefly.currency_is_now_enabled', ['name' => $currency->name])); - Log::channel('audit')->info(sprintf('Enabled currency %s.', $currency->code)); - } - } - - return response()->json([]); - } - - /** - * Show overview of currencies. - * - * @param Request $request - * - * @return Factory|View - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function index(Request $request) - { - /** @var User $user */ - $user = auth()->user(); - $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $collection = $this->repository->getAll(); - $total = $collection->count(); - $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); - $currencies = new LengthAwarePaginator($collection, $total, $pageSize, $page); - $currencies->setPath(route('currencies.index')); - - $defaultCurrency = $this->repository->getCurrencyByPreference(app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR'))); - $isOwner = true; - if (!$this->userRepository->hasRole($user, 'owner')) { - $request->session()->flash('info', (string)trans('firefly.ask_site_owner', ['owner' => config('firefly.site_owner')])); - $isOwner = false; - } - - return view('currencies.index', compact('currencies', 'defaultCurrency', 'isOwner')); - } - /** * Store new currency. * From 9b22c16f1484f44158cc6f184ee43e4c8895ce49 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 06:58:17 +0200 Subject: [PATCH 08/17] Remove unused logic from home controller --- app/Http/Controllers/HomeController.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 90e27d3c18..69a2d2d2aa 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -118,9 +118,6 @@ class HomeController extends Controller */ public function index(AccountRepositoryInterface $repository): mixed { - if ('v3' === config('firefly.layout')) { - return view('pwa'); - } $types = config('firefly.accountTypesByIdentifier.asset'); $count = $repository->count($types); Log::channel('audit')->info('User visits homepage.'); From 704fc24d209a86944baaecf5733f6b631f81d093 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:12:47 +0200 Subject: [PATCH 09/17] Rename field for less confusion. --- app/Models/TransactionCurrency.php | 4 ++-- app/Models/UserGroup.php | 2 +- app/User.php | 2 +- .../2023_10_21_113213_add_currency_pivot_tables.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 713bbbb32f..cf08a38f71 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -131,7 +131,7 @@ class TransactionCurrency extends Model */ public function users(): BelongsToMany { - return $this->belongsToMany(User::class)->withTimestamps()->withPivot('default'); + return $this->belongsToMany(User::class)->withTimestamps()->withPivot('user_default'); } /** @@ -141,7 +141,7 @@ class TransactionCurrency extends Model */ public function userGroups(): BelongsToMany { - return $this->belongsToMany(UserGroup::class)->withTimestamps()->withPivot('default'); + return $this->belongsToMany(UserGroup::class)->withTimestamps()->withPivot('group_default'); } /** diff --git a/app/Models/UserGroup.php b/app/Models/UserGroup.php index ac8b3c8320..e1958fa7c5 100644 --- a/app/Models/UserGroup.php +++ b/app/Models/UserGroup.php @@ -137,7 +137,7 @@ class UserGroup extends Model */ public function currencies(): BelongsToMany { - return $this->belongsToMany(TransactionCurrency::class); + return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('group_default'); } /** diff --git a/app/User.php b/app/User.php index dc9af881bf..cac5cbf8ca 100644 --- a/app/User.php +++ b/app/User.php @@ -239,7 +239,7 @@ class User extends Authenticatable */ public function currencies(): BelongsToMany { - return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('default'); + return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('user_default'); } /** diff --git a/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php b/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php index c20b4b1173..214729e075 100644 --- a/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php +++ b/database/migrations/2023_10_21_113213_add_currency_pivot_tables.php @@ -18,7 +18,7 @@ return new class extends Migration { $table->timestamps(); $table->integer('user_id', false, true); $table->integer('transaction_currency_id', false, true); - $table->boolean('default')->default(false); + $table->boolean('user_default')->default(false); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); $table->unique(['user_id', 'transaction_currency_id'],'unique_combo'); @@ -37,7 +37,7 @@ return new class extends Migration { $table->timestamps(); $table->bigInteger('user_group_id', false, true); $table->integer('transaction_currency_id', false, true); - $table->boolean('default')->default(false); + $table->boolean('group_default')->default(false); $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); $table->unique(['user_group_id', 'transaction_currency_id'],'unique_combo'); From 0d65e396d465e031f9a37198adeb93d4af1b56e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:16:05 +0200 Subject: [PATCH 10/17] Update get() methods. --- .../Currency/CurrencyRepository.php | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index c977e730b0..591b4ae146 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -166,11 +166,25 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** + * Returns ALL currencies, regardless of whether they are enabled or not. + * * @return Collection */ public function getAll(): Collection { - return TransactionCurrency::orderBy('code', 'ASC')->get(); + $all = TransactionCurrency::orderBy('code', 'ASC')->get(); + $local = $this->get(); + return $all->map(function (TransactionCurrency $current) use ($local) { + $hasId = $local->contains(function (TransactionCurrency $entry) use ($current) { + return (int)$entry->id === (int)$current->id; + }); + $isDefault = $local->contains(function (TransactionCurrency $entry) use ($current) { + return 1 === (int)$entry->pivot->user_default && (int)$entry->id === (int)$current->id; + }); + $current->userEnabled = $hasId; + $current->userDefault = $isDefault; + return $current; + }); } /** @@ -178,7 +192,13 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function get(): Collection { - return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); + $all = $this->user->currencies()->orderBy('code', 'ASC')->withPivot(['user_default'])->get(); + $all->map(function (TransactionCurrency $current) { + $current->userEnabled = true; + $current->userDefault = 1 === (int)$current->pivot->user_default; + return $current; + }); + return $all; } /** From 5425dac18060ebcf42bef7e50ddd79bfbf8f2a11 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:17:53 +0200 Subject: [PATCH 11/17] Update index to handle new fields. --- .../TransactionCurrency/IndexController.php | 87 +++++++++++++++++++ public/v1/js/ff/currencies/index.js | 71 +++++++++------ resources/views/currencies/index.twig | 65 +++++++------- 3 files changed, 163 insertions(+), 60 deletions(-) create mode 100644 app/Http/Controllers/TransactionCurrency/IndexController.php diff --git a/app/Http/Controllers/TransactionCurrency/IndexController.php b/app/Http/Controllers/TransactionCurrency/IndexController.php new file mode 100644 index 0000000000..7467b37d0a --- /dev/null +++ b/app/Http/Controllers/TransactionCurrency/IndexController.php @@ -0,0 +1,87 @@ +middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.currencies')); + app('view')->share('mainTitleIcon', 'fa-usd'); + $this->repository = app(CurrencyRepositoryInterface::class); + $this->userRepository = app(UserRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Show overview of currencies. + * + * @param Request $request + * + * @return Factory|View + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function index(Request $request) + { + /** @var User $user */ + $user = auth()->user(); + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $collection = $this->repository->getAll(); + $total = $collection->count(); + $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); + + // order so default is on top: + $collection = $collection->sortBy( + function (TransactionCurrency $currency) { + $default = true === $currency->userDefault ? 0 : 1; + $enabled = true === $currency->userEnabled ? 0 : 1; + return sprintf('%s-%s-%s',$default, $enabled, $currency->code); + } + ); + + $currencies = new LengthAwarePaginator($collection, $total, $pageSize, $page); + $currencies->setPath(route('currencies.index')); + $isOwner = true; + if (!$this->userRepository->hasRole($user, 'owner')) { + $request->session()->flash('info', (string)trans('firefly.ask_site_owner', ['owner' => config('firefly.site_owner')])); + $isOwner = false; + } + + return view('currencies.index', compact('currencies', 'isOwner')); + } + +} diff --git a/public/v1/js/ff/currencies/index.js b/public/v1/js/ff/currencies/index.js index dbccfae942..e173b5c3e6 100644 --- a/public/v1/js/ff/currencies/index.js +++ b/public/v1/js/ff/currencies/index.js @@ -24,55 +24,70 @@ $(function () { "use strict"; $('.make_default').on('click', setDefaultCurrency); - $('.enable-currency').on('click', enableCurrency); $('.disable-currency').on('click', disableCurrency); }); function setDefaultCurrency(e) { var button = $(e.currentTarget); - var currencyId = parseInt(button.data('id')); + var currencyCode = button.data('code'); - $.post(makeDefaultUrl, { - _token: token, - id: currencyId - }).done(function (data) { - // lame but it works - location.reload(); - }).fail(function () { - console.error('I failed :('); + var params = { + default: true, + enabled: true + } + + $.ajax({ + url: updateCurrencyUrl + '/' + currencyCode, + data: JSON.stringify(params), + type: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), + }, }); + window.location = redirectUrl + '?message=default&code=' + currencyCode; return false; } function enableCurrency(e) { var button = $(e.currentTarget); - var currencyId = parseInt(button.data('id')); + var currencyCode = button.data('code'); - $.post(enableCurrencyUrl, { - _token: token, - id: currencyId - }).done(function (data) { - // lame but it works - location.reload(); - }).fail(function () { - console.error('I failed :('); + var params = { + enabled: true + } + + $.ajax({ + url: updateCurrencyUrl + '/' + currencyCode, + data: JSON.stringify(params), + type: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), + }, }); + window.location = redirectUrl + '?message=enabled&code=' + currencyCode; return false; } function disableCurrency(e) { var button = $(e.currentTarget); - var currencyId = parseInt(button.data('id')); + var currencyCode = button.data('code'); - $.post(disableCurrencyUrl, { - _token: token, - id: currencyId - }).done(function (data) { - // lame but it works - location.reload(); - }).fail(function () { - console.error('I failed :('); + var params = { + enabled: true + } + + $.ajax({ + url: updateCurrencyUrl + '/' + currencyCode, + data: JSON.stringify(params), + type: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), + }, }); + window.location = redirectUrl + '?message=disabled&code=' + currencyCode; return false; } diff --git a/resources/views/currencies/index.twig b/resources/views/currencies/index.twig index d542f5fe6a..1a30c963ad 100644 --- a/resources/views/currencies/index.twig +++ b/resources/views/currencies/index.twig @@ -26,62 +26,64 @@ - {% if isOwner %} - - {% endif %} + - {% for currency in currencies %} - {% if isOwner %} - {% endif %} - {% endfor %} @@ -100,9 +102,8 @@ {% endblock %} {% block scripts %} {% endblock %} From 840fd61b045a8be99473234d529ceed9c08d08b5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:45:48 +0200 Subject: [PATCH 12/17] Update API --- .../TransactionCurrency/ShowController.php | 17 +++++++--- .../TransactionCurrency/UpdateController.php | 32 ++++++++++--------- .../TransactionCurrency/UpdateRequest.php | 1 - 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/ShowController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/ShowController.php index 3c5b7c65c0..6f2d001fea 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/ShowController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/ShowController.php @@ -31,6 +31,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CurrencyTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Pagination\LengthAwarePaginator; use JsonException; @@ -81,13 +82,12 @@ class ShowController extends Controller $pageSize = $this->parameters->get('limit'); $collection = $this->repository->getAll(); $count = $collection->count(); + // slice them: $currencies = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); $paginator = new LengthAwarePaginator($currencies, $count, $pageSize, $this->parameters->get('page')); $paginator->setPath(route('api.v1.currencies.index') . $this->buildParams()); $manager = $this->getManager(); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); @@ -113,10 +113,15 @@ class ShowController extends Controller */ public function show(TransactionCurrency $currency): JsonResponse { + /** @var User $user */ + $user = auth()->user(); $manager = $this->getManager(); $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $this->parameters->set('defaultCurrency', $defaultCurrency); + // update fields with user info. + $currency->refreshForUser($user); + /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); $transformer->setParameters($this->parameters); @@ -138,9 +143,13 @@ class ShowController extends Controller */ public function showDefault(): JsonResponse { + /** @var User $user */ + $user = auth()->user(); $manager = $this->getManager(); - $currency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $currency); + $currency = app('amount')->getDefaultCurrencyByUser($user); + + // update fields with user info. + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php index e0f978c048..4401c8a50a 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php @@ -32,6 +32,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CurrencyTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use JsonException; use League\Fractal\Resource\Item; @@ -82,11 +83,12 @@ class UpdateController extends Controller if ($this->repository->currencyInUse($currency)) { return response()->json([], 409); } + /** @var User $user */ + $user = auth()->user(); $this->repository->disable($currency); $manager = $this->getManager(); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); @@ -110,14 +112,15 @@ class UpdateController extends Controller */ public function makeDefault(TransactionCurrency $currency): JsonResponse { + /** @var User $user */ + $user = auth()->user(); $this->repository->enable($currency); + $this->repository->makeDefault($currency); - app('preferences')->set('currencyPreference', $currency->code); app('preferences')->mark(); $manager = $this->getManager(); - - $this->parameters->set('defaultCurrency', $currency); + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); @@ -144,9 +147,10 @@ class UpdateController extends Controller { $this->repository->enable($currency); $manager = $this->getManager(); + /** @var User $user */ + $user = auth()->user(); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); @@ -172,18 +176,16 @@ class UpdateController extends Controller */ public function update(UpdateRequest $request, TransactionCurrency $currency): JsonResponse { - $data = $request->getAll(); + $data = $request->getAll(); + + /** @var User $user */ + $user = auth()->user(); $currency = $this->repository->update($currency, $data); - if (true === $request->boolean('default')) { - app('preferences')->set('currencyPreference', $currency->code); - app('preferences')->mark(); - } + app('preferences')->mark(); $manager = $this->getManager(); - - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php index 5e673d42b4..7c15fb7357 100644 --- a/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php @@ -57,7 +57,6 @@ class UpdateRequest extends FormRequest ]; return $this->getAllData($fields); - // return $return; } /** From 7feb4b4aaf31a5a6ed029e37b21fe1b30a67d281 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:45:57 +0200 Subject: [PATCH 13/17] Upgrade database version --- config/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/firefly.php b/config/firefly.php index 66fdf0205e..c0f55e1dcf 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -114,7 +114,7 @@ return [ ], 'version' => '6.0.27', 'api_version' => '2.0.10', - 'db_version' => 20, + 'db_version' => 21, // generic settings 'maxUploadSize' => 1073741824, // 1 GB From a810eb2cb54797b7546f038ec8dba4ded3cd6d93 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:50:46 +0200 Subject: [PATCH 14/17] Do error catching etc. --- app/Http/Middleware/InterestingMessage.php | 56 +++++++++++++++++++++- public/v1/js/ff/currencies/index.js | 23 +++++++-- resources/lang/en_US/firefly.php | 5 +- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/app/Http/Middleware/InterestingMessage.php b/app/Http/Middleware/InterestingMessage.php index b609de52ab..a9c8c42044 100644 --- a/app/Http/Middleware/InterestingMessage.php +++ b/app/Http/Middleware/InterestingMessage.php @@ -26,6 +26,7 @@ namespace FireflyIII\Http\Middleware; use Closure; use FireflyIII\Models\Account; use FireflyIII\Models\Bill; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Webhook; @@ -68,6 +69,10 @@ class InterestingMessage Preferences::mark(); $this->handleWebhookMessage($request); } + if ($this->currencyMessage($request)) { + Preferences::mark(); + $this->handleCurrencyMessage($request); + } return $next($request); } @@ -221,10 +226,57 @@ class InterestingMessage private function webhookMessage(Request $request): bool { // get parameters from request. - $billId = $request->get('webhook_id'); + $webhookId = $request->get('webhook_id'); + $message = $request->get('message'); + + return null !== $webhookId && null !== $message; + } + + /** + * @param Request $request + * + * @return bool + */ + private function currencyMessage(Request $request): bool + { + // get parameters from request. + $code = $request->get('code'); $message = $request->get('message'); - return null !== $billId && null !== $message; + return null !== $code && null !== $message; + } + + private function handleCurrencyMessage(Request $request): void + { + // params: + // get parameters from request. + $code = $request->get('code'); + $message = $request->get('message'); + + /** @var TransactionCurrency $webhook */ + $currency = TransactionCurrency::whereCode($code)->first(); + + if (null === $currency) { + return; + } + if ('enabled' === $message) { + session()->flash('success', (string)trans('firefly.currency_is_now_enabled', ['name' => $currency->name])); + } + if ('enable_failed' === $message) { + session()->flash('error', (string)trans('firefly.could_not_enable_currency', ['name' => $currency->name])); + } + if ('disabled' === $message) { + session()->flash('success', (string)trans('firefly.currency_is_now_disabled', ['name' => $currency->name])); + } + if ('disable_failed' === $message) { + session()->flash('error', (string)trans('firefly.could_not_disable_currency', ['name' => $currency->name])); + } + if ('default' === $message) { + session()->flash('success', (string)trans('firefly.new_default_currency', ['name' => $currency->name])); + } + if ('default_failed' === $message) { + session()->flash('error', (string)trans('firefly.default_currency_failed', ['name' => $currency->name])); + } } /** diff --git a/public/v1/js/ff/currencies/index.js b/public/v1/js/ff/currencies/index.js index e173b5c3e6..99ecaf7e24 100644 --- a/public/v1/js/ff/currencies/index.js +++ b/public/v1/js/ff/currencies/index.js @@ -45,8 +45,13 @@ function setDefaultCurrency(e) { 'Content-Type': 'application/json', 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), }, + error: function () { + window.location = redirectUrl + '?message=default_failed&code=' + currencyCode; + }, + success: function () { + window.location = redirectUrl + '?message=default&code=' + currencyCode; + } }); - window.location = redirectUrl + '?message=default&code=' + currencyCode; return false; } @@ -66,8 +71,13 @@ function enableCurrency(e) { 'Content-Type': 'application/json', 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), }, + error: function () { + window.location = redirectUrl + '?message=enable_failed&code=' + currencyCode; + }, + success: function () { + window.location = redirectUrl + '?message=enabled&code=' + currencyCode; + } }); - window.location = redirectUrl + '?message=enabled&code=' + currencyCode; return false; } @@ -76,7 +86,7 @@ function disableCurrency(e) { var currencyCode = button.data('code'); var params = { - enabled: true + enabled: false } $.ajax({ @@ -87,7 +97,12 @@ function disableCurrency(e) { 'Content-Type': 'application/json', 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), }, + error: function () { + window.location = redirectUrl + '?message=disable_failed&code=' + currencyCode; + }, + success: function () { + window.location = redirectUrl + '?message=disabled&code=' + currencyCode; + } }); - window.location = redirectUrl + '?message=disabled&code=' + currencyCode; return false; } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 832a7f369a..8bcd11ccaf 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1582,7 +1582,8 @@ return [ 'create_currency' => 'Create a new currency', 'store_currency' => 'Store new currency', 'update_currency' => 'Update currency', - 'new_default_currency' => ':name is now the default currency.', + 'new_default_currency' => '":name" is now the default currency.', + 'default_currency_failed' => 'Could not make ":name" the default currency. Please check the logs.', 'cannot_delete_currency' => 'Cannot delete :name because it is still in use.', 'cannot_delete_fallback_currency' => ':name is the system fallback currency and can\'t be deleted.', 'cannot_disable_currency_journals' => 'Cannot disable :name because transactions are still using it.', @@ -1608,7 +1609,9 @@ return [ 'disable_currency' => 'Disable', 'currencies_default_disabled' => 'Most of these currencies are disabled by default. To use them, you must enable them first.', 'currency_is_now_enabled' => 'Currency ":name" has been enabled', + 'could_not_enable_currency' => 'Could not enable currency ":name". Please review the logs.', 'currency_is_now_disabled' => 'Currency ":name" has been disabled', + 'could_not_disable_currency' => 'Could not disable currency ":name". Perhaps it is still in use?', // forms: 'mandatoryFields' => 'Mandatory fields', From 4cec0a9f979c7e72163db0668b2aa76eeed9e043 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:51:26 +0200 Subject: [PATCH 15/17] Refactor repository and some commands for upgrading --- .../Commands/Upgrade/AccountCurrencies.php | 15 +- .../Upgrade/UpgradeCurrencyPreferences.php | 153 ++++++++++++++++++ .../Commands/Upgrade/UpgradeSkeleton.php.stub | 1 + .../Currency/CurrencyRepository.php | 81 ++++++++-- .../Currency/CurrencyRepositoryInterface.php | 14 ++ .../Internal/Update/CurrencyUpdateService.php | 7 +- 6 files changed, 243 insertions(+), 28 deletions(-) create mode 100644 app/Console/Commands/Upgrade/UpgradeCurrencyPreferences.php diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php index 73b6ffdf62..c470b70a1d 100644 --- a/app/Console/Commands/Upgrade/AccountCurrencies.php +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -129,20 +129,7 @@ class AccountCurrencies extends Command $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); // get user's currency preference: - $defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data; - if (!is_string($defaultCurrencyCode)) { - $defaultCurrencyCode = $systemCurrencyCode; - } - - /** @var TransactionCurrency|null $defaultCurrency */ - $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); - - if (null === $defaultCurrency) { - Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode)); - $this->friendlyError(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode)); - - return; - } + $defaultCurrency = app('amount')->getDefaultCurrencyByUser($user); /** @var Account $account */ foreach ($accounts as $account) { diff --git a/app/Console/Commands/Upgrade/UpgradeCurrencyPreferences.php b/app/Console/Commands/Upgrade/UpgradeCurrencyPreferences.php new file mode 100644 index 0000000000..29c21063f6 --- /dev/null +++ b/app/Console/Commands/Upgrade/UpgradeCurrencyPreferences.php @@ -0,0 +1,153 @@ +isExecuted() && true !== $this->option('force')) { + $this->friendlyInfo('This command has already been executed.'); + + return 0; + } + $this->runUpgrade(); + + $this->friendlyPositive('Currency preferences migrated.'); + + //$this->markAsExecuted(); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; + } + + /** + * @param User $user + * + * @return string + */ + private function getPreference(User $user): string + { + $preference = Preference::where('user_id', $user->id)->where('name', 'currencyPreference')->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']); + + if (null !== $preference) { + return (string)$preference->data; + } + return 'EUR'; + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + private function runUpgrade(): void + { + $groups = UserGroup::get(); + /** @var UserGroup $group */ + foreach ($groups as $group) { + $this->upgradeGroupPreferences($group); + } + + $users = User::get(); + /** @var User $user */ + foreach ($users as $user) { + $this->upgradeUserPreferences($user); + } + } + + /** + * @param User $user + * + * @return void + */ + private function upgradeUserPreferences(User $user): void + { + $currencies = TransactionCurrency::get(); + $enabled = new Collection(); + /** @var TransactionCurrency $currency */ + foreach ($currencies as $currency) { + if ($currency->enabled) { + $enabled->push($currency); + } + } + $user->currencies()->sync($enabled->pluck('id')->toArray()); + + // set the default currency for the user and for the group: + $preference = $this->getPreference($user); + $defaultCurrency = TransactionCurrency::where('code', $preference)->first(); + if (null === $currency) { + // get EUR + $defaultCurrency = TransactionCurrency::where('code', 'EUR')->first(); + } + $user->currencies()->updateExistingPivot($defaultCurrency->id, ['user_default' => true]); + $user->userGroup->currencies()->updateExistingPivot($defaultCurrency->id, ['group_default' => true]); + } + + /** + * @param UserGroup $group + * + * @return void + */ + private function upgradeGroupPreferences(UserGroup $group) + { + $currencies = TransactionCurrency::get(); + $enabled = new Collection(); + /** @var TransactionCurrency $currency */ + foreach ($currencies as $currency) { + if ($currency->enabled) { + $enabled->push($currency); + } + } + $group->currencies()->sync($enabled->pluck('id')->toArray()); + } +} diff --git a/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub index 4ba6803d78..a5fd3aab50 100644 --- a/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub +++ b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub @@ -10,6 +10,7 @@ use Illuminate\Console\Command; */ class UpgradeSkeleton extends Command { + use ShowsFriendlyMessages; public const CONFIG_NAME = '480_some_name'; /** * The console command description. diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 591b4ae146..39684f8a41 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -140,8 +140,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface } // is the default currency for the user or the system - $defaultCode = app('preferences')->getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; - if ($currency->code === $defaultCode) { + $count = $this->user->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('user_default', 1)->count(); + if ($count > 0) { Log::info('Is the default currency of the user, return true.'); return 'current_default'; @@ -226,6 +226,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function disable(TransactionCurrency $currency): void { + $this->user->currencies()->detach($currency->id); $currency->enabled = false; $currency->save(); } @@ -236,15 +237,13 @@ class CurrencyRepository implements CurrencyRepositoryInterface public function ensureMinimalEnabledCurrencies(): void { // if no currencies are enabled, enable the first one in the DB (usually the EUR) - if (0 === $this->get()->count()) { - /** @var TransactionCurrency $first */ - $first = $this->getAll()->first(); - if (null === $first) { + if (0 === $this->user->currencies()->count()) { + $euro = app('amount')->getSystemCurrency(); + if (null === $euro) { throw new FireflyException('No currencies found. You broke Firefly III'); } - Log::channel('audit')->info(sprintf('Auto-enabled currency %s.', $first->code)); - $this->enable($first); - app('preferences')->set('currencyPreference', $first->code); + Log::channel('audit')->info(sprintf('Auto-enabled currency %s.', $euro->code)); + $this->enable($euro); app('preferences')->mark(); } } @@ -255,7 +254,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function enable(TransactionCurrency $currency): void { - $currency->enabled = true; + $this->user->currencies()->syncWithoutDetaching([$currency->id]); + $currency->enabled = false; $currency->save(); } @@ -462,6 +462,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface return null; } + /** + * @inheritDoc + */ + public function getUserCurrencies(User $user): Collection + { + return $user->currencies()->get(); + } + /** * @inheritDoc */ @@ -527,6 +535,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function store(array $data): TransactionCurrency { + throw new FireflyException(sprintf('Method "%s" needs a refactor.', __METHOD__)); /** @var TransactionCurrencyFactory $factory */ $factory = app(TransactionCurrencyFactory::class); $result = $factory->create($data); @@ -546,9 +555,61 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function update(TransactionCurrency $currency, array $data): TransactionCurrency { + app('log')->debug('Now in update()'); + // can be true, false, null + $enabled = array_key_exists('enabled', $data) ? $data['enabled'] : null; + // can be true, false, but method only responds to "true". + $default = array_key_exists('default', $data) ? $data['default'] : false; + + // remove illegal combo's: + if (false === $enabled && true === $default) { + $enabled = true; + } + if (false === $default) { + app('log')->warning(sprintf('Set default=false will NOT do anything for currency %s', $currency->code)); + } + + // update currency with current user specific settings + $currency->refreshForUser($this->user); + + // currency is enabled, must be disabled. + if (false === $enabled) { + app('log')->debug(sprintf('Disabled currency %s for user #%d', $currency->code, $this->user->id)); + $this->user->currencies()->detach($currency->id); + } + // currency must be enabled + if (true === $enabled) { + app('log')->debug(sprintf('Enabled currency %s for user #%d', $currency->code, $this->user->id)); + $this->user->currencies()->detach($currency->id); + $this->user->currencies()->syncWithoutDetaching([$currency->id => ['user_default' => false]]); + } + + // currency must be made default. + if (true === $default) { + app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->user->id)); + $this->user->currencies()->detach($currency->id); + foreach ($this->user->currencies()->get() as $item) { + $this->user->currencies()->updateExistingPivot($item->id, ['user_default' => false]); + } + $this->user->currencies()->syncWithoutDetaching([$currency->id => ['user_default' => true]]); + } + /** @var CurrencyUpdateService $service */ $service = app(CurrencyUpdateService::class); return $service->update($currency, $data); } + + /** + * @inheritDoc + */ + public function makeDefault(TransactionCurrency $currency): void + { + app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->user->id)); + $this->user->currencies()->detach($currency->id); + foreach ($this->user->currencies()->get() as $item) { + $this->user->currencies()->updateExistingPivot($item->id, ['user_default' => false]); + } + $this->user->currencies()->syncWithoutDetaching([$currency->id => ['user_default' => true]]); + } } diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 3ef07f66f4..ff8c1c8d1d 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -204,6 +204,20 @@ interface CurrencyRepositoryInterface */ public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate; + /** + * @param TransactionCurrency $currency + * + * @return void + */ + public function makeDefault(TransactionCurrency $currency): void; + + /** + * @param User $user + * + * @return Collection + */ + public function getUserCurrencies(User $user): Collection; + /** * @param TransactionCurrency $currency * diff --git a/app/Services/Internal/Update/CurrencyUpdateService.php b/app/Services/Internal/Update/CurrencyUpdateService.php index c01444e4e8..01fe1692f6 100644 --- a/app/Services/Internal/Update/CurrencyUpdateService.php +++ b/app/Services/Internal/Update/CurrencyUpdateService.php @@ -52,14 +52,13 @@ class CurrencyUpdateService $currency->name = e($data['name']); } - if (array_key_exists('enabled', $data) && is_bool($data['enabled'])) { - $currency->enabled = (bool)$data['enabled']; - } + $currency->enabled = false; if (array_key_exists('decimal_places', $data) && is_int($data['decimal_places'])) { $currency->decimal_places = (int)$data['decimal_places']; } - + unset($currency->userEnabled); + unset($currency->userDefault); $currency->save(); return $currency; From 80237d8bc3571d6a26814c3846ccfb8a6344798c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 07:55:36 +0200 Subject: [PATCH 16/17] Refactor methods that request the old currency preference. --- .../TransactionCurrency/StoreController.php | 9 ++-- app/Http/Controllers/JavascriptController.php | 8 ++-- app/Http/Controllers/NewUserController.php | 2 +- .../TransactionGroupRepository.php | 4 -- app/Support/Amount.php | 47 +++---------------- app/Support/Preferences.php | 27 +++++++++++ app/Transformers/CurrencyTransformer.php | 11 +---- 7 files changed, 46 insertions(+), 62 deletions(-) diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php index 61a4c63d22..12a8db0d50 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php @@ -31,6 +31,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CurrencyTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use JsonException; use League\Fractal\Resource\Item; @@ -79,12 +80,14 @@ class StoreController extends Controller { $currency = $this->repository->store($request->getAll()); if (true === $request->boolean('default')) { - app('preferences')->set('currencyPreference', $currency->code); + $this->repository->makeDefault($currency); app('preferences')->mark(); } $manager = $this->getManager(); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - $this->parameters->set('defaultCurrency', $defaultCurrency); + + /** @var User $user */ + $user = auth()->user(); + $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index bdb002df78..66ec5e7242 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -56,13 +56,11 @@ class JavascriptController extends Controller */ public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository): Response { - $accounts = $repository->getAccountsByType( + $accounts = $repository->getAccountsByType( [AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD] ); - $preference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR')); - $default = $currencyRepository->findByCodeNull((string)$preference->data); - - $data = ['accounts' => []]; + $default = app('amount')->getDefaultCurrency(); + $data = ['accounts' => []]; /** @var Account $account */ foreach ($accounts as $account) { diff --git a/app/Http/Controllers/NewUserController.php b/app/Http/Controllers/NewUserController.php index 5ba5d4fd12..5f85744279 100644 --- a/app/Http/Controllers/NewUserController.php +++ b/app/Http/Controllers/NewUserController.php @@ -114,7 +114,7 @@ class NewUserController extends Controller $this->createCashWalletAccount($currency, $language); // create cash wallet account // store currency preference: - app('preferences')->set('currencyPreference', $currency->code); + $currencyRepository->makeDefault($currency); // store frontpage preferences: $accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray(); diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index 860d03187d..aeb9411303 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -418,10 +418,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface if (null !== $currencyPreference) { $currency = TransactionCurrency::where('id', $currencyPreference->data)->first(); } - if (null === $currencyPreference) { - $currencyCode = app('preferences')->getForUser($this->user, 'currencyPreference', 'EUR')->data; - $currency = TransactionCurrency::where('code', $currencyCode)->first(); - } $journalId = (int)$row->transaction_journal_id; $return[$journalId] = $return[$journalId] ?? []; diff --git a/app/Support/Amount.php b/app/Support/Amount.php index fdad00c33d..54541bad07 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -108,35 +108,10 @@ class Amount */ public function getCurrencies(): Collection { + throw new FireflyException(sprintf('Method "%s" needs a refactor', __METHOD__)); return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); } - /** - * @return string - * @throws FireflyException - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - public function getCurrencyCode(): string - { - $cache = new CacheProperties(); - $cache->addProperty('getCurrencyCode'); - if ($cache->has()) { - return $cache->get(); - } - $currencyPreference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR')); - - $currency = TransactionCurrency::where('code', $currencyPreference->data)->first(); - if ($currency) { - $cache->store($currency->code); - - return $currency->code; - } - $cache->store(config('firefly.default_currency', 'EUR')); - - return (string)config('firefly.default_currency', 'EUR'); - } - /** * @return TransactionCurrency * @throws FireflyException @@ -163,22 +138,14 @@ class Amount if ($cache->has()) { return $cache->get(); } - $currencyPreference = app('preferences')->getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR')); - $currencyPrefStr = $currencyPreference ? $currencyPreference->data : 'EUR'; - - // at this point the currency preference could be encrypted, if coming from an old version. - $currencyCode = $this->tryDecrypt((string)$currencyPrefStr); - - // could still be json encoded: - /** @var TransactionCurrency|null $currency */ - $currency = TransactionCurrency::where('code', $currencyCode)->first(); - if (null === $currency) { - // get EUR - $currency = TransactionCurrency::where('code', 'EUR')->first(); + $default = $user->currencies()->where('user_default', true)->first(); + if(null === $default) { + $default = $this->getSystemCurrency(); + $user->currencies()->sync([$default->id => ['user_default' => true]]); } - $cache->store($currency); + $cache->store($default); - return $currency; + return $default; } /** diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index c519a992ee..46a1a91d28 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -60,6 +60,9 @@ class Preferences */ public function get(string $name, $default = null): ?Preference { + if('currencyPreference' === $name) { + throw new FireflyException('No longer supports "currencyPreference", please refactor me.'); + } /** @var User|null $user */ $user = auth()->user(); if (null === $user) { @@ -82,6 +85,9 @@ class Preferences */ public function getForUser(User $user, string $name, $default = null): ?Preference { + if('currencyPreference' === $name) { + throw new FireflyException('No longer supports "currencyPreference", please refactor me.'); + } $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) { $preference->delete(); @@ -108,6 +114,9 @@ class Preferences */ 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); if (Cache::has($fullName)) { Cache::forget($fullName); @@ -123,6 +132,9 @@ class Preferences */ 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); Cache::forget($key); Cache::put($key, '', 5); @@ -138,6 +150,9 @@ class Preferences */ 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); Cache::forget($fullName); /** @var Preference|null $pref */ @@ -185,6 +200,9 @@ class Preferences */ 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(); } @@ -220,6 +238,9 @@ class Preferences */ public function getFresh(string $name, $default = null): ?Preference { + if('currencyPreference' === $name) { + throw new FireflyException('No longer supports "currencyPreference", please refactor me.'); + } /** @var User|null $user */ $user = auth()->user(); if (null === $user) { @@ -243,6 +264,9 @@ class Preferences */ 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); } @@ -283,6 +307,9 @@ class Preferences */ public function set(string $name, $value): Preference { + if('currencyPreference' === $name) { + throw new FireflyException('No longer supports "currencyPreference", please refactor me.'); + } $user = auth()->user(); if (null === $user) { // make new preference, return it: diff --git a/app/Transformers/CurrencyTransformer.php b/app/Transformers/CurrencyTransformer.php index 2c363f19eb..0449db14b3 100644 --- a/app/Transformers/CurrencyTransformer.php +++ b/app/Transformers/CurrencyTransformer.php @@ -34,23 +34,16 @@ class CurrencyTransformer extends AbstractTransformer * Transform the currency. * * @param TransactionCurrency $currency - * * @return array */ public function transform(TransactionCurrency $currency): array { - $isDefault = false; - $defaultCurrency = $this->parameters->get('defaultCurrency'); - if (null !== $defaultCurrency) { - $isDefault = (int)$defaultCurrency->id === (int)$currency->id; - } - return [ 'id' => (int)$currency->id, 'created_at' => $currency->created_at->toAtomString(), 'updated_at' => $currency->updated_at->toAtomString(), - 'default' => $isDefault, - 'enabled' => $currency->enabled, + 'default' => $currency->userDefault, + 'enabled' => $currency->userEnabled, 'name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol, From c3398d4d51325997dfba5db1940a3b60027c651f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 22 Oct 2023 08:05:28 +0200 Subject: [PATCH 17/17] Fix refactor for field. --- app/Support/Amount.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 54541bad07..c018e502c7 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -108,8 +108,9 @@ class Amount */ public function getCurrencies(): Collection { - throw new FireflyException(sprintf('Method "%s" needs a refactor', __METHOD__)); - return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); + /** @var User $user */ + $user = auth()->user(); + return $user->currencies()->orderBy('code','ASC')->get(); } /**
   {{ 'currency'|_ }} {{ 'number_of_decimals'|_ }} 
+ {% if isOwner %} + {% endif %} + {# Disable the currency. #} + {% if currency.userEnabled %} + + + {{ 'disable_currency'|_ }} + {% endif %} + + {# Enable the currency. #} + {% if not currency.userEnabled %} + + + {{ 'enable_currency'|_ }} + {% endif %} + + {# Make currency default. #} + {% if currency.id != defaultCurrency.id %} + + {% endif %}
- {% if currency.enabled == false %} + {% if currency.userEnabled == false %} {% endif %} {{ currency.name }} ({{ currency.code }}) ({{ currency.symbol|raw }}) {% if currency.id == defaultCurrency.id %} {{ 'default_currency'|_ }} {% endif %} - {% if currency.enabled == false %} + {% if currency.userEnabled == false %} + + {{ 'currency_is_disabled'|_ }} + {% endif %} + + {% if currency.userEnabled == false %} -
{{ 'currency_is_disabled'|_ }} {% endif %}
{{ currency.decimal_places }} -
- {% if currency.id != defaultCurrency.id %} - - {% endif %} - {% if currency.enabled %} - - - {{ 'disable_currency'|_ }} - {% endif %} - {% if not currency.enabled %} - - - {{ 'enable_currency'|_ }} - {% endif %} -
-