Compare commits

...

25 Commits

Author SHA1 Message Date
github-actions
f727a38b69 Auto commit for release 'develop' on 2025-01-01 2025-01-01 16:47:50 +01:00
James Cole
c11a5384da Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-01-01 16:43:44 +01:00
James Cole
ed92cbd4b8 Fix piggy bank rule action 2025-01-01 16:43:31 +01:00
github-actions
a9ea32772f Auto commit for release 'develop' on 2025-01-01 2025-01-01 16:38:58 +01:00
James Cole
6fde693e7a Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
Some checks failed
Code - Run Sonarcloud / SonarCloud (push) Failing after 19s
# Conflicts:
#	app/Support/Steam.php
2024-12-31 08:19:47 +01:00
James Cole
2e46d9ba33 Clean up some logs. 2024-12-31 08:18:40 +01:00
James Cole
e36f8deb08 Clean up some logs. 2024-12-31 08:18:21 +01:00
James Cole
1631b422f1 Clean up logs, add cache. 2024-12-31 08:17:35 +01:00
github-actions
b58d809063 Auto commit for release 'develop' on 2024-12-31 2024-12-31 08:09:51 +01:00
James Cole
9e34314dbc Fix various issues with new features, nullpointers and missing checks. 2024-12-31 08:05:25 +01:00
github-actions
e4aa218b5f Auto commit for release 'v6.2.0-alpha.1' on 2024-12-30
Some checks failed
Code - Run Sonarcloud / SonarCloud (push) Failing after 19s
Chore - Prune old builds / prune (push) Failing after 10s
Issues - Lock old issues / lock (push) Failing after 8s
Issues - Mark and close stale issues / stale (push) Successful in 10s
2024-12-30 17:09:40 +01:00
github-actions
31722477d4 Merge branch 'develop' 2024-12-30 16:07:29 +00:00
github-actions
ec82105433 Auto commit for release 'develop' on 2024-12-30
Some checks failed
Code - Run Sonarcloud / SonarCloud (push) Failing after 20s
2024-12-30 15:35:26 +01:00
James Cole
146e164f04 Back to 8. 2024-12-30 15:31:27 +01:00
James Cole
7d37c93988 Expand workflow.
Some checks failed
Code - Run Sonarcloud / SonarCloud (push) Failing after 19s
Chore - Prune old builds / prune (push) Failing after 9s
Issues - Lock old issues / lock (push) Failing after 8s
Issues - Mark and close stale issues / stale (push) Successful in 10s
2024-12-30 15:25:39 +01:00
James Cole
73dffacd9a Expand to support alpha and beta versions. 2024-12-30 12:23:50 +01:00
James Cole
d37304fa68 Fix available budgets. 2024-12-30 12:22:39 +01:00
James Cole
62f4da6063 Update available budgets code. 2024-12-30 10:51:34 +01:00
James Cole
760da08ab7 Update exchange rates.
Some checks failed
Code - Run Sonarcloud / SonarCloud (push) Failing after 19s
2024-12-30 07:36:22 +01:00
James Cole
e68c4d4408 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-12-30 07:36:12 +01:00
James Cole
46a200aa1f Fix broken variable. 2024-12-30 07:30:23 +01:00
James Cole
c422039335 Merge pull request #9589 from tasnim0tantawi/autocomplete-test
Autocomplete test - CurrencyControllerTest & ObjectGroupControllerTest
2024-12-30 07:18:05 +01:00
TasneemTantawy
5971d155ef fixed create authenticated user 2024-12-23 09:37:06 +03:00
TasneemTantawy
9e373a9b0d object group controller test 2024-12-22 16:29:56 +03:00
TasneemTantawy
4fb61646b4 currency controller test 2024-12-22 15:54:18 +03:00
56 changed files with 824 additions and 372 deletions

View File

@@ -1641,16 +1641,16 @@
},
{
"name": "symfony/finder",
"version": "v7.2.0",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49"
"reference": "87a71856f2f56e4100373e92529eed3171695cfb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49",
"url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
"reference": "87a71856f2f56e4100373e92529eed3171695cfb",
"shasum": ""
},
"require": {
@@ -1685,7 +1685,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.2.0"
"source": "https://github.com/symfony/finder/tree/v7.2.2"
},
"funding": [
{
@@ -1701,7 +1701,7 @@
"type": "tidelift"
}
],
"time": "2024-10-23T06:56:12+00:00"
"time": "2024-12-30T19:00:17+00:00"
},
{
"name": "symfony/options-resolver",
@@ -2390,16 +2390,16 @@
},
{
"name": "symfony/stopwatch",
"version": "v7.2.0",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "696f418b0d722a4225e1c3d95489d262971ca924"
"reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/696f418b0d722a4225e1c3d95489d262971ca924",
"reference": "696f418b0d722a4225e1c3d95489d262971ca924",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df",
"reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df",
"shasum": ""
},
"require": {
@@ -2432,7 +2432,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v7.2.0"
"source": "https://github.com/symfony/stopwatch/tree/v7.2.2"
},
"funding": [
{
@@ -2448,7 +2448,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
"time": "2024-12-18T14:28:33+00:00"
},
{
"name": "symfony/string",

View File

@@ -168,7 +168,7 @@ jobs:
# if this is a develop build, slightly different variable names.
if [[ "develop" == "$version" ]]; then
[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
#[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
releaseName=$version-$(date +'%Y%m%d')
originalName=$releaseName
zipName=FireflyIII-develop.zip
@@ -177,7 +177,7 @@ jobs:
# if this is a branch build, also slightly different variable names.
if [[ "$version" == branch* ]]; then
[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
#[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
# branch builds overrule develop
releaseName=$version-$(date +'%Y%m%d')
originalName=$releaseName
@@ -229,7 +229,7 @@ jobs:
# describe the development release.
if [[ "develop" == "$version" ]]; then
echo 'Develop release.'
rm output.txt
rm -f output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
@@ -244,7 +244,7 @@ jobs:
# describe a branch release
if [[ "$version" == branch* ]]; then
echo 'Branch release.'
rm output.txt
rm -f output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Irregular BRANCH release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
@@ -257,9 +257,45 @@ jobs:
echo ":warning: Please be careful with this branch pre-release, as it may not work as expected." >> output.txt
fi
# describe the main release
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]]; then
sudo chown -R runner:docker output.txt
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]] && [[ "$version" != *alpha* ]] && [[ "$version" != *beta* ]]; then
echo 'Main release.'
sudo chown -R runner:docker output.txt
echo '' >> output.txt
echo '### Instructions' >> output.txt
echo '' >> output.txt
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
fi
# describe alpha release
if [[ "$version" == *alpha* ]]; then
echo 'ALPHA release.'
rm -f output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Very early ALPHA release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
echo '' >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo '' >> output.txt
echo '### Instructions' >> output.txt
echo '' >> output.txt
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
fi
# describe beta release
if [[ "$version" == *beta* ]]; then
echo 'BETA release.'
rm -f output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Very early BETA release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
echo '' >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo '' >> output.txt
echo '### Instructions' >> output.txt
echo '' >> output.txt
@@ -336,12 +372,12 @@ jobs:
gh release upload $releaseName HEAD.txt
# remove all temporary files
rm output.txt
rm HEAD.txt
rm $zipName
rm $zipName.sha256
rm $tarName
rm $tarName.sha256
rm -f output.txt
rm -f HEAD.txt
rm -f $zipName
rm -f $zipName.sha256
rm -f $tarName
rm -f $tarName.sha256
# merge main back into develop
git checkout develop

View File

@@ -4,6 +4,7 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2024
- TasneemTantawy
- Antônio Franco
- yparitcher
- Jhon Pedroza

View File

@@ -29,7 +29,6 @@ use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
@@ -86,7 +85,7 @@ class AccountController extends Controller
foreach ($result as $account) {
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$useCurrency = $currency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = Steam::finalAccountBalance($account, $date);
$key = $this->convertToNative && $currency->id !== $this->defaultCurrency->id ? 'native_balance' : 'balance';
@@ -116,7 +115,7 @@ class AccountController extends Controller
usort(
$return,
static function (array $left, array $right) {
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
$order = [AccountTypeEnum::ASSET->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::EXPENSE->value];
$posA = (int) array_search($left['type'], $order, true);
$posB = (int) array_search($right['type'], $order, true);

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Chart;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
@@ -40,7 +39,6 @@ class ChartRequest extends FormRequest
{
use ChecksLogin;
use ConvertsDataTypes;
use ParsesQueryFilters;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];

View File

@@ -143,6 +143,7 @@ class BudgetLimitHandler
);
$availableBudget->save();
Log::debug(sprintf('ID of new AB is #%d', $availableBudget->id));
$this->calculateAmount($availableBudget);
}
}

View File

