Merge branch 'main' into develop

This commit is contained in:
James Cole
2023-10-22 08:05:48 +02:00
31 changed files with 765 additions and 323 deletions

37
.github/label-actions.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# 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 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.
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.
enhancement:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
Hi there! This is an automatic reply. `Share and enjoy`
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 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.
# The `solved` label is added to discussions
triage:
issues:
# Post a comment, `{issue-author}` is an optional placeholder
comment: |
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.
Thank you for your contributions.

21
.github/workflows/label-actions.yml vendored Normal file
View File

@@ -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

View File

@@ -31,6 +31,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\CurrencyTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use JsonException; use JsonException;
@@ -81,13 +82,12 @@ class ShowController extends Controller
$pageSize = $this->parameters->get('limit'); $pageSize = $this->parameters->get('limit');
$collection = $this->repository->getAll(); $collection = $this->repository->getAll();
$count = $collection->count(); $count = $collection->count();
// slice them: // slice them:
$currencies = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); $currencies = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($currencies, $count, $pageSize, $this->parameters->get('page')); $paginator = new LengthAwarePaginator($currencies, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.currencies.index') . $this->buildParams()); $paginator->setPath(route('api.v1.currencies.index') . $this->buildParams());
$manager = $this->getManager(); $manager = $this->getManager();
$defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user());
$this->parameters->set('defaultCurrency', $defaultCurrency);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);
@@ -113,10 +113,15 @@ class ShowController extends Controller
*/ */
public function show(TransactionCurrency $currency): JsonResponse public function show(TransactionCurrency $currency): JsonResponse
{ {
/** @var User $user */
$user = auth()->user();
$manager = $this->getManager(); $manager = $this->getManager();
$defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user());
$this->parameters->set('defaultCurrency', $defaultCurrency); $this->parameters->set('defaultCurrency', $defaultCurrency);
// update fields with user info.
$currency->refreshForUser($user);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);
@@ -138,9 +143,13 @@ class ShowController extends Controller
*/ */
public function showDefault(): JsonResponse public function showDefault(): JsonResponse
{ {
/** @var User $user */
$user = auth()->user();
$manager = $this->getManager(); $manager = $this->getManager();
$currency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $currency = app('amount')->getDefaultCurrencyByUser($user);
$this->parameters->set('defaultCurrency', $currency);
// update fields with user info.
$currency->refreshForUser($user);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);

View File

@@ -31,6 +31,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\CurrencyTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use JsonException; use JsonException;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@@ -79,12 +80,14 @@ class StoreController extends Controller
{ {
$currency = $this->repository->store($request->getAll()); $currency = $this->repository->store($request->getAll());
if (true === $request->boolean('default')) { if (true === $request->boolean('default')) {
app('preferences')->set('currencyPreference', $currency->code); $this->repository->makeDefault($currency);
app('preferences')->mark(); app('preferences')->mark();
} }
$manager = $this->getManager(); $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 */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);

View File

@@ -32,6 +32,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\CurrencyTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use JsonException; use JsonException;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@@ -82,11 +83,12 @@ class UpdateController extends Controller
if ($this->repository->currencyInUse($currency)) { if ($this->repository->currencyInUse($currency)) {
return response()->json([], 409); return response()->json([], 409);
} }
/** @var User $user */
$user = auth()->user();
$this->repository->disable($currency); $this->repository->disable($currency);
$manager = $this->getManager(); $manager = $this->getManager();
$defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $currency->refreshForUser($user);
$this->parameters->set('defaultCurrency', $defaultCurrency);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);
@@ -110,14 +112,15 @@ class UpdateController extends Controller
*/ */
public function makeDefault(TransactionCurrency $currency): JsonResponse public function makeDefault(TransactionCurrency $currency): JsonResponse
{ {
/** @var User $user */
$user = auth()->user();
$this->repository->enable($currency); $this->repository->enable($currency);
$this->repository->makeDefault($currency);
app('preferences')->set('currencyPreference', $currency->code);
app('preferences')->mark(); app('preferences')->mark();
$manager = $this->getManager(); $manager = $this->getManager();
$currency->refreshForUser($user);
$this->parameters->set('defaultCurrency', $currency);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);
@@ -144,9 +147,10 @@ class UpdateController extends Controller
{ {
$this->repository->enable($currency); $this->repository->enable($currency);
$manager = $this->getManager(); $manager = $this->getManager();
/** @var User $user */
$user = auth()->user();
$defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); $currency->refreshForUser($user);
$this->parameters->set('defaultCurrency', $defaultCurrency);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);
@@ -172,18 +176,16 @@ class UpdateController extends Controller
*/ */
public function update(UpdateRequest $request, TransactionCurrency $currency): JsonResponse 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); $currency = $this->repository->update($currency, $data);
if (true === $request->boolean('default')) { app('preferences')->mark();
app('preferences')->set('currencyPreference', $currency->code);
app('preferences')->mark();
}
$manager = $this->getManager(); $manager = $this->getManager();
$currency->refreshForUser($user);
$defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user());
$this->parameters->set('defaultCurrency', $defaultCurrency);
/** @var CurrencyTransformer $transformer */ /** @var CurrencyTransformer $transformer */
$transformer = app(CurrencyTransformer::class); $transformer = app(CurrencyTransformer::class);