@@ -33,7 +33,7 @@ class AvailableBudgetObserver
{
public function created(AvailableBudget $availableBudget): void
{
Log::debug('Observe "created" of an available budget.');
// Log::debug('Observe "created" of an available budget.');
$this->updateNativeAmount($availableBudget);
}
@@ -46,7 +46,7 @@ class AvailableBudgetObserver
private function updateNativeAmount(AvailableBudget $availableBudget): void
{
if (!Amount::convertToNative($availableBudget->user)) {
Log::debug('Do not update native available amount of the available budget.');
// Log::debug('Do not update native available amount of the available budget.');
return;
}

View File

@@ -46,7 +46,7 @@ class BudgetLimitObserver
private function updateNativeAmount(BudgetLimit $budgetLimit): void
{
if (!Amount::convertToNative($budgetLimit->budget->user)) {
Log::debug('Do not update native amount of the budget limit.');
// Log::debug('Do not update native amount of the budget limit.');
return;
}

View File

@@ -76,7 +76,6 @@ class CreateController extends Controller
*/
public function create(Request $request, string $objectType)
{
$defaultCurrency = app('amount')->getDefaultCurrency();
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
$subTitle = (string) trans(sprintf('firefly.make_new_%s_account', $objectType));
$roles = $this->getRoles();
@@ -106,7 +105,7 @@ class CreateController extends Controller
$request->session()->flash(
'preFilled',
[
'currency_id' => $defaultCurrency->id,
'currency_id' => $this->defaultCurrency->id,
'include_net_worth' => $hasOldInput ? (bool) $request->old('include_net_worth') : true,
]
);

View File

@@ -124,7 +124,7 @@ class EditController extends Controller
$openingBalanceAmount = '';
}
$openingBalanceDate = $repository->getOpeningBalanceDate($account);
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
// include this account in net-worth charts?
$includeNetWorth = $repository->getMetaValue($account, 'include_net_worth');

View File

@@ -86,7 +86,7 @@ class ReconcileController extends Controller
return redirect(route('accounts.index', [config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type))]));
}
$currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->defaultCurrency;
// no start or end:
$range = app('navigation')->getViewRange(false);
@@ -197,7 +197,7 @@ class ReconcileController extends Controller
}
$reconciliation = $this->accountRepos->getReconciliation($account);
$currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->defaultCurrency;
$source = $reconciliation;
$destination = $account;
if (1 === bccomp($difference, '0')) {

View File

@@ -30,7 +30,6 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\PeriodOverview;
use Illuminate\Contracts\View\Factory;
@@ -101,7 +100,7 @@ class ShowController extends Controller
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$accountCurrency = $this->repository->getAccountCurrency($account);
$currency = $accountCurrency ?? Amount::getDefaultCurrency();
$currency = $accountCurrency ?? $this->defaultCurrency;
$fStart = $start->isoFormat($this->monthAndDayFormat);
$fEnd = $end->isoFormat($this->monthAndDayFormat);
$subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
@@ -178,7 +177,7 @@ class ShowController extends Controller
$subTitleIcon = config('firefly.subIconsByIdentifier.'.$account->accountType->type);
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? Amount::getDefaultCurrency();
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
$periods = new Collection();

View File

@@ -77,7 +77,7 @@ class CreateController extends Controller
$periods[$current] = (string) trans('firefly.repeat_freq_'.$current);
}
$subTitle = (string) trans('firefly.create_new_bill');
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
// put previous url in session if not redirect from store (not "create another").
if (true !== session('bills.create.fromStore')) {

View File

@@ -85,11 +85,10 @@ class EditController extends Controller
$this->rememberPreviousUrl('bills.edit.url');
}
$currency = app('amount')->getDefaultCurrency();
$bill->amount_min = app('steam')->bcround($bill->amount_min, $currency->decimal_places);
$bill->amount_max = app('steam')->bcround($bill->amount_max, $currency->decimal_places);
$bill->amount_min = app('steam')->bcround($bill->amount_min, $bill->transactionCurrency->decimal_places);
$bill->amount_max = app('steam')->bcround($bill->amount_max, $bill->transactionCurrency->decimal_places);
$rules = $this->repository->getRulesForBill($bill);
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');

View File

@@ -86,11 +86,10 @@ class CreateController extends Controller
'half_year' => (string) trans('firefly.auto_budget_period_half_year'),
'yearly' => (string) trans('firefly.auto_budget_period_yearly'),
];
$currency = app('amount')->getDefaultCurrency();
$preFilled = [
'auto_budget_period' => $hasOldInput ? (bool) $request->old('auto_budget_period') : 'monthly',
'auto_budget_currency_id' => $hasOldInput ? (int) $request->old('auto_budget_currency_id') : $currency->id,
'auto_budget_currency_id' => $hasOldInput ? (int) $request->old('auto_budget_currency_id') : $this->defaultCurrency->id,
];
$request->session()->flash('preFilled', $preFilled);

View File

@@ -91,10 +91,9 @@ class EditController extends Controller
// code to handle active-checkboxes
$hasOldInput = null !== $request->old('_token');
$currency = app('amount')->getDefaultCurrency();
$preFilled = [
'active' => $hasOldInput ? (bool) $request->old('active') : $budget->active,
'auto_budget_currency_id' => $hasOldInput ? (int) $request->old('auto_budget_currency_id') : $currency->id,
'auto_budget_currency_id' => $hasOldInput ? (int) $request->old('auto_budget_currency_id') : $this->defaultCurrency->id,
];
if (null !== $autoBudget) {
$amount = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount;

View File

@@ -42,6 +42,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
/**
@@ -106,7 +107,6 @@ class IndexController extends Controller
$end ??= session('end', today(config('app.timezone'))->endOfMonth());
}
$defaultCurrency = app('amount')->getDefaultCurrency();
$currencies = $this->currencyRepository->get();
$budgeted = '0';
$spent = '0';
@@ -119,14 +119,14 @@ class IndexController extends Controller
// get all available budgets:
$availableBudgets = $this->getAllAvailableBudgets($start, $end);
// get all active budgets:
$budgets = $this->getAllBudgets($start, $end, $currencies, $defaultCurrency);
$budgets = $this->getAllBudgets($start, $end, $currencies, $this->defaultCurrency);
$sums = $this->getSums($budgets);
// get budgeted for default currency:
if (0 === count($availableBudgets)) {
$budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency);
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $defaultCurrency);
$spent = $spentArr[$defaultCurrency->id]['sum'] ?? '0';
$budgeted = $this->blRepository->budgeted($start, $end, $this->defaultCurrency);
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $this->defaultCurrency);
$spent = $spentArr[$this->defaultCurrency->id]['sum'] ?? '0';
unset($spentArr);
}
@@ -136,6 +136,7 @@ class IndexController extends Controller
// get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets();
$defaultCurrency = $this->defaultCurrency;
return view(
'budgets.index',
@@ -162,6 +163,7 @@ class IndexController extends Controller
private function getAllAvailableBudgets(Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Start of getAllAvailableBudgets("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$converter = new ExchangeRateConverter();
// get all available budgets.
$ab = $this->abRepository->get($start, $end);

View File

@@ -103,7 +103,6 @@ class AccountController extends Controller
$currencies = [];
$chartData = [];
$tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]);
@@ -115,7 +114,7 @@ class AccountController extends Controller
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name));
// Log::debug(sprintf('[a] Now in account #%d ("%s")', $account->id, $account->name));
$expenses = $endBalances[$account->id] ?? false;
if (false === $expenses) {
Log::error(sprintf('Found no end balance for account #%d', $account->id));
@@ -138,13 +137,13 @@ class AccountController extends Controller
continue;
}
Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $default->code : $key;
Log::debug(sprintf('Search code is %s', $searchCode));
// Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $this->defaultCurrency->code : $key;
// Log::debug(sprintf('Search code is %s', $searchCode));
// see if there is an accompanying start amount.
// grab the difference and find the currency.
$startBalance = ($startBalances[$account->id][$key] ?? '0');
Log::debug(sprintf('Start balance is %s', $startBalance));
// Log::debug(sprintf('Start balance is %s', $startBalance));
$diff = bcsub($endBalance, $startBalance);
$currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode);
if (0 !== bccomp($diff, '0')) {
@@ -562,7 +561,6 @@ class AccountController extends Controller
$currencies = [];
$chartData = [];
$tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::REVENUE->value]);
@@ -575,7 +573,7 @@ class AccountController extends Controller
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name));
// Log::debug(sprintf('[b] Now in account #%d ("%s")', $account->id, $account->name));
$expenses = $endBalances[$account->id] ?? false;
if (false === $expenses) {
Log::error(sprintf('Found no end balance for account #%d', $account->id));
@@ -598,13 +596,13 @@ class AccountController extends Controller
continue;
}
Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $default->code : $key;
Log::debug(sprintf('Search code is %s', $searchCode));
// Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $this->defaultCurrency->code : $key;
// Log::debug(sprintf('Search code is %s', $searchCode));
// see if there is an accompanying start amount.
// grab the difference and find the currency.
$startBalance = ($startBalances[$account->id][$key] ?? '0');
Log::debug(sprintf('Start balance is %s', $startBalance));
// Log::debug(sprintf('Start balance is %s', $startBalance));
$diff = bcsub($endBalance, $startBalance);
$currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode);
if (0 !== bccomp($diff, '0')) {

View File

@@ -465,7 +465,7 @@ class BudgetController extends Controller
$chartGenerator->setStart($start);
$chartGenerator->setEnd($end);
$chartGenerator->convertToNative = $this->convertToNative;
$chartGenerator->default = Amount::getDefaultCurrency();
$chartGenerator->default = $this->defaultCurrency;
$chartData = $chartGenerator->generate();
$data = $this->generator->multiSet($chartData);

View File

@@ -118,7 +118,7 @@ abstract class Controller extends BaseController
$this->defaultCurrency =null;
// get shown-intro-preference:
if (auth()->check()) {
$this->defaultCurrency = app('amount')->getDefaultCurrency();
$this->defaultCurrency = Amount::getDefaultCurrency();
$language = Steam::getLanguage();
$locale = Steam::getLocale();
$darkMode = app('preferences')->get('darkMode', 'browser')->data;

View File

@@ -78,8 +78,7 @@ class BoxController extends Controller
$incomes = [];
$expenses = [];
$sums = [];
$currency = app('amount')->getDefaultCurrency();
$currency = $this->defaultCurrency;
// collect income of user:
/** @var GroupCollectorInterface $collector */
@@ -91,7 +90,7 @@ class BoxController extends Controller
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = $this->convertToNative ? $currency->id : (int) $journal['currency_id'];
$currencyId = $this->convertToNative && $this->defaultCurrency->id !== (int) $journal['currency_id'] ? $this->defaultCurrency->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd($incomes[$currencyId], app('steam')->positive($amount));
@@ -109,7 +108,7 @@ class BoxController extends Controller
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = $this->convertToNative ? $currency->id : (int) $journal['currency_id'];
$currencyId = $this->convertToNative ? $this->defaultCurrency->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
@@ -126,10 +125,10 @@ class BoxController extends Controller
$expenses[$currencyId] = app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false);
}
if (0 === count($sums)) {
$currency = app('amount')->getDefaultCurrency();
$sums[$currency->id] = app('amount')->formatAnything($currency, '0', false);
$incomes[$currency->id] = app('amount')->formatAnything($currency, '0', false);
$expenses[$currency->id] = app('amount')->formatAnything($currency, '0', false);
$currency = $this->defaultCurrency;
$sums[$this->defaultCurrency->id] = app('amount')->formatAnything($this->defaultCurrency, '0', false);
$incomes[$this->defaultCurrency->id] = app('amount')->formatAnything($this->defaultCurrency, '0', false);
$expenses[$this->defaultCurrency->id] = app('amount')->formatAnything($this->defaultCurrency, '0', false);
}
$response = [

View File

@@ -73,7 +73,7 @@ class ReconcileController extends Controller
{
$startBalance = $request->get('startBalance');
$endBalance = $request->get('endBalance');
$accountCurrency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$accountCurrency = $this->accountRepos->getAccountCurrency($account) ?? $this->defaultCurrency;
$amount = '0';
$clearedAmount = '0';
@@ -193,7 +193,7 @@ class ReconcileController extends Controller
$startDate->subDay();
$end->endOfDay();
$currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->defaultCurrency;
$startBalance = Steam::finalAccountBalance($account, $startDate)['balance'];
$endBalance = Steam::finalAccountBalance($account, $end)['balance'];

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Events\Preferences\UserGroupChangedDefaultCurrency;
use FireflyIII\Events\Test\UserTestNotificationChannel;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\PreferencesRequest;
@@ -35,6 +36,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
/**
@@ -258,6 +260,11 @@ class PreferencesController extends Controller
// convert native
$convertToNative = 1 === (int) $request->get('convertToNative');
if ($convertToNative && !$this->convertToNative) {
// set to true!
Log::debug('User sets convertToNative to true.');
event(new UserGroupChangedDefaultCurrency(auth()->user()->userGroup));
}
app('preferences')->set('convert_to_native', $convertToNative);
// custom fiscal year

View File

@@ -84,7 +84,7 @@ class CreateController extends Controller
{
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
$tomorrow = today(config('app.timezone'));
$oldRepetitionType = $request->old('repetition_type');
$tomorrow->addDay();
@@ -129,7 +129,7 @@ class CreateController extends Controller
{
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
$tomorrow = today(config('app.timezone'));
$oldRepetitionType = $request->old('repetition_type');
$tomorrow->addDay();

View File

@@ -62,8 +62,6 @@ class InstallController extends Controller
'migrate' => ['--seed' => true, '--force' => true],
'generate-keys' => [], // an exception :(
'firefly-iii:upgrade-database' => [],
// 'firefly-iii:correct-database' => [],
// 'firefly-iii:report-integrity' => [],
'firefly-iii:set-latest-version' => ['--james-is-cool' => true],
'firefly-iii:verify-security-alerts' => [],
];

View File

@@ -216,15 +216,14 @@ class ConvertController extends Controller
private function getLiabilities(): array
{
// make repositories
$accountList = $this->accountRepository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
$defaultCurrency = app('amount')->getDefaultCurrency();
$grouped = [];
$accountList = $this->accountRepository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
$grouped = [];
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = Steam::finalAccountBalance($account, today()->endOfDay())['balance'];
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->defaultCurrency;
$role = 'l_'.$account->accountType->type;
$key = (string) trans('firefly.opt_group_'.$role);
$grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
@@ -239,15 +238,14 @@ class ConvertController extends Controller
private function getAssetAccounts(): array
{
// make repositories
$accountList = $this->accountRepository->getActiveAccountsByType([AccountType::ASSET]);
$defaultCurrency = app('amount')->getDefaultCurrency();
$grouped = [];
$accountList = $this->accountRepository->getActiveAccountsByType([AccountType::ASSET]);
$grouped = [];
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = Steam::finalAccountBalance($account, today()->endOfDay())['balance'];
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->defaultCurrency;
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
if ('' === $role) {
$role = 'no_account_type';

View File

@@ -114,7 +114,7 @@ class CreateController extends Controller
$optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url($previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');

View File

@@ -82,7 +82,7 @@ class EditController extends Controller
$title = $transactionGroup->transactionJournals()->count() > 1 ? $transactionGroup->title : $transactionGroup->transactionJournals()->first()->description;
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = $this->defaultCurrency;
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url($previousUrl);

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Http\Middleware;
use Carbon\Carbon;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@@ -108,7 +109,7 @@ class Range
setlocale(LC_TIME, $localeArray);
$moneyResult = setlocale(LC_MONETARY, $localeArray);
// send error to view, if could not set money format
// send error to view, if it could not set money format
if (false === $moneyResult) {
app('log')->error('Could not set locale. The following array doesnt work: ', $localeArray);
app('view')->share('invalidMonetaryLocale', true);
@@ -117,7 +118,7 @@ class Range
// save some formats:
$monthAndDayFormat = (string) trans('config.month_and_day_js', [], $locale);
$dateTimeFormat = (string) trans('config.date_time_js', [], $locale);
$defaultCurrency = app('amount')->getDefaultCurrency();
$defaultCurrency = Amount::getDefaultCurrency();
// also format for moment JS:
$madMomentJS = (string) trans('config.month_and_day_moment_js', [], $locale);

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Requests;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -125,7 +126,7 @@ class PiggyBankStoreRequest extends FormRequest
$currencyId = (int) ($data['transaction_currency_id'] ?? 0);
$currency = TransactionCurrency::find($currencyId);
if (null === $currency) {
return app('amount')->getDefaultCurrency();
return Amount::getDefaultCurrency();
}
return $currency;

View File

@@ -27,6 +27,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -128,7 +129,7 @@ class PiggyBankUpdateRequest extends FormRequest
$currencyId = (int) ($data['transaction_currency_id'] ?? 0);
$currency = TransactionCurrency::find($currencyId);
if (null === $currency) {
return app('amount')->getDefaultCurrency();
return Amount::getDefaultCurrency();
}
return $currency;

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
@@ -101,4 +102,20 @@ class AvailableBudget extends Model
get: static fn ($value) => (int) $value,
);
}
protected function startDate(): Attribute
{
return Attribute::make(
get: fn (string $value) => Carbon::parse($value),
set: fn (Carbon $value) => $value->format('Y-m-d'),
);
}
protected function endDate(): Attribute
{
return Attribute::make(
get: fn (string $value) => Carbon::parse($value),
set: fn (Carbon $value) => $value->format('Y-m-d'),
);
}
}

View File

@@ -38,16 +38,16 @@ class RecurrenceRepetition extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const int WEEKEND_DO_NOTHING = 1;
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const int WEEKEND_SKIP_CREATION = 2;
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const int WEEKEND_TO_FRIDAY = 3;
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const int WEEKEND_TO_MONDAY = 4;
protected $casts

View File

@@ -83,15 +83,17 @@ class ReturnsAvailableChannels
private static function returnUserChannels(User $user): array
{
Log::debug(sprintf('Checking channels for user #%d', $user->id));
$channels = ['mail'];
$slackUrl = app('preferences')->getEncryptedForUser($user, 'slack_webhook_url', '')->data;
$slackUrl = (string) app('preferences')->getEncryptedForUser($user, 'slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
}
// validate presence of of Ntfy settings.
if ('' !== (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data) {
Log::debug('Enabled ntfy.');
$ntfyTopic = (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data;
if ('' !== $ntfyTopic) {
Log::debug(sprintf('Enabled ntfy, "%s"', $ntfyTopic));
$channels[] = NtfyChannel::class;
}
if ('' === (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data) {

View File

@@ -550,9 +550,9 @@ class BillRepository implements BillRepositoryInterface
foreach ($set as $transactionJournal) {
$setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
}
Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
// Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
// Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
}
return $return;
@@ -586,11 +586,11 @@ class BillRepository implements BillRepositoryInterface
$minField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_min' : 'amount_min';
$maxField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_max' : 'amount_max';
Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
// Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
if ($total > 0) {
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$average = bcdiv(bcadd($bill->{$maxField}, $bill->{$minField}), '2');
$average = bcdiv(bcadd($bill->{$maxField} ?? '0', $bill->{$minField} ?? '0'), '2');
Log::debug(sprintf('Amount to pay is %s %s (%d times)', $currency->code, $average, $total));
$return[$currency->id] ??= [
'id' => (string) $currency->id,

View File

@@ -64,17 +64,19 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
*/
public function get(?Carbon $start = null, ?Carbon $end = null): Collection
{
$query = $this->user->availableBudgets()->with(['transactionCurrency']);
$query = $this->user->availableBudgets()->with(['transactionCurrency']);
if (null !== $start && null !== $end) {
$query->where(
static function (Builder $q1) use ($start, $end): void { // @phpstan-ignore-line
$q1->where('start_date', '=', $start->format('Y-m-d H:i:s'));
$q1->where('end_date', '=', $end->format('Y-m-d H:i:s'));
$q1->where('start_date', '=', $start->format('Y-m-d'));
$q1->where('end_date', '=', $end->format('Y-m-d'));
}
);
}
$result = $query->get(['available_budgets.*']);
Log::debug(sprintf('Found %d available budgets between %s and %s', $result->count(), $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
return $query->get(['available_budgets.*']);
return $result;
}
/**
@@ -131,8 +133,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
Log::debug(sprintf('Now in %s(%s, %s)', __METHOD__, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$return = [];
$availableBudgets = $this->user->availableBudgets()
->where('start_date', $start->format('Y-m-d H:i:s'))
->where('end_date', $end->format('Y-m-d H:i:s'))->get()
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->get()
;
Log::debug(sprintf('Found %d available budgets', $availableBudgets->count()));
@@ -214,9 +216,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$availableBudget = new AvailableBudget();
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->start_date = $start->startOfDay();
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
$availableBudget->end_date = $end->endOfDay();
$availableBudget->end_date_tz = $end->format('e');
}
$availableBudget->amount = $amount;
@@ -249,9 +251,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $data['currency_id'],
'amount' => $data['amount'],
'start_date' => $start->format('Y-m-d'),
'start_date' => $start,
'start_date_tz' => $start->format('e'),
'end_date' => $end->format('Y-m-d'),
'end_date' => $end,
'end_date_tz' => $end->format('e'),
]
);
@@ -273,7 +275,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$start = $data['start'];
if ($start instanceof Carbon) {
$start = $data['start']->startOfDay();
$availableBudget->start_date = $start->format('Y-m-d');
$availableBudget->start_date = $start;
$availableBudget->start_date_tz = $start->format('e');
$availableBudget->save();
}
@@ -283,7 +285,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$end = $data['end'];
if ($end instanceof Carbon) {
$end = $data['end']->endOfDay();
$availableBudget->end_date = $end->format('Y-m-d');
$availableBudget->end_date = $end;
$availableBudget->end_date_tz = $end->format('e');
$availableBudget->save();
}

View File

@@ -287,7 +287,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$amount = '' === $amount ? '0' : $amount;
$sum = bcadd($sum, $amount);
}
Log::debug(sprintf('Current amount in piggy bank #%d ("%s") is %s', $piggyBank->id, $piggyBank->name, $sum));
// Log::debug(sprintf('Current amount in piggy bank #%d ("%s") is %s', $piggyBank->id, $piggyBank->name, $sum));
return $sum;
}

View File

@@ -159,7 +159,7 @@ class CreditRecalculateService
private function processWorkAccount(Account $account): void
{
Log::debug(sprintf('Now processing account #%d ("%s"). All amounts with 2 decimals!', $account->id, $account->name));
Log::debug(sprintf('Now processing account #%d ("%s").', $account->id, $account->name));
// get opening balance (if present)
$this->repository->setUser($account->user);
$direction = (string) $this->repository->getMetaValue($account, 'liability_direction');
@@ -230,7 +230,7 @@ class CreditRecalculateService
return;
}
Log::debug('Opening balance is valid');
// Log::debug('Opening balance is valid');
}
/**
@@ -263,7 +263,7 @@ class CreditRecalculateService
return $leftOfDebt;
}
if (TransactionTypeEnum::LIABILITY_CREDIT->value === $type || TransactionTypeEnum::OPENING_BALANCE->value === $type) {
Log::warning(sprintf('Transaction type is "%s", so do nothing.', $type));
// Log::warning(sprintf('Transaction type is "%s", so do nothing.', $type));
return $leftOfDebt;
}

View File

@@ -210,6 +210,10 @@ class Preferences
try {
$result->data = decrypt($result->data);
} catch (DecryptException $e) {
if ('The MAC is invalid.' === $e->getMessage()) {
Log::debug('Set data to NULL');
$result->data = null;
}
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result;
@@ -230,6 +234,10 @@ class Preferences
try {
$result->data = decrypt($result->data);
} catch (DecryptException $e) {
if ('The MAC is invalid.' === $e->getMessage()) {
Log::debug('Set data to NULL');
$result->data = null;
}
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result;

View File

@@ -97,7 +97,7 @@ class TransactionSummarizer
'currency_decimal_places' => $currencyDecimalPlaces,
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->{$method}($amount));
Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
// Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
}
Log::debug('End of sumExpenses.', $array);

View File

@@ -310,7 +310,15 @@ class Steam
*/
public function finalAccountBalance(Account $account, Carbon $date): array
{
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
Log::debug(sprintf('Now in finalAccountBalance(#%d, "%s", "%s")', $account->id, $account->name, $date->format('Y-m-d H:i:s')));
$native = Amount::getDefaultCurrencyByUserGroup($account->user->userGroup);
$convertToNative = Amount::convertToNative($account->user);
$accountCurrency = $this->getAccountCurrency($account);
@@ -331,7 +339,7 @@ class Steam
if ($native->id === $accountCurrency?->id) {
$return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']);
}
Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $this->bcround($return['balance'], 2)));
// Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $this->bcround($return['balance'], 2)));
// native balance
$return['native_balance'] = (string) $account->transactions()
@@ -342,7 +350,7 @@ class Steam
;
// plus native virtual balance.
$return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']);
Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $this->bcround($return['native_balance'])));
// Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $this->bcround($return['native_balance'])));
// plus foreign transactions in THIS currency.
$sum = (string) $account->transactions()
@@ -354,7 +362,7 @@ class Steam
;
$return['native_balance'] = bcadd($return['native_balance'], $sum);
Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $this->bcround($sum), $this->bcround($return['native_balance'])));
// Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $this->bcround($sum), $this->bcround($return['native_balance'])));
}
// balance(s) in other (all) currencies.
@@ -365,12 +373,12 @@ class Steam
->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
;
$others = $this->groupAndSumTransactions($array, 'code', 'amount');
Log::debug('All balances are (joined)', $others);
// Log::debug('All balances are (joined)', $others);
// if the account has no own currency preference, drop balance in favor of native balance
if ($hasCurrency && !$convertToNative) {
$return['balance'] = $others[$currency->code] ?? '0';
$return['native_balance'] = $others[$currency->code] ?? '0';
Log::debug(sprintf('Set balance + native_balance to %s', $return['balance']));
// Log::debug(sprintf('Set balance + native_balance to %s', $return['balance']));
}
// if the currency is the same as the native currency, set the native_balance to the balance for consistency.
@@ -379,16 +387,18 @@ class Steam
// }
if (!$hasCurrency && array_key_exists('balance', $return) && array_key_exists('native_balance', $return)) {
Log::debug('Account has no currency preference, dropping balance in favor of native balance.');
// Log::debug('Account has no currency preference, dropping balance in favor of native balance.');
$sum = bcadd($return['balance'], $return['native_balance']);
Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum));
// Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum));
$return['native_balance'] = $sum;
unset($return['balance']);
}
$final = array_merge($return, $others);
Log::debug('Return is', $final);
// Log::debug('Return is', $final);
$cache->store($final);
return $final;
return array_merge($return, $others);
// Log::debug('Return is', $final);
}
public function filterAccountBalances(array $total, Account $account, bool $convertToNative, ?TransactionCurrency $currency = null): array