View File

@@ -57,7 +57,6 @@ class UpdateRequest extends FormRequest
]; ];
return $this->getAllData($fields); return $this->getAllData($fields);
// return $return;
} }
/** /**

View File

@@ -129,20 +129,7 @@ class AccountCurrencies extends Command
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// get user's currency preference: // get user's currency preference:
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data; $defaultCurrency = app('amount')->getDefaultCurrencyByUser($user);
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;
}
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {

View File

@@ -0,0 +1,153 @@
<?php
namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
/**
* Class UpgradeCurrencyPreferences
* TODO DONT FORGET TO ADD THIS TO THE DOCKER BUILD
*/
class UpgradeCurrencyPreferences extends Command
{
use ShowsFriendlyMessages;
public const CONFIG_NAME = '610_upgrade_currency_prefs';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Upgrade user currency preferences';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly-iii:upgrade-currency-preferences {--F|force : Force the execution of this command.}';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
if ($this->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());
}
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Console\Command;
*/ */
class UpgradeSkeleton extends Command class UpgradeSkeleton extends Command
{ {
use ShowsFriendlyMessages;
public const CONFIG_NAME = '480_some_name'; public const CONFIG_NAME = '480_some_name';
/** /**
* The console command description. * The console command description.

View File

@@ -100,34 +100,7 @@ class CurrencyController extends Controller
return view('currencies.create', compact('subTitleIcon', 'subTitle')); 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. * Deletes a currency.
@@ -206,57 +179,6 @@ class CurrencyController extends Controller
return redirect($this->getPreviousUrl('currencies.delete.url')); 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. * Edit a currency.
@@ -299,60 +221,6 @@ class CurrencyController extends Controller
return view('currencies.edit', compact('currency', 'subTitle', 'subTitleIcon')); 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. * Store new currency.
* *

View File

@@ -118,9 +118,6 @@ class HomeController extends Controller
*/ */
public function index(AccountRepositoryInterface $repository): mixed public function index(AccountRepositoryInterface $repository): mixed
{ {
if ('v3' === config('firefly.layout')) {
return view('pwa');
}
$types = config('firefly.accountTypesByIdentifier.asset'); $types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types); $count = $repository->count($types);
Log::channel('audit')->info('User visits homepage.'); Log::channel('audit')->info('User visits homepage.');

View File

@@ -56,13 +56,11 @@ class JavascriptController extends Controller
*/ */
public function accounts(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository): Response 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] [AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]
); );
$preference = app('preferences')->get('currencyPreference', config('firefly.default_currency', 'EUR')); $default = app('amount')->getDefaultCurrency();
$default = $currencyRepository->findByCodeNull((string)$preference->data); $data = ['accounts' => []];
$data = ['accounts' => []];
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {

View File

@@ -114,7 +114,7 @@ class NewUserController extends Controller
$this->createCashWalletAccount($currency, $language); // create cash wallet account $this->createCashWalletAccount($currency, $language); // create cash wallet account
// store currency preference: // store currency preference:
app('preferences')->set('currencyPreference', $currency->code); $currencyRepository->makeDefault($currency);
// store frontpage preferences: // store frontpage preferences:
$accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray(); $accounts = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray();

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\TransactionCurrency;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class IndexController extends Controller
{
protected CurrencyRepositoryInterface $repository;
protected UserRepositoryInterface $userRepository;
/**
* CurrencyController constructor.
*
*/
public function __construct()
{
parent::__construct();
$this->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'));
}
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Middleware;
use Closure; use Closure;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Bill; use FireflyIII\Models\Bill;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Webhook; use FireflyIII\Models\Webhook;
@@ -68,6 +69,10 @@ class InterestingMessage
Preferences::mark(); Preferences::mark();
$this->handleWebhookMessage($request); $this->handleWebhookMessage($request);
} }
if ($this->currencyMessage($request)) {
Preferences::mark();
$this->handleCurrencyMessage($request);
}
return $next($request); return $next($request);
} }
@@ -221,10 +226,57 @@ class InterestingMessage
private function webhookMessage(Request $request): bool private function webhookMessage(Request $request): bool
{ {
// get parameters from request. // 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'); $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]));
}
} }
/** /**

View File

@@ -24,8 +24,10 @@ declare(strict_types=1);
namespace FireflyIII\Models; namespace FireflyIII\Models;
use Eloquent; use Eloquent;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
@@ -40,6 +42,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @property Carbon|null $updated_at * @property Carbon|null $updated_at
* @property Carbon|null $deleted_at * @property Carbon|null $deleted_at
* @property bool $enabled * @property bool $enabled
* @property bool $userDefault
* @property bool $userEnabled
* @property string $code * @property string $code
* @property string $name * @property string $name
* @property string $symbol * @property string $symbol
@@ -107,6 +111,39 @@ class TransactionCurrency extends Model
throw new NotFoundHttpException(); 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('user_default');
}
/**
* Link to user groups
*
* @return BelongsToMany
*/
public function userGroups(): BelongsToMany
{
return $this->belongsToMany(UserGroup::class)->withTimestamps()->withPivot('group_default');
}
/** /**
* @return HasMany * @return HasMany
*/ */