View File

@@ -26,13 +26,14 @@ namespace FireflyIII\TransactionRules\Actions;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Facades\Log;
/**
* Class UpdatePiggybank
@@ -53,7 +54,7 @@ class UpdatePiggybank implements ActionInterface
{
$actionValue = $this->action->getValue($journal);
app('log')->debug(sprintf('Triggered rule action UpdatePiggybank on journal #%d', $journal['transaction_journal_id']));
Log::debug(sprintf('Triggered rule action UpdatePiggybank on journal #%d', $journal['transaction_journal_id']));
// refresh the transaction type.
/** @var User $user */
@@ -64,7 +65,7 @@ class UpdatePiggybank implements ActionInterface
$piggyBank = $this->findPiggyBank($user, $actionValue);
if (null === $piggyBank) {
app('log')->info(
Log::info(
sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)
);
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue])));
@@ -72,20 +73,19 @@ class UpdatePiggybank implements ActionInterface
return false;
}
app('log')->debug(sprintf('Found piggy bank #%d ("%s")', $piggyBank->id, $piggyBank->name));
/** @var Transaction $source */
$source = $journalObj->transactions()->where('amount', '<', 0)->first();
Log::debug(sprintf('Found piggy bank #%d ("%s")', $piggyBank->id, $piggyBank->name));
/** @var Transaction $destination */
$destination = $journalObj->transactions()->where('amount', '>', 0)->first();
if ($source->account_id === $piggyBank->account_id) {
app('log')->debug('Piggy bank account is linked to source, so remove amount from piggy bank.');
throw new FireflyException('Reference the correct account here.');
$this->removeAmount($piggyBank, $journal, $journalObj, $destination->amount);
$accounts = $this->getAccounts($journalObj);
Log::debug(sprintf('Source account is #%d: "%s"', $accounts['source']->id, $accounts['source']->name));
Log::debug(sprintf('Destination account is #%d: "%s"', $accounts['destination']->id, $accounts['source']->name));
// if connected to source but not to destination, needs to be removed from source account connected to piggy bank.
if ($this->isConnected($piggyBank, $accounts['source']) && !$this->isConnected($piggyBank, $accounts['destination'])) {
Log::debug('Piggy bank account is linked to source, so remove amount from piggy bank.');
$this->removeAmount($piggyBank, $journal, $journalObj, $accounts['source'], $destination->amount);
event(
new TriggeredAuditLog(
$this->action->rule,
@@ -103,9 +103,11 @@ class UpdatePiggybank implements ActionInterface
return true;
}
if ($destination->account_id === $piggyBank->account_id) {
app('log')->debug('Piggy bank account is linked to source, so add amount to piggy bank.');
$this->addAmount($piggyBank, $journal, $journalObj, $destination->amount);
// if connected to destination but not to source, needs to be removed from source account connected to piggy bank.
if (!$this->isConnected($piggyBank, $accounts['source']) && $this->isConnected($piggyBank, $accounts['destination'])) {
Log::debug('Piggy bank account is linked to source, so add amount to piggy bank.');
$this->addAmount($piggyBank, $journal, $journalObj, $accounts['destination'], $destination->amount);
event(
new TriggeredAuditLog(
@@ -125,13 +127,13 @@ class UpdatePiggybank implements ActionInterface
return true;
}
app('log')->info(
sprintf(
'Piggy bank is not linked to source ("#%d") or destination ("#%d"), so no action will be taken.',
$source->account_id,
$destination->account_id
)
);
if ($this->isConnected($piggyBank, $accounts['source']) && $this->isConnected($piggyBank, $accounts['destination'])) {
Log::info(sprintf('Piggy bank is linked to BOTH source ("#%d") and destination ("#%d"), so no action will be taken.', $accounts['source']->id, $accounts['destination']->id));
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_link_piggy', ['name' => $actionValue])));
return false;
}
Log::info(sprintf('Piggy bank is not linked to source ("#%d") or destination ("#%d"), so no action will be taken.', $accounts['source']->id, $accounts['destination']->id));
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_link_piggy', ['name' => $actionValue])));
return false;
@@ -139,80 +141,102 @@ class UpdatePiggybank implements ActionInterface
private function findPiggyBank(User $user, string $name): ?PiggyBank
{
return $user->piggyBanks()->where('piggy_banks.name', $name)->first();
/** @var PiggyBankRepositoryInterface $repository */
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($user);
return $repository->findByName($name);
}
private function removeAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
private function removeAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, Account $account, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($journal->user);
// how much can we remove from this piggy bank?
$toRemove = $repository->getCurrentAmount($piggyBank);
app('log')->debug(sprintf('Amount is %s, max to remove is %s', $amount, $toRemove));
$toRemove = $repository->getCurrentAmount($piggyBank, $account);
Log::debug(sprintf('Amount is %s, max to remove is %s', $amount, $toRemove));
// if $amount is bigger than $toRemove, shrink it.
$amount = -1 === bccomp($amount, $toRemove) ? $amount : $toRemove;
app('log')->debug(sprintf('Amount is now %s', $amount));
Log::debug(sprintf('Amount is now %s', $amount));
// if amount is zero, stop.
if (0 === bccomp('0', $amount)) {
app('log')->warning('Amount left is zero, stop.');
Log::warning('Amount left is zero, stop.');
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_zero_piggy', ['name' => $piggyBank->name])));
return;
}
// make sure we can remove amount:
throw new FireflyException('Reference the correct account here.');
if (false === $repository->canRemoveAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot remove %s from piggy bank.', $amount));
if (false === $repository->canRemoveAmount($piggyBank, $account, $amount)) {
Log::warning(sprintf('Cannot remove %s from piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_remove_from_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
return;
}
app('log')->debug(sprintf('Will now remove %s from piggy bank.', $amount));
Log::debug(sprintf('Will now remove %s from piggy bank.', $amount));
throw new FireflyException('Reference the correct account here.');
$repository->removeAmount($piggyBank, $amount, $journal);
$repository->removeAmount($piggyBank, $account, $amount, $journal);
}
private function addAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, string $amount): void
private function addAmount(PiggyBank $piggyBank, array $array, TransactionJournal $journal, Account $account, string $amount): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($journal->user);
// how much can we add to the piggy bank?
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$toAdd = bcsub($piggyBank->target_amount, $repository->getCurrentAmount($piggyBank));
app('log')->debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount));
$toAdd = bcsub($piggyBank->target_amount, $repository->getCurrentAmount($piggyBank, $account));
Log::debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount));
// update amount to fit:
$amount = -1 === bccomp($amount, $toAdd) ? $amount : $toAdd;
app('log')->debug(sprintf('Amount is now %s', $amount));
Log::debug(sprintf('Amount is now %s', $amount));
}
if (0 === bccomp($piggyBank->target_amount, '0')) {
app('log')->debug('Target amount is zero, can add anything.');
Log::debug('Target amount is zero, can add anything.');
}
// if amount is zero, stop.
if (0 === bccomp('0', $amount)) {
app('log')->warning('Amount left is zero, stop.');
Log::warning('Amount left is zero, stop.');
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_zero_piggy', ['name' => $piggyBank->name])));
return;
}
// make sure we can add amount:
throw new FireflyException('Reference the correct account here.');
if (false === $repository->canAddAmount($piggyBank, $amount)) {
app('log')->warning(sprintf('Cannot add %s to piggy bank.', $amount));
if (false === $repository->canAddAmount($piggyBank, $account, $amount)) {
Log::warning(sprintf('Cannot add %s to piggy bank.', $amount));
event(new RuleActionFailedOnArray($this->action, $array, trans('rules.cannot_add_to_piggy', ['amount' => $amount, 'name' => $piggyBank->name])));
return;
}
app('log')->debug(sprintf('Will now add %s to piggy bank.', $amount));
Log::debug(sprintf('Will now add %s to piggy bank.', $amount));
$repository->addAmount($piggyBank, $amount, $journal);
$repository->addAmount($piggyBank, $account, $amount, $journal);
}
private function getAccounts(TransactionJournal $journal): array
{
return [
'source' => $journal->transactions()->where('amount', '<', '0')->first()?->account,
'destination' => $journal->transactions()->where('amount', '>', '0')->first()?->account,
];
}
private function isConnected(PiggyBank $piggyBank, ?Account $link): bool
{
if (null === $link) {
return false;
}
foreach ($piggyBank->accounts as $account) {
if ($account->id === $link->id) {
return true;
}
}
Log::debug(sprintf('Piggy bank is not connected to account #%d "%s"', $account->id, $account->name));
return false;
}
}

110
composer.lock generated
View File

@@ -6364,16 +6364,16 @@
},
{
"name": "spatie/laravel-package-tools",
"version": "1.17.0",
"version": "1.18.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-package-tools.git",
"reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85"
"reference": "8332205b90d17164913244f4a8e13ab7e6761d29"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/9ab30fd24f677e5aa370ea4cf6b41c517d16cf85",
"reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/8332205b90d17164913244f4a8e13ab7e6761d29",
"reference": "8332205b90d17164913244f4a8e13ab7e6761d29",
"shasum": ""
},
"require": {
@@ -6412,7 +6412,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-package-tools/issues",
"source": "https://github.com/spatie/laravel-package-tools/tree/1.17.0"
"source": "https://github.com/spatie/laravel-package-tools/tree/1.18.0"
},
"funding": [
{
@@ -6420,7 +6420,7 @@
"type": "github"
}
],
"time": "2024-12-09T16:29:14+00:00"
"time": "2024-12-30T13:13:39+00:00"
},
{
"name": "spatie/period",
@@ -7246,16 +7246,16 @@
},
{
"name": "symfony/finder",
"version": "v7.2.0",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49"
"reference": "87a71856f2f56e4100373e92529eed3171695cfb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49",
"reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49",
"url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
"reference": "87a71856f2f56e4100373e92529eed3171695cfb",
"shasum": ""
},
"require": {
@@ -7290,7 +7290,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.2.0"
"source": "https://github.com/symfony/finder/tree/v7.2.2"
},
"funding": [
{
@@ -7306,20 +7306,20 @@
"type": "tidelift"
}
],
"time": "2024-10-23T06:56:12+00:00"
"time": "2024-12-30T19:00:17+00:00"
},
{
"name": "symfony/http-client",
"version": "v7.2.1",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e"
"reference": "339ba21476eb184290361542f732ad12c97591ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e",
"reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e",
"url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec",
"reference": "339ba21476eb184290361542f732ad12c97591ec",
"shasum": ""
},
"require": {
@@ -7385,7 +7385,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.2.1"
"source": "https://github.com/symfony/http-client/tree/v7.2.2"
},
"funding": [
{
@@ -7401,7 +7401,7 @@
"type": "tidelift"
}
],
"time": "2024-12-07T08:50:44+00:00"
"time": "2024-12-30T18:35:15+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -7483,16 +7483,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.2.0",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744"
"reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744",
"reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588",
"reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588",
"shasum": ""
},
"require": {
@@ -7541,7 +7541,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.2.0"
"source": "https://github.com/symfony/http-foundation/tree/v7.2.2"
},
"funding": [
{
@@ -7557,20 +7557,20 @@
"type": "tidelift"
}
],
"time": "2024-11-13T18:58:46+00:00"
"time": "2024-12-30T19:00:17+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.2.1",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97"
"reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97",
"reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/3c432966bd8c7ec7429663105f5a02d7e75b4306",
"reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306",
"shasum": ""
},
"require": {
@@ -7655,7 +7655,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.2.1"
"source": "https://github.com/symfony/http-kernel/tree/v7.2.2"
},
"funding": [
{
@@ -7671,7 +7671,7 @@
"type": "tidelift"
}
],
"time": "2024-12-11T12:09:10+00:00"
"time": "2024-12-31T14:59:40+00:00"
},
{
"name": "symfony/mailer",
@@ -9082,16 +9082,16 @@
},
{
"name": "symfony/translation",
"version": "v7.2.0",
"version": "v7.2.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5"
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5",
"reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5",
"url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923",
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923",
"shasum": ""
},
"require": {
@@ -9157,7 +9157,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v7.2.0"
"source": "https://github.com/symfony/translation/tree/v7.2.2"
},
"funding": [
{
@@ -9173,7 +9173,7 @@
"type": "tidelift"
}
],
"time": "2024-11-12T20:47:56+00:00"
"time": "2024-12-07T08:18:10+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -10287,20 +10287,20 @@
},
{
"name": "barryvdh/reflection-docblock",
"version": "v2.2.0",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
"reference": "db125e8df4329bd45f2da405aab007f502f38531"
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/db125e8df4329bd45f2da405aab007f502f38531",
"reference": "db125e8df4329bd45f2da405aab007f502f38531",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/818be8de6af4d16ef3ad51ea9234b3d37026ee5f",
"reference": "818be8de6af4d16ef3ad51ea9234b3d37026ee5f",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5.14|^9"
@@ -10312,7 +10312,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
"dev-master": "2.3.x-dev"
}
},
"autoload": {
@@ -10333,9 +10333,9 @@
}
],
"support": {
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.2.0"
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.3.0"
},
"time": "2024-12-28T10:00:03+00:00"
"time": "2024-12-30T10:35:04+00:00"
},
{
"name": "cloudcreativity/json-api-testing",
@@ -11147,16 +11147,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.3.1",
"version": "v5.4.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
"shasum": ""
},
"require": {
@@ -11199,9 +11199,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
},
"time": "2024-10-08T18:51:32+00:00"
"time": "2024-12-30T11:07:19+00:00"
},
{
"name": "phar-io/manifest",
@@ -11616,16 +11616,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.12.13",
"version": "1.12.14",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "9b469068840cfa031e1deaf2fa1886d00e20680f"
"reference": "e73868f809e68fff33be961ad4946e2e43ec9e38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b469068840cfa031e1deaf2fa1886d00e20680f",
"reference": "9b469068840cfa031e1deaf2fa1886d00e20680f",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e73868f809e68fff33be961ad4946e2e43ec9e38",
"reference": "e73868f809e68fff33be961ad4946e2e43ec9e38",
"shasum": ""
},
"require": {
@@ -11670,7 +11670,7 @@
"type": "github"
}
],
"time": "2024-12-17T17:00:20+00:00"
"time": "2024-12-31T07:26:13+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",