View File

@@ -30,6 +30,7 @@ use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@@ -129,6 +130,16 @@ class UserGroup extends Model
return $this->hasMany(Account::class); return $this->hasMany(Account::class);
} }
/**
* Link to currencies
*
* @return BelongsToMany
*/
public function currencies(): BelongsToMany
{
return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('group_default');
}
/** /**
* Link to attachments. * Link to attachments.
* *

View File

@@ -140,8 +140,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
} }
// is the default currency for the user or the system // is the default currency for the user or the system
$defaultCode = app('preferences')->getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; $count = $this->user->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('user_default', 1)->count();
if ($currency->code === $defaultCode) { if ($count > 0) {
Log::info('Is the default currency of the user, return true.'); Log::info('Is the default currency of the user, return true.');
return 'current_default'; return 'current_default';
@@ -166,11 +166,25 @@ class CurrencyRepository implements CurrencyRepositoryInterface
} }
/** /**
* Returns ALL currencies, regardless of whether they are enabled or not.
*
* @return Collection * @return Collection
*/ */
public function getAll(): 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 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;
} }
/** /**
@@ -206,6 +226,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function disable(TransactionCurrency $currency): void public function disable(TransactionCurrency $currency): void
{ {
$this->user->currencies()->detach($currency->id);
$currency->enabled = false; $currency->enabled = false;
$currency->save(); $currency->save();
} }
@@ -216,15 +237,13 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function ensureMinimalEnabledCurrencies(): void public function ensureMinimalEnabledCurrencies(): void
{ {
// if no currencies are enabled, enable the first one in the DB (usually the EUR) // if no currencies are enabled, enable the first one in the DB (usually the EUR)
if (0 === $this->get()->count()) { if (0 === $this->user->currencies()->count()) {
/** @var TransactionCurrency $first */ $euro = app('amount')->getSystemCurrency();
$first = $this->getAll()->first(); if (null === $euro) {
if (null === $first) {
throw new FireflyException('No currencies found. You broke Firefly III'); throw new FireflyException('No currencies found. You broke Firefly III');
} }
Log::channel('audit')->info(sprintf('Auto-enabled currency %s.', $first->code)); Log::channel('audit')->info(sprintf('Auto-enabled currency %s.', $euro->code));
$this->enable($first); $this->enable($euro);
app('preferences')->set('currencyPreference', $first->code);
app('preferences')->mark(); app('preferences')->mark();
} }
} }
@@ -235,7 +254,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function enable(TransactionCurrency $currency): void public function enable(TransactionCurrency $currency): void
{ {
$currency->enabled = true; $this->user->currencies()->syncWithoutDetaching([$currency->id]);
$currency->enabled = false;
$currency->save(); $currency->save();
} }
@@ -442,6 +462,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return null; return null;
} }
/**
* @inheritDoc
*/
public function getUserCurrencies(User $user): Collection
{
return $user->currencies()->get();
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -507,6 +535,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function store(array $data): TransactionCurrency public function store(array $data): TransactionCurrency
{ {
throw new FireflyException(sprintf('Method "%s" needs a refactor.', __METHOD__));
/** @var TransactionCurrencyFactory $factory */ /** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class); $factory = app(TransactionCurrencyFactory::class);
$result = $factory->create($data); $result = $factory->create($data);
@@ -526,9 +555,61 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/ */
public function update(TransactionCurrency $currency, array $data): TransactionCurrency 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 */ /** @var CurrencyUpdateService $service */
$service = app(CurrencyUpdateService::class); $service = app(CurrencyUpdateService::class);
return $service->update($currency, $data); 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]]);
}
} }