View File

@@ -28,47 +28,48 @@ return [
'download_enabled' => env('ENABLE_EXTERNAL_RATES', false),
// if currencies are added, default rates must be added as well!
// last exchange rate update: 6-6-2022
// last exchange rate update: 2024-12-30
// source: https://www.xe.com/currencyconverter/
'date' => '2022-06-06',
'date' => '2024-12-30',
// all rates are from EUR to $currency:
'rates' => [
// europa
'EUR' => 1,
'HUF' => 387.9629,
'GBP' => 0.85420754,
'UAH' => 31.659752,
'PLN' => 4.581788,
'TRY' => 17.801397,
'DKK' => 7.4389753,
'HUF' => 410.79798,
'GBP' => 0.82858703,
'UAH' => 43.485934,
'PLN' => 4.2708542,
'TRY' => 36.804124,
'DKK' => 7.4591,
'RON' => 4.9768699,
// Americas
'USD' => 1.0722281,
'BRL' => 5.0973173,
'CAD' => 1.3459969,
'MXN' => 20.899824,
'USD' => 1.0430046,
'BRL' => 6.4639113,
'CAD' => 1.5006908,
'MXN' => 21.249542,
// Oceania currencies
'IDR' => 15466.299,
'AUD' => 1.4838549,
'NZD' => 1.6425829,
'IDR' => 16860.057,
'AUD' => 1.6705648,
'NZD' => 1.8436945,
// africa
'EGP' => 19.99735,
'MAD' => 10.573307,
'ZAR' => 16.413167,
'EGP' => 53.038174,
'MAD' => 10.521629,
'ZAR' => 19.460263,
// asia
'JPY' => 140.15257,
'RMB' => 7.1194265,
'CNY' => 1,
'RUB' => 66.000895,
'INR' => 83.220481,
'JPY' => 164.74767,
'RMB' => 7.6138994,
'CNY' => 7.6138994,
'RUB' => 108.56771,
'INR' => 89.157391,
// int
'ILS' => 3.5712508,
'CHF' => 1.0323891,
'HRK' => 7.5220845,
'ILS' => 3.8428028,
'CHF' => 0.94044969,
'HRK' => 7.5345, // replaced by EUR
],
];

View File

@@ -81,7 +81,7 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-12-30',
'version' => 'develop/2025-01-01',
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25,

18
package-lock.json generated
View File

@@ -3007,9 +3007,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz",
"integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.3.tgz",
"integrity": "sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3133,9 +3133,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"version": "22.10.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz",
"integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12114,9 +12114,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
"dev": true,
"license": "ISC",
"bin": {

View File

@@ -36,7 +36,7 @@
"is_reconciled_fields_dropped": "Da diese Buchung abgeglichen ist, k\u00f6nnen Sie weder die Konten noch den\/die Betrag\/Betr\u00e4ge aktualisieren.",
"tags": "Schlagw\u00f6rter",
"no_budget": "(kein Budget)",
"no_bill": "(kein Abonnement)",
"no_bill": "(no subscription)",
"category": "Kategorie",
"attachments": "Anh\u00e4nge",
"notes": "Notizen",
@@ -52,7 +52,7 @@
"destination_account_reconciliation": "Sie k\u00f6nnen das Zielkonto einer Kontenausgleichsbuchung nicht bearbeiten.",
"source_account_reconciliation": "Sie k\u00f6nnen das Quellkonto einer Kontenausgleichsbuchung nicht bearbeiten.",
"budget": "Budget",
"bill": "Abonnement",
"bill": "Subscription",
"you_create_withdrawal": "Sie haben eine Ausgabe erstellt.",
"you_create_transfer": "Sie erstellen eine Umbuchung.",
"you_create_deposit": "Sie haben eine Einnahme erstellt.",
@@ -130,15 +130,15 @@
"response": "Antwort",
"visit_webhook_url": "Webhook-URL besuchen",
"reset_webhook_secret": "Webhook Secret zur\u00fccksetzen",
"header_exchange_rates": "Wechselkurse",
"exchange_rates_intro": "Firefly III unterst\u00fctzt das Herunterladen und Verwenden von Wechselkursen. Lesen Sie mehr dar\u00fcber in <a href=\u201ehttps:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\u201c>der Dokumentation<\/a>.",
"exchange_rates_from_to": "Zwischen {from} und {to} (und umgekehrt)",
"exchange_rates_intro_rates": "Firefly III verwendet die folgenden Wechselkurse. Der Kehrwert wird automatisch berechnet, wenn er nicht angegeben wurde. Wenn f\u00fcr das Datum der Transaktion kein Wechselkurs vorhanden ist, sucht Firefly III in der Vergangenheit nach einem Kurs. Wenn keine vorhanden sind, wird der Kurs \u201e1\u201c verwendet.",
"header_exchange_rates_rates": "Wechselkurse",
"header_exchange_rates_table": "Tabelle mit Wechselkursen",
"help_rate_form": "An diesem Tag, wie viele {to} werden Sie f\u00fcr {from} bekommen?",
"add_new_rate": "Neuen Wechselkurs hinzuf\u00fcgen",
"save_new_rate": "Neuen Kurs speichern"
"header_exchange_rates": "Exchange rates",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">the documentation<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
"header_exchange_rates_rates": "Exchange rates",
"header_exchange_rates_table": "Table with exchange rates",
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate"
},
"form": {
"url": "URL",

View File

@@ -36,7 +36,7 @@
"is_reconciled_fields_dropped": "Omdat deze transactie al is afgestemd, kan je het bedrag noch de rekeningen wijzigen.",
"tags": "Tags",
"no_budget": "(geen budget)",
"no_bill": "(geen abonnement)",
"no_bill": "(no subscription)",
"category": "Categorie",
"attachments": "Bijlagen",
"notes": "Notities",
@@ -52,7 +52,7 @@
"destination_account_reconciliation": "Je kan de doelrekening van een afstemming niet wijzigen.",
"source_account_reconciliation": "Je kan de bronrekening van een afstemming niet wijzigen.",
"budget": "Budget",
"bill": "Abonnement",
"bill": "Subscription",
"you_create_withdrawal": "Je maakt een uitgave.",
"you_create_transfer": "Je maakt een overschrijving.",
"you_create_deposit": "Je maakt inkomsten.",
@@ -130,15 +130,15 @@
"response": "Reactie",
"visit_webhook_url": "Bezoek URL van webhook",
"reset_webhook_secret": "Reset webhook-geheim",
"header_exchange_rates": "Wisselkoersen",
"exchange_rates_intro": "Firefly III kan wisselkoersen downloaden en gebruiken. Lees hier meer over in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">de documentatie<\/a>.",
"exchange_rates_from_to": "Tussen {from} en {to} (en andersom)",
"exchange_rates_intro_rates": "Firefly III gebruikt de volgende wisselkoersen. De inverse berekent zichzelf als deze niet is opgegeven. Als er geen wisselkoers bestaat voor de datum van de transactie, gaat Firefly III terug in de tijd om er een te vinden. Als er geen aanwezig is, zal de koers \"1\" gebruikt worden.",
"header_exchange_rates_rates": "Wisselkoersen",
"header_exchange_rates_table": "Tabel met wisselkoersen",
"help_rate_form": "Hoeveel {to} krijg je op deze dag voor \u00e9\u00e9n {from}?",
"add_new_rate": "Nieuwe wisselkoers toevoegen",
"save_new_rate": "Nieuwe wisselkoers opslaan"
"header_exchange_rates": "Exchange rates",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">the documentation<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
"header_exchange_rates_rates": "Exchange rates",
"header_exchange_rates_table": "Table with exchange rates",
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate"
},
"form": {
"url": "URL",
@@ -158,7 +158,7 @@
"webhook_delivery": "Bericht",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Wisselkoers"
"rate": "Rate"
},
"list": {
"active": "Actief?",

View File

@@ -21,7 +21,7 @@
"apply_rules_checkbox": "Uporabite pravila",
"fire_webhooks_checkbox": "Spro\u017eite Webhooke",
"no_budget_pointer": "Zdi se, da \u0161e nimate prora\u010duna. Ustvarite jih nekaj na strani <a href=\"budgets\">prora\u010duni<\/a>. Prora\u010duni vam lahko pomagajo spremljati stro\u0161ke.",
"no_bill_pointer": "You seem to have no subscription yet. You should create some on the <a href=\"subscriptions\">subscription<\/a>-page. Subscriptions can help you keep track of expenses.",
"no_bill_pointer": "Zdi se, da \u0161e nimate naro\u010dnine. Ustvarite jih na strani za <a href=\"subscriptions\">naro\u010dnine<\/a>. Naro\u010dnine vam lahko pomagajo spremljati stro\u0161ke.",
"source_account": "Izvorni ra\u010dun",
"hidden_fields_preferences": "Ve\u010d mo\u017enosti transakcije lahko omogo\u010dite v <a href=\"preferences\">nastavitvah<\/a>.",
"destination_account": "Ciljni ra\u010dun",
@@ -36,7 +36,7 @@
"is_reconciled_fields_dropped": "Ker je ta transakcija usklajena, ne boste mogli posodobiti ra\u010dunov niti zneskov.",
"tags": "Oznake",
"no_budget": "(brez prora\u010duna)",
"no_bill": "(no subscription)",
"no_bill": "(brez naro\u010dnin)",
"category": "Kategorija",
"attachments": "Priloge",
"notes": "Opombe",
@@ -52,7 +52,7 @@
"destination_account_reconciliation": "Pri usklajevalni transakciji ni mo\u017eno urejati ciljnega ra\u010duna.",
"source_account_reconciliation": "Pri usklajevalni transakciji ni mo\u017eno urejati izvornega ra\u010duna.",
"budget": "Prora\u010dun",
"bill": "Subscription",
"bill": "Naro\u010dnina",
"you_create_withdrawal": "Ustvarjate odliv.",
"you_create_transfer": "Ustvarjate prenos.",
"you_create_deposit": "Ustvarja\u0161 priliv.",
@@ -130,15 +130,15 @@
"response": "Odziv",
"visit_webhook_url": "Obi\u0161\u010dite URL webhooka",
"reset_webhook_secret": "Ponastavi skrivno kodo webhooka",
"header_exchange_rates": "Exchange rates",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">the documentation<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
"header_exchange_rates_rates": "Exchange rates",
"header_exchange_rates_table": "Table with exchange rates",
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate"
"header_exchange_rates": "Menjalni te\u010daji",
"exchange_rates_intro": "Firefly III podpira prenos in uporabo menjalnih te\u010dajev. Preberite ve\u010d o tem v <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">dokumentaciji<\/a>.",
"exchange_rates_from_to": "Med {from} in {to} (in obratno)",
"exchange_rates_intro_rates": "Firefly III uporablja naslednje menjalne te\u010daje. Obratna vrednost se samodejno izra\u010duna, \u010de ni na voljo. \u010ce na dan transakcije ni menjalnega te\u010daja, se bo Firefly III vrnil v preteklost, da bi ga na\u0161el. \u010ce jih ni, se uporabi te\u010daj \"1\".",
"header_exchange_rates_rates": "Menjalni te\u010daji",
"header_exchange_rates_table": "Tabela s te\u010daji",
"help_rate_form": "Na ta dan, koliko {to} boste dobili za enega {from}?",
"add_new_rate": "Dodaj nov menjalni te\u010daj",
"save_new_rate": "Shrani nov te\u010daj"
},
"form": {
"url": "URL",
@@ -158,7 +158,7 @@
"webhook_delivery": "Dostava",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Rate"
"rate": "Te\u010daj"
},
"list": {
"active": "Aktiviran?",

View File

@@ -217,9 +217,9 @@ return [
'button_register' => 'Register',
'authorization' => 'Authorization',
'active_bills_only' => 'active subscription only',
'active_bills_only_total' => 'all active subscription',
'active_exp_bills_only' => 'active and expected subscription only',
'active_exp_bills_only_total' => 'all active expected subscription only',
'active_bills_only_total' => 'all active subscriptions',
'active_exp_bills_only' => 'active and expected subscriptions only',
'active_exp_bills_only_total' => 'all active expected subscriptions only',
'per_period_sum_1D' => 'Expected daily costs',
'per_period_sum_1W' => 'Expected weekly costs',
'per_period_sum_1M' => 'Expected monthly costs',
@@ -1328,7 +1328,7 @@ return [
'rule_for_bill_title' => 'Auto-generated rule for subscription ":name"',
'rule_for_bill_description' => 'This rule is auto-generated to try to match subscription ":name".',
'create_rule_for_bill' => 'Create a new rule for subscription ":name"',
'create_rule_for_bill_txt' => 'You have just created a new subscription called ":name", congratulations!Firefly III can automagically match new withdrawals to this subscription. For example, whenever you pay your rent, the subscription "rent" will be linked to the expense. This way, Firefly III can accurately show you which subscriptions are due and which ones aren\'t. In order to do so, a new rule must be created. Firefly III has filled in some sensible defaults for you. Please make sure these are correct. If these values are correct, Firefly III will automatically link the correct withdrawal to the correct subscription. Please check out the triggers to see if they are correct, and add some if they\'re wrong.',
'create_rule_for_bill_txt' => 'You have just created a new subscription called ":name", congratulations! Firefly III can automagically match new withdrawals to this subscription. For example, whenever you pay your rent, the subscription "rent" will be linked to the expense. This way, Firefly III can accurately show you which subscriptions are due and which ones aren\'t. In order to do so, a new rule must be created. Firefly III has filled in some sensible defaults for you. Please make sure these are correct. If these values are correct, Firefly III will automatically link the correct withdrawal to the correct subscription. Please check out the triggers to see if they are correct, and add some if they\'re wrong.',
'new_rule_for_bill_title' => 'Rule for subscription ":name"',
'new_rule_for_bill_description' => 'This rule marks transactions for subscription ":name".',

View File

@@ -23,56 +23,57 @@
declare(strict_types=1);
return [
'main_message' => 'Action ":action", present in rule ":rule", could not be applied to transaction #:group: :error',
'find_or_create_tag_failed' => 'Could not find or create tag ":tag"',
'tag_already_added' => 'Tag ":tag" is already linked to this transaction',
'inspect_transaction' => 'Inspect transaction ":title" @ Firefly III',
'inspect_rule' => 'Inspect rule ":title" @ Firefly III',
'journal_other_user' => 'This transaction doesn\'t belong to the user',
'no_such_journal' => 'This transaction doesn\'t exist',
'journal_already_no_budget' => 'This transaction has no budget, so it cannot be removed',
'journal_already_no_category' => 'This transaction had no category, so it cannot be removed',
'journal_already_no_notes' => 'This transaction had no notes, so they cannot be removed',
'journal_not_found' => 'Firefly III can\'t find the requested transaction',
'split_group' => 'Firefly III cannot execute this action on a transaction with multiple splits',
'is_already_withdrawal' => 'This transaction is already a withdrawal',
'is_already_deposit' => 'This transaction is already a deposit',
'is_already_transfer' => 'This transaction is already a transfer',
'is_not_transfer' => 'This transaction is not a transfer',
'complex_error' => 'Something complicated went wrong. Sorry about that. Please inspect the logs of Firefly III',
'no_valid_opposing' => 'Conversion failed because there is no valid account named ":account"',
'new_notes_empty' => 'The notes to be set are empty',
'unsupported_transaction_type_withdrawal' => 'Firefly III cannot convert a ":type" to a withdrawal',
'unsupported_transaction_type_deposit' => 'Firefly III cannot convert a ":type" to a deposit',
'unsupported_transaction_type_transfer' => 'Firefly III cannot convert a ":type" to a transfer',
'already_has_source_asset' => 'This transaction already has ":name" as the source asset account',
'already_has_destination_asset' => 'This transaction already has ":name" as the destination asset account',
'already_has_destination' => 'This transaction already has ":name" as the destination account',
'already_has_source' => 'This transaction already has ":name" as the source account',
'already_linked_to_subscription' => 'The transaction is already linked to subscription ":name"',
'already_linked_to_category' => 'The transaction is already linked to category ":name"',
'already_linked_to_budget' => 'The transaction is already linked to budget ":name"',
'cannot_find_subscription' => 'Firefly III can\'t find subscription ":name"',
'no_notes_to_move' => 'The transaction has no notes to move to the description field',
'no_tags_to_remove' => 'The transaction has no tags to remove',
'not_withdrawal' => 'The transaction is not a withdrawal',
'not_deposit' => 'The transaction is not a deposit',
'cannot_find_tag' => 'Firefly III can\'t find tag ":tag"',
'cannot_find_asset' => 'Firefly III can\'t find asset account ":name"',
'cannot_find_accounts' => 'Firefly III can\'t find the source or destination account',
'cannot_find_source_transaction' => 'Firefly III can\'t find the source transaction',
'cannot_find_destination_transaction' => 'Firefly III can\'t find the destination transaction',
'cannot_find_source_transaction_account' => 'Firefly III can\'t find the source transaction account',
'cannot_find_destination_transaction_account' => 'Firefly III can\'t find the destination transaction account',
'cannot_find_piggy' => 'Firefly III can\'t find a piggy bank named ":name"',
'no_link_piggy' => 'This transaction\'s accounts are not linked to the piggy bank, so no action will be taken',
'cannot_unlink_tag' => 'Tag ":tag" isn\'t linked to this transaction',
'cannot_find_budget' => 'Firefly III can\'t find budget ":name"',
'cannot_find_category' => 'Firefly III can\'t find category ":name"',
'cannot_set_budget' => 'Firefly III can\'t set budget ":name" to a transaction of type ":type"',
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.',
'cannot_remove_zero_piggy' => 'Cannot remove zero amount from piggy bank ":name"',
'cannot_remove_from_piggy' => 'Cannot remove ":amount" from piggy bank ":name"',
'cannot_add_zero_piggy' => 'Cannot add zero amount to piggy bank ":name"',
'cannot_add_to_piggy' => 'Cannot add ":amount" to piggy bank ":name"',
'main_message' => 'Action ":action", present in rule ":rule", could not be applied to transaction #:group: :error',
'find_or_create_tag_failed' => 'Could not find or create tag ":tag"',
'tag_already_added' => 'Tag ":tag" is already linked to this transaction',
'inspect_transaction' => 'Inspect transaction ":title" @ Firefly III',
'inspect_rule' => 'Inspect rule ":title" @ Firefly III',
'journal_other_user' => 'This transaction doesn\'t belong to the user',
'no_such_journal' => 'This transaction doesn\'t exist',
'journal_already_no_budget' => 'This transaction has no budget, so it cannot be removed',
'journal_already_no_category' => 'This transaction had no category, so it cannot be removed',
'journal_already_no_notes' => 'This transaction had no notes, so they cannot be removed',
'journal_not_found' => 'Firefly III can\'t find the requested transaction',
'split_group' => 'Firefly III cannot execute this action on a transaction with multiple splits',
'is_already_withdrawal' => 'This transaction is already a withdrawal',
'is_already_deposit' => 'This transaction is already a deposit',
'is_already_transfer' => 'This transaction is already a transfer',
'is_not_transfer' => 'This transaction is not a transfer',
'complex_error' => 'Something complicated went wrong. Sorry about that. Please inspect the logs of Firefly III',
'no_valid_opposing' => 'Conversion failed because there is no valid account named ":account"',
'new_notes_empty' => 'The notes to be set are empty',
'unsupported_transaction_type_withdrawal' => 'Firefly III cannot convert a ":type" to a withdrawal',
'unsupported_transaction_type_deposit' => 'Firefly III cannot convert a ":type" to a deposit',
'unsupported_transaction_type_transfer' => 'Firefly III cannot convert a ":type" to a transfer',
'already_has_source_asset' => 'This transaction already has ":name" as the source asset account',
'already_has_destination_asset' => 'This transaction already has ":name" as the destination asset account',
'already_has_destination' => 'This transaction already has ":name" as the destination account',
'already_has_source' => 'This transaction already has ":name" as the source account',
'already_linked_to_subscription' => 'The transaction is already linked to subscription ":name"',
'already_linked_to_category' => 'The transaction is already linked to category ":name"',
'already_linked_to_budget' => 'The transaction is already linked to budget ":name"',
'cannot_find_subscription' => 'Firefly III can\'t find subscription ":name"',
'no_notes_to_move' => 'The transaction has no notes to move to the description field',
'no_tags_to_remove' => 'The transaction has no tags to remove',
'not_withdrawal' => 'The transaction is not a withdrawal',
'not_deposit' => 'The transaction is not a deposit',
'cannot_find_tag' => 'Firefly III can\'t find tag ":tag"',
'cannot_find_asset' => 'Firefly III can\'t find asset account ":name"',
'cannot_find_accounts' => 'Firefly III can\'t find the source or destination account',
'cannot_find_source_transaction' => 'Firefly III can\'t find the source transaction',
'cannot_find_destination_transaction' => 'Firefly III can\'t find the destination transaction',
'cannot_find_source_transaction_account' => 'Firefly III can\'t find the source transaction account',
'cannot_find_destination_transaction_account' => 'Firefly III can\'t find the destination transaction account',
'cannot_find_piggy' => 'Firefly III can\'t find a piggy bank named ":name"',
'no_link_piggy' => 'This transaction\'s accounts are not linked to the piggy bank, so no action will be taken',
'both_link_piggy' => 'This transaction\'s accounts are both linked to the piggy bank, so no action will be taken',
'cannot_unlink_tag' => 'Tag ":tag" isn\'t linked to this transaction',
'cannot_find_budget' => 'Firefly III can\'t find budget ":name"',
'cannot_find_category' => 'Firefly III can\'t find category ":name"',
'cannot_set_budget' => 'Firefly III can\'t set budget ":name" to a transaction of type ":type"',
'journal_invalid_amount' => 'Firefly III can\'t set amount ":amount" because it is not a valid number.',
'cannot_remove_zero_piggy' => 'Cannot remove zero amount from piggy bank ":name"',
'cannot_remove_from_piggy' => 'Cannot remove ":amount" from piggy bank ":name"',
'cannot_add_zero_piggy' => 'Cannot add zero amount to piggy bank ":name"',
'cannot_add_to_piggy' => 'Cannot add ":amount" to piggy bank ":name"',
];

View File

@@ -46,13 +46,16 @@ final class BillControllerTest extends TestCase
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$userGroup = UserGroup::create(['title' => 'Test Group']);
return User::create([
$user = User::create([
'email' => 'test@email.com',
'password' => 'password',
'user_group_id' => $userGroup->id,
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestBills(int $count, User $user): void

View File

@@ -46,13 +46,16 @@ final class BudgetControllerTest extends TestCase
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$userGroup = UserGroup::create(['title' => 'Test Group']);
return User::create([
$user = User::create([
'email' => 'test@email.com',
'password' => 'password',
'user_group_id' => $userGroup->id,
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestBudgets(int $count, User $user): void

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\Category;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class CategoryControllerTest
@@ -45,10 +46,16 @@ final class CategoryControllerTest extends TestCase
protected function createAuthenticatedUser(): User
{
return User::create([
'email' => 'test@email.com',
'password' => 'password',
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user = User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestCategories(int $count, User $user): void

View File

@@ -0,0 +1,183 @@
<?php
/*
* CurrencyControllerTest.php
* Copyright (c) 2024 tasnim0tantawi
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace Tests\integration\Api\Autocomplete;
use FireflyIII\Models\TransactionCurrency;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class CurrencyControllerTest
*
* @internal
*
* @coversNothing
*/
final class CurrencyControllerTest extends TestCase
{
/**
* @covers \FireflyIII\Api\V1\Controllers\Autocomplete\CurrencyController
*/
use RefreshDatabase;
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user = User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestCurrencies(int $count, bool $enabled): void
{
for ($i = 1; $i <= $count; ++$i) {
$currency = TransactionCurrency::create([
'name' => 'Currency '.$i,
'code' => 'CUR'.$i,
'symbol' => 'C'.$i,
'decimal_places' => $i,
'enabled' => $enabled,
]);
}
}
public function testGivenAnUnauthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturns401HttpCode(): void
{
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(401);
$response->assertHeader('Content-Type', 'application/json');
$response->assertContent('{"message":"Unauthenticated","exception":"AuthenticationException"}');
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturns200HttpCode(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointThenReturnsACollectionOfEnabledCurrencies(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(10, true);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonFragment(['name' => 'Currency 1']);
$response->assertJsonFragment(['code' => 'CUR1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'code',
'symbol',
'decimal_places',
],
]);
$response->assertJsonCount(10);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointDoesNotReturnDisabledCurrencies(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(10, false);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(0);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointWithQueryThenReturnsCurrenciesWithLimit(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// create test data
$this->createTestCurrencies(5, true);
// test API
$response = $this->get(route('api.v1.autocomplete.currencies', ['query' => 'Currency 1']), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonFragment(['name' => 'Currency 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'code',
'symbol',
'decimal_places',
],
]);
$response->assertJsonCount(1);
}
public function testGivenAuthenticatedRequestWhenCallingTheCurrenciesEndpointWithQueryThenReturnsCurrenciesThatMatchQuery(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestCurrencies(20, true);
$response = $this->get(route('api.v1.autocomplete.currencies', [
'query' => 'Currency 1',
'limit' => 20,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
// Currency 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11)
$response->assertJsonCount(11);
}
}

View File

@@ -0,0 +1,156 @@
<?php
/*
* ObjectGroupControllerTest.php
* Copyright (c) 2024 tasnim0tantawi
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace Tests\integration\Api\Autocomplete;
use FireflyIII\Models\ObjectGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
use FireflyIII\User;
use FireflyIII\Models\UserGroup;
/**
* Class ObjectGroupControllerTest
*
* @internal
*
* @coversNothing
*/
final class ObjectGroupControllerTest extends TestCase
{
/**
* @covers \FireflyIII\Api\V1\Controllers\Autocomplete\ObjectGroupController
*/
use RefreshDatabase;
protected function createAuthenticatedUser(): User
{
$userGroup = UserGroup::create(['title' => 'Test Group']);
$user = User::create([
'email' => 'test@email.com',
'password' => 'password',
]);
$user->user_group_id = $userGroup->id;
$user->save();
return $user;
}
private function createTestObjectGroups(int $count, User $user): void
{
for ($i = 1; $i <= $count; ++$i) {
$objectGroup = ObjectGroup::create([
'title' => 'Object Group '.$i,
'order' => $i,
'user_group_id' => $user->user_group_id,
'user_id' => $user->id,
]);
}
}
public function testGivenAnUnauthenticatedRequestWhenCallingTheObjectGroupEndpointThenReturn401HttpCode(): void
{
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(401);
$response->assertHeader('Content-Type', 'application/json');
$response->assertContent('{"message":"Unauthenticated","exception":"AuthenticationException"}');
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointThenReturns200HttpCode(): void
{
// act as a user
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
// test API
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointThenReturnsObjectGroups(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(5, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups'), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(5);
$response->assertJsonFragment(['title' => 'Object Group 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'title',
],
]);
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointWithQueryThenReturnsObjectGroupsWithLimit(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(5, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups', [
'query' => 'Object Group',
'limit' => 3,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
$response->assertJsonCount(3);
$response->assertJsonFragment(['name' => 'Object Group 1']);
$response->assertJsonStructure([
'*' => [
'id',
'name',
'title',
],
]);
}
public function testGivenAuthenticatedRequestWhenCallingTheObjectGroupsEndpointWithQueryThenReturnsObjectGroupsThatMatchQuery(): void
{
$user = $this->createAuthenticatedUser();
$this->actingAs($user);
$this->createTestObjectGroups(20, $user);
$response = $this->get(route('api.v1.autocomplete.object-groups', [
'query' => 'Object Group 1',
'limit' => 20,
]), ['Accept' => 'application/json']);
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'application/json');
// Object Group 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 (11)
$response->assertJsonCount(11);
$response->assertJsonMissing(['name' => 'Object Group 2']);
}
}