View File

@@ -204,6 +204,20 @@ interface CurrencyRepositoryInterface
*/ */
public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate; 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 * @param TransactionCurrency $currency
* *

View File

@@ -418,10 +418,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
if (null !== $currencyPreference) { if (null !== $currencyPreference) {
$currency = TransactionCurrency::where('id', $currencyPreference->data)->first(); $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; $journalId = (int)$row->transaction_journal_id;
$return[$journalId] = $return[$journalId] ?? []; $return[$journalId] = $return[$journalId] ?? [];

View File

@@ -52,14 +52,13 @@ class CurrencyUpdateService
$currency->name = e($data['name']); $currency->name = e($data['name']);
} }
if (array_key_exists('enabled', $data) && is_bool($data['enabled'])) { $currency->enabled = false;
$currency->enabled = (bool)$data['enabled'];
}
if (array_key_exists('decimal_places', $data) && is_int($data['decimal_places'])) { if (array_key_exists('decimal_places', $data) && is_int($data['decimal_places'])) {
$currency->decimal_places = (int)$data['decimal_places']; $currency->decimal_places = (int)$data['decimal_places'];
} }
unset($currency->userEnabled);
unset($currency->userDefault);
$currency->save(); $currency->save();
return $currency; return $currency;

View File

@@ -108,33 +108,9 @@ class Amount
*/ */
public function getCurrencies(): Collection public function getCurrencies(): Collection
{ {
return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); /** @var User $user */
} $user = auth()->user();
return $user->currencies()->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');
} }
/** /**
@@ -163,22 +139,14 @@ class Amount
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} }
$currencyPreference = app('preferences')->getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR')); $default = $user->currencies()->where('user_default', true)->first();
$currencyPrefStr = $currencyPreference ? $currencyPreference->data : 'EUR'; if(null === $default) {
$default = $this->getSystemCurrency();
// at this point the currency preference could be encrypted, if coming from an old version. $user->currencies()->sync([$default->id => ['user_default' => true]]);
$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();
} }
$cache->store($currency); $cache->store($default);
return $currency; return $default;
} }
/** /**

View File

@@ -60,6 +60,9 @@ class Preferences
*/ */
public function get(string $name, $default = null): ?Preference 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 */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
if (null === $user) { if (null === $user) {
@@ -82,6 +85,9 @@ class Preferences
*/ */
public function getForUser(User $user, string $name, $default = null): ?Preference 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']); $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();
@@ -108,6 +114,9 @@ class Preferences
*/ */
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);
@@ -123,6 +132,9 @@ 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);
@@ -138,6 +150,9 @@ class Preferences
*/ */
public function setForUser(User $user, string $name, $value): Preference 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); Cache::forget($fullName);
/** @var Preference|null $pref */ /** @var Preference|null $pref */
@@ -185,6 +200,9 @@ class Preferences
*/ */
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();
} }
@@ -220,6 +238,9 @@ class Preferences
*/ */
public function getFresh(string $name, $default = null): ?Preference 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 */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
if (null === $user) { if (null === $user) {
@@ -243,6 +264,9 @@ class Preferences
*/ */
public function getFreshForUser(User $user, string $name, $default = null): ?Preference 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);
} }
@@ -283,6 +307,9 @@ class Preferences
*/ */
public function set(string $name, $value): Preference public function set(string $name, $value): Preference
{ {
if('currencyPreference' === $name) {
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:

View File

@@ -34,23 +34,16 @@ class CurrencyTransformer extends AbstractTransformer
* Transform the currency. * Transform the currency.
* *
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
*
* @return array * @return array
*/ */
public function transform(TransactionCurrency $currency): 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 [ return [
'id' => (int)$currency->id, 'id' => (int)$currency->id,
'created_at' => $currency->created_at->toAtomString(), 'created_at' => $currency->created_at->toAtomString(),
'updated_at' => $currency->updated_at->toAtomString(), 'updated_at' => $currency->updated_at->toAtomString(),
'default' => $isDefault, 'default' => $currency->userDefault,
'enabled' => $currency->enabled, 'enabled' => $currency->userEnabled,
'name' => $currency->name, 'name' => $currency->name,
'code' => $currency->code, 'code' => $currency->code,
'symbol' => $currency->symbol, 'symbol' => $currency->symbol,

View File

@@ -46,6 +46,7 @@ use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
@@ -231,6 +232,16 @@ class User extends Authenticatable
return $this->hasMany(Account::class); return $this->hasMany(Account::class);
} }
/**
* Link to currencies
*
* @return BelongsToMany
*/
public function currencies(): BelongsToMany
{
return $this->belongsToMany(TransactionCurrency::class)->withTimestamps()->withPivot('user_default');
}
/** /**
* Link to attachments * Link to attachments
* *

View File

@@ -114,7 +114,7 @@ return [
], ],
'version' => '6.0.27', 'version' => '6.0.27',
'api_version' => '2.0.10', 'api_version' => '2.0.10',
'db_version' => 20, 'db_version' => 21,
// generic settings // generic settings
'maxUploadSize' => 1073741824, // 1 GB 'maxUploadSize' => 1073741824, // 1 GB

View File

@@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
// transaction_currency_user
if (!Schema::hasTable('transaction_currency_user')) {
try {
Schema::create('transaction_currency_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->integer('user_id', false, true);
$table->integer('transaction_currency_id', false, true);
$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');
});
} 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('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');
});
} 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');
}
};

View File

@@ -24,55 +24,85 @@
$(function () { $(function () {
"use strict"; "use strict";
$('.make_default').on('click', setDefaultCurrency); $('.make_default').on('click', setDefaultCurrency);
$('.enable-currency').on('click', enableCurrency); $('.enable-currency').on('click', enableCurrency);
$('.disable-currency').on('click', disableCurrency); $('.disable-currency').on('click', disableCurrency);
}); });
function setDefaultCurrency(e) { function setDefaultCurrency(e) {
var button = $(e.currentTarget); var button = $(e.currentTarget);
var currencyId = parseInt(button.data('id')); var currencyCode = button.data('code');
$.post(makeDefaultUrl, { var params = {
_token: token, default: true,
id: currencyId enabled: true
}).done(function (data) { }
// lame but it works
location.reload(); $.ajax({
}).fail(function () { url: updateCurrencyUrl + '/' + currencyCode,
console.error('I failed :('); data: JSON.stringify(params),
type: 'PUT',
headers: {
'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;
}
}); });
return false; return false;
} }
function enableCurrency(e) { function enableCurrency(e) {
var button = $(e.currentTarget); var button = $(e.currentTarget);
var currencyId = parseInt(button.data('id')); var currencyCode = button.data('code');
$.post(enableCurrencyUrl, { var params = {
_token: token, enabled: true
id: currencyId }
}).done(function (data) {
// lame but it works $.ajax({
location.reload(); url: updateCurrencyUrl + '/' + currencyCode,
}).fail(function () { data: JSON.stringify(params),
console.error('I failed :('); type: 'PUT',
headers: {
'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;
}
}); });
return false; return false;
} }
function disableCurrency(e) { function disableCurrency(e) {
var button = $(e.currentTarget); var button = $(e.currentTarget);
var currencyId = parseInt(button.data('id')); var currencyCode = button.data('code');
$.post(disableCurrencyUrl, { var params = {
_token: token, enabled: false
id: currencyId }
}).done(function (data) {
// lame but it works $.ajax({
location.reload(); url: updateCurrencyUrl + '/' + currencyCode,
}).fail(function () { data: JSON.stringify(params),
console.error('I failed :('); type: 'PUT',
headers: {
'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;
}
}); });
return false; return false;
} }

View File

@@ -1583,7 +1583,8 @@ return [
'create_currency' => 'Create a new currency', 'create_currency' => 'Create a new currency',
'store_currency' => 'Store new currency', 'store_currency' => 'Store new currency',
'update_currency' => 'Update 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_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_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.', 'cannot_disable_currency_journals' => 'Cannot disable :name because transactions are still using it.',
@@ -1609,7 +1610,9 @@ return [
'disable_currency' => 'Disable', 'disable_currency' => 'Disable',
'currencies_default_disabled' => 'Most of these currencies are disabled by default. To use them, you must enable them first.', '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', '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', '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: // forms:
'mandatoryFields' => 'Mandatory fields', 'mandatoryFields' => 'Mandatory fields',

View File

@@ -26,62 +26,64 @@
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
{% if isOwner %} <th>&nbsp;</th>
<th>&nbsp;</th>
{% endif %}
<th>{{ 'currency'|_ }}</th> <th>{{ 'currency'|_ }}</th>
<th>{{ 'number_of_decimals'|_ }}</th> <th>{{ 'number_of_decimals'|_ }}</th>
<th>&nbsp;</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for currency in currencies %} {% for currency in currencies %}
<tr> <tr>
{% if isOwner %}
<td> <td>
<div class="btn-group btn-group-xs"> <div class="btn-group btn-group-xs">
{% if isOwner %}
<a class="btn btn-default" href="{{ route('currencies.edit',currency.id) }}"><span <a class="btn btn-default" href="{{ route('currencies.edit',currency.id) }}"><span
class="fa fa-fw fa-pencil"></span></a> class="fa fa-fw fa-pencil"></span></a>
<a class="btn btn-danger" href="{{ route('currencies.delete',currency.id) }}"><span <a class="btn btn-danger" href="{{ route('currencies.delete',currency.id) }}"><span
class="fa fa-fw fa-trash"></span></a> class="fa fa-fw fa-trash"></span></a>
{% endif %}
{# Disable the currency. #}
{% if currency.userEnabled %}
<a class="btn btn-default disable-currency" data-code="{{ currency.code }}"
href="#">
<span class="fa fa-fw fa-square-o"></span>
{{ 'disable_currency'|_ }}</a>
{% endif %}
{# Enable the currency. #}
{% if not currency.userEnabled %}
<a class="btn btn-default enable-currency" data-code="{{ currency.code }}"
href="#">
<span class="fa fa-fw fa-check-square-o"></span>
{{ 'enable_currency'|_ }}</a>
{% endif %}
{# Make currency default. #}
{% if currency.id != defaultCurrency.id %}
<button data-code="{{ currency.code }}" class="make_default btn btn-default"><span
class="fa fa-fw fa-star"></span> {{ 'make_default_currency'|_ }}</button>
{% endif %}
</div> </div>
</td> </td>
{% endif %}
<td> <td>
{% if currency.enabled == false %} {% if currency.userEnabled == false %}
<span class="text-muted"> <span class="text-muted">
{% endif %} {% endif %}
{{ currency.name }} ({{ currency.code }}) ({{ currency.symbol|raw }}) {{ currency.name }} ({{ currency.code }}) ({{ currency.symbol|raw }})
{% if currency.id == defaultCurrency.id %} {% if currency.id == defaultCurrency.id %}
<span class="label label-success" id="default-currency">{{ 'default_currency'|_ }}</span> <span class="label label-success" id="default-currency">{{ 'default_currency'|_ }}</span>
{% endif %} {% endif %}
{% if currency.enabled == false %} {% if currency.userEnabled == false %}
<span class="label label-default">{{ 'currency_is_disabled'|_ }}</span>
{% endif %}
{% if currency.userEnabled == false %}
</span> </span>
<br><small class="text-danger">{{ 'currency_is_disabled'|_ }}</small>
{% endif %} {% endif %}
</td> </td>
<td>{{ currency.decimal_places }}</td> <td>{{ currency.decimal_places }}</td>
<td class="buttons">
<div class="btn-group">
{% if currency.id != defaultCurrency.id %}
<button data-id="{{ currency.id }}" class="make_default btn btn-default"><span
class="fa fa-fw fa-star"></span> {{ 'make_default_currency'|_ }}</button>
{% endif %}
{% if currency.enabled %}
<a class="btn btn-default disable-currency" data-id="{{ currency.id }}"
href="#">
<span class="fa fa-fw fa-square-o"></span>
{{ 'disable_currency'|_ }}</a>
{% endif %}
{% if not currency.enabled %}
<a class="btn btn-default enable-currency" data-id="{{ currency.id }}"
href="#">
<span class="fa fa-fw fa-check-square-o"></span>
{{ 'enable_currency'|_ }}</a>
{% endif %}
</div>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -100,9 +102,8 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script type="text/javascript" nonce="{{ JS_NONCE }}"> <script type="text/javascript" nonce="{{ JS_NONCE }}">
var makeDefaultUrl = "{{ route('currencies.default') }}"; var redirectUrl = "{{ route('currencies.index') }}";
var disableCurrencyUrl = "{{ route('currencies.disable') }}"; var updateCurrencyUrl = "{{ route('api.v1.currencies.update', ['']) }}";
var enableCurrencyUrl = "{{ route('currencies.enable') }}";
</script> </script>
<script type="text/javascript" src="v1/js/ff/currencies/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> <script type="text/javascript" src="v1/js/ff/currencies/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
{% endblock %} {% endblock %}

View File

@@ -333,19 +333,16 @@ Route::group(
* Currency Controller. * Currency Controller.
*/ */
Route::group( 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 () { static function () {
Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']); Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
Route::get('create', ['uses' => 'CurrencyController@create', 'as' => 'create']); Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']);
Route::get('edit/{currency}', ['uses' => 'CurrencyController@edit', 'as' => 'edit']); Route::get('edit/{currency}', ['uses' => 'EditController@edit', 'as' => 'edit']);
Route::get('delete/{currency}', ['uses' => 'CurrencyController@delete', 'as' => 'delete']); Route::get('delete/{currency}', ['uses' => 'DeleteController@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::post('store', ['uses' => 'CurrencyController@store', 'as' => 'store']); Route::post('store', ['uses' => 'CreateController@store', 'as' => 'store']);
Route::post('update/{currency}', ['uses' => 'CurrencyController@update', 'as' => 'update']); Route::post('update/{currency}', ['uses' => 'EditController@update', 'as' => 'update']);
Route::post('destroy/{currency}', ['uses' => 'CurrencyController@destroy', 'as' => 'destroy']); Route::post('destroy/{currency}', ['uses' => 'EditController@destroy', 'as' => 'destroy']);
} }
); );