From e3c3a0a84b0daadde40fadcecb45e13475900388 Mon Sep 17 00:00:00 2001
From: JC5
Date: Tue, 7 Oct 2025 20:00:49 +0200
Subject: [PATCH 01/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-07?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
composer.lock | 10 +++++-----
config/firefly.php | 4 ++--
package-lock.json | 18 +++++++++---------
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/composer.lock b/composer.lock
index 2cdbc5e87e..cc8d7c5359 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
- "version": "v12.32.5",
+ "version": "v12.33.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a"
+ "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/77b2740391cd2a825ba59d6fada45e9b8b9bcc5a",
- "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/124efc5f09d4668a4dc13f94a1018c524a58bcb1",
+ "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1",
"shasum": ""
},
"require": {
@@ -2093,7 +2093,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2025-09-30T17:39:22+00:00"
+ "time": "2025-10-07T14:30:39+00:00"
},
{
"name": "laravel/passport",
diff --git a/config/firefly.php b/config/firefly.php
index 8bdd78ce6e..db7f7d8cb4 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => '6.4.2',
- 'build_time' => 1759824590,
+ 'version' => 'develop/2025-10-07',
+ 'build_time' => 1759859933,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 9e5ed88c78..670e032192 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.12",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz",
- "integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==",
+ "version": "2.8.13",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz",
+ "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -11836,9 +11836,9 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
- "version": "5.102.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.0.tgz",
- "integrity": "sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==",
+ "version": "5.102.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz",
+ "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11850,7 +11850,7 @@
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.15.0",
"acorn-import-phases": "^1.0.3",
- "browserslist": "^4.24.5",
+ "browserslist": "^4.26.3",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.3",
"es-module-lexer": "^1.2.1",
@@ -11862,8 +11862,8 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
- "schema-utils": "^4.3.2",
- "tapable": "^2.2.3",
+ "schema-utils": "^4.3.3",
+ "tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.4",
"webpack-sources": "^3.3.3"
From a1c870c9621c9b2cfa5d69a1cda8449b9186b8e0 Mon Sep 17 00:00:00 2001
From: Nicky De Maeyer
Date: Tue, 7 Oct 2025 14:32:23 +0200
Subject: [PATCH 02/82] improved request and balance range date handling
---
.../Autocomplete/AccountController.php | 2 +-
.../Controllers/Summary/BasicController.php | 2 --
.../Autocomplete/AutocompleteRequest.php | 3 ++-
app/Api/V1/Requests/Data/DateRequest.php | 2 +-
.../Controllers/Account/IndexController.php | 24 ++++++++-----------
.../Controllers/Chart/AccountController.php | 18 +++++++-------
app/Repositories/Account/AccountTasker.php | 9 +++----
.../JsonApi/Enrichments/AccountEnrichment.php | 6 +++--
app/Support/Request/ConvertsDataTypes.php | 16 ++++++-------
app/Support/Steam.php | 12 ++++++++--
10 files changed, 51 insertions(+), 43 deletions(-)
diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php
index c3f7cd9618..7ab92ca47b 100644
--- a/app/Api/V1/Controllers/Autocomplete/AccountController.php
+++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php
@@ -84,7 +84,7 @@ class AccountController extends Controller
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
- $date = $data['date'] ?? today(config('app.timezone'));
+ $date = $data['date'];
$return = [];
$timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query));
diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php
index 6b2acc86eb..64c39e5766 100644
--- a/app/Api/V1/Controllers/Summary/BasicController.php
+++ b/app/Api/V1/Controllers/Summary/BasicController.php
@@ -589,8 +589,6 @@ class BasicController extends Controller
private function getNetWorthInfo(Carbon $end): array
{
- $end->endOfDay();
-
/** @var User $user */
$user = auth()->user();
Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s')));
diff --git a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
index 566624b060..6e8432ef45 100644
--- a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
+++ b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
@@ -48,10 +48,11 @@ class AutocompleteRequest extends FormRequest
// remove 'initial balance' from allowed types. its internal
$array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]);
+ $date = $this->getCarbonDate('date') ?? today(config('app.timezone'));
return [
'types' => $array,
'query' => $this->convertString('query'),
- 'date' => $this->getCarbonDate('date'),
+ 'date' => $date->endOfDay(),
];
}
diff --git a/app/Api/V1/Requests/Data/DateRequest.php b/app/Api/V1/Requests/Data/DateRequest.php
index 7155994862..f5b37f7ca7 100644
--- a/app/Api/V1/Requests/Data/DateRequest.php
+++ b/app/Api/V1/Requests/Data/DateRequest.php
@@ -28,6 +28,7 @@ use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Validator;
/**
* Request class for end points that require date parameters.
@@ -55,7 +56,6 @@ class DateRequest extends FormRequest
// sanity check on dates:
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
$start->startOfDay();
$end->endOfDay();
if ($start->diffInYears($end, true) > 5) {
diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php
index 4f34f0dd5f..55d8f886c4 100644
--- a/app/Http/Controllers/Account/IndexController.php
+++ b/app/Http/Controllers/Account/IndexController.php
@@ -91,14 +91,12 @@ class IndexController extends Controller
/** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
- // #10618 go to the end of the previous day.
- $start->subSecond();
-
$ids = $accounts->pluck('id')->toArray();
- Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
- Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
- $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
- $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
+ Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ [
+ $startBalances,
+ $endBalances,
+ ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);
@@ -169,14 +167,12 @@ class IndexController extends Controller
/** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
- // #10618 go to the end of the previous day.
- $start->subSecond();
-
$ids = $accounts->pluck('id')->toArray();
- Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
- Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
- $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
- $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
+ Log::debug(sprintf('index: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ [
+ $startBalances,
+ $endBalances,
+ ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 3df8e35845..527f6daefb 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -116,10 +116,11 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
- Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
- Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
- $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
- $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
+ Log::debug(sprintf('expenseAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ [
+ $startBalances,
+ $endBalances,
+ ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
Log::debug('Done collecting balances');
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
@@ -666,10 +667,11 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
- Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
- Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
- $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
- $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
+ Log::debug(sprintf('revAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ [
+ $startBalances,
+ $endBalances,
+ ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.
diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php
index 63260b6643..1b0cc75be1 100644
--- a/app/Repositories/Account/AccountTasker.php
+++ b/app/Repositories/Account/AccountTasker.php
@@ -50,10 +50,11 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$yesterday = clone $start;
$yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
$end->endOfDay(); // needs to be end of day to be correct.
- Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s')));
- Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
- $startSet = Steam::accountsBalancesOptimized($accounts, $yesterday);
- $endSet = Steam::accountsBalancesOptimized($accounts, $end);
+ Log::debug(sprintf('getAccountReport: accountsBalancesInRange("%s", "%s")', $yesterday->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ [
+ $startSet,
+ $endSet,
+ ] = Steam::accountsBalancesInRange($yesterday, $end, $accounts);
Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */
diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
index 7aedeb6880..d89bb2c704 100644
--- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
@@ -289,8 +289,10 @@ class AccountEnrichment implements EnrichmentInterface
{
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
if ($this->start instanceof Carbon && $this->end instanceof Carbon) {
- $this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary);
- $this->endBalances = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary);
+ [
+ $this->startBalances,
+ $this->endBalances,
+ ] = Steam::accountsBalancesInRange($this->start, $this->end, $this->collection, $this->primaryCurrency, $this->convertToPrimary);
}
}
diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php
index a57c45a320..f8cb76378f 100644
--- a/app/Support/Request/ConvertsDataTypes.php
+++ b/app/Support/Request/ConvertsDataTypes.php
@@ -402,21 +402,21 @@ trait ConvertsDataTypes
*/
protected function getCarbonDate(string $field): ?Carbon
{
- $result = null;
+ $data = (string)$this->get($field);
+ Log::debug(sprintf('Date string is "%s"', $data));
- Log::debug(sprintf('Date string is "%s"', (string)$this->get($field)));
+ if ('' === $data) {
+ return null;
+ }
try {
- $result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null;
+ return new Carbon($data, config('app.timezone'));
} catch (InvalidFormatException) {
// @ignoreException
- Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
- }
- if (!$result instanceof Carbon) {
- Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
+ Log::debug(sprintf('Exception when parsing date "%s".', $data));
}
- return $result;
+ return null;
}
/**
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 10a8061153..0e4ddd661b 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -49,7 +49,7 @@ use function Safe\preg_replace;
*/
class Steam
{
- public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{
Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
$result = [];
@@ -61,7 +61,7 @@ class Steam
$arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
+ ->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
->groupBy(['transactions.account_id', 'transaction_currencies.code'])
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
;
@@ -125,6 +125,14 @@ class Steam
return $result;
}
+ public function accountsBalancesInRange(Carbon $start, Carbon $end, Collection $accounts, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ {
+ return [
+ $this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false),
+ $this->accountsBalancesOptimized($accounts, $end, $primary, $convertToPrimary),
+ ];
+ }
+
/**
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
*/
From d91d30c8f0f19adffd8e64f4a445a13db4ec956e Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 06:31:47 +0200
Subject: [PATCH 03/82] Remove some debug logging, use Facade.
---
.../Authentication/RemoteUserGuard.php | 20 +++++++++----------
app/Support/System/OAuthKeys.php | 19 +++++++++---------
2 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php
index 1c87806e00..f53ecaa4fc 100644
--- a/app/Support/Authentication/RemoteUserGuard.php
+++ b/app/Support/Authentication/RemoteUserGuard.php
@@ -49,14 +49,14 @@ class RemoteUserGuard implements Guard
{
/** @var null|Request $request */
$request = $app->get('request');
- Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
+ // Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
$this->application = $app;
$this->user = null;
}
public function authenticate(): void
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
if ($this->user instanceof User) {
Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email));
@@ -104,21 +104,21 @@ class RemoteUserGuard implements Guard
public function check(): bool
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
return $this->user() instanceof User;
}
public function guest(): bool
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
return !$this->check();
}
public function hasUser(): bool
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException('Did not implement RemoteUserGuard::hasUser()');
}
@@ -128,14 +128,14 @@ class RemoteUserGuard implements Guard
*/
public function id(): int|string|null
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
return $this->user?->id;
}
public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
if ($user instanceof User) {
$this->user = $user;
@@ -146,7 +146,7 @@ class RemoteUserGuard implements Guard
public function user(): ?User
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
$user = $this->user;
if (!$user instanceof User) {
Log::debug('User is NULL');
@@ -164,14 +164,14 @@ class RemoteUserGuard implements Guard
*/
public function validate(array $credentials = []): bool
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
throw new FireflyException('Did not implement RemoteUserGuard::validate()');
}
public function viaRemember(): bool
{
- Log::debug(sprintf('Now at %s', __METHOD__));
+ // Log::debug(sprintf('Now at %s', __METHOD__));
return false;
}
diff --git a/app/Support/System/OAuthKeys.php b/app/Support/System/OAuthKeys.php
index afb028180a..0f66372a69 100644
--- a/app/Support/System/OAuthKeys.php
+++ b/app/Support/System/OAuthKeys.php
@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\System;
use FireflyIII\Exceptions\FireflyException;
+use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Crypt;
@@ -63,10 +64,10 @@ class OAuthKeys
$privateKey = '';
$publicKey = '';
// better check if keys are in the database:
- if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) {
+ if (FireflyConfig::has(self::PRIVATE_KEY) && FireflyConfig::has(self::PUBLIC_KEY)) {
try {
- $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
- $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
+ $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
+ $publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
} catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) {
app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
app('log')->error($e->getTraceAsString());
@@ -87,8 +88,8 @@ class OAuthKeys
*/
public static function restoreKeysFromDB(): bool
{
- $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
- $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
+ $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
+ $publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
try {
$privateContent = Crypt::decrypt($privateKey);
@@ -98,8 +99,8 @@ class OAuthKeys
app('log')->error($e->getMessage());
// delete config vars from DB:
- app('fireflyconfig')->delete(self::PRIVATE_KEY);
- app('fireflyconfig')->delete(self::PUBLIC_KEY);
+ FireflyConfig::delete(self::PRIVATE_KEY);
+ FireflyConfig::delete(self::PUBLIC_KEY);
return false;
}
@@ -115,8 +116,8 @@ class OAuthKeys
{
$private = storage_path('oauth-private.key');
$public = storage_path('oauth-public.key');
- app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
- app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
+ FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
+ FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
}
public static function verifyKeysRoutine(): void
From ec08485c2b3841ad67aa5a5555a53c47328c1dc0 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 06:44:48 +0200
Subject: [PATCH 04/82] Clean up some code surrounding account balances.
---
app/Helpers/Report/NetWorth.php | 51 ------
app/Helpers/Report/NetWorthInterface.php | 7 -
app/Http/Controllers/Json/BoxController.php | 5 +-
app/Support/Steam.php | 189 ++++++++++----------
4 files changed, 95 insertions(+), 157 deletions(-)
diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php
index 0ea1ce6cfe..80c0171ad6 100644
--- a/app/Helpers/Report/NetWorth.php
+++ b/app/Helpers/Report/NetWorth.php
@@ -130,55 +130,4 @@ class NetWorth implements NetWorthInterface
$this->accountRepository->setUserGroup($userGroup);
}
-
- #[Deprecated]
- public function sumNetWorthByCurrency(Carbon $date): array
- {
- /**
- * Collect accounts
- */
- $accounts = $this->getAccounts();
- $return = [];
- Log::debug(sprintf('SumNetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s')));
- $balances = Steam::accountsBalancesOptimized($accounts, $date);
- foreach ($accounts as $account) {
- $currency = $this->accountRepository->getAccountCurrency($account);
- $balance = $balances[$account->id]['balance'] ?? '0';
-
- // always subtract virtual balance.
- $virtualBalance = $account->virtual_balance;
- if ('' !== $virtualBalance) {
- $balance = bcsub($balance, (string) $virtualBalance);
- }
-
- $return[$currency->id] ??= [
- 'id' => (string) $currency->id,
- 'name' => $currency->name,
- 'symbol' => $currency->symbol,
- 'code' => $currency->code,
- 'decimal_places' => $currency->decimal_places,
- 'sum' => '0',
- ];
- $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $balance);
- }
-
- return $return;
- }
-
- private function getAccounts(): Collection
- {
- $accounts = $this->accountRepository->getAccountsByType(
- [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]
- );
- $filtered = new Collection();
-
- /** @var Account $account */
- foreach ($accounts as $account) {
- if (1 === (int) $this->accountRepository->getMetaValue($account, 'include_net_worth')) {
- $filtered->push($account);
- }
- }
-
- return $filtered;
- }
}
diff --git a/app/Helpers/Report/NetWorthInterface.php b/app/Helpers/Report/NetWorthInterface.php
index 421caa9a74..8660ab4e4c 100644
--- a/app/Helpers/Report/NetWorthInterface.php
+++ b/app/Helpers/Report/NetWorthInterface.php
@@ -50,11 +50,4 @@ interface NetWorthInterface
public function setUserGroup(UserGroup $userGroup): void;
- /**
- * TODO move to repository
- *
- * Same as above but cleaner function with less dependencies.
- */
- #[Deprecated]
- public function sumNetWorthByCurrency(Carbon $date): array;
}
diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php
index d202a80af0..04d6a6b108 100644
--- a/app/Http/Controllers/Json/BoxController.php
+++ b/app/Http/Controllers/Json/BoxController.php
@@ -38,6 +38,7 @@ use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Log;
/**
* Class BoxController.
@@ -165,7 +166,7 @@ class BoxController extends Controller
$allAccounts = $accountRepository->getActiveAccountsByType(
[AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]
);
- app('log')->debug(sprintf('Found %d accounts.', $allAccounts->count()));
+ Log::debug(sprintf('Found %d accounts.', $allAccounts->count()));
// filter list on preference of being included.
$filtered = $allAccounts->filter(
@@ -173,7 +174,7 @@ class BoxController extends Controller
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
$result = null === $includeNetWorth || '1' === $includeNetWorth;
if (false === $result) {
- app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
+ Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
}
return $result;
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 0e4ddd661b..df270a2a3c 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -40,7 +40,6 @@ use Illuminate\Support\Str;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use ValueError;
-
use function Safe\parse_url;
use function Safe\preg_replace;
@@ -51,47 +50,45 @@ class Steam
{
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{
- Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
- $result = [];
+ Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true)));
+ $result = [];
$convertToPrimary ??= Amount::convertToPrimary();
$primary ??= Amount::getPrimaryCurrency();
- $currencies = $this->getCurrencies($accounts);
+ $currencies = $this->getCurrencies($accounts);
// balance(s) in all currencies for ALL accounts.
$arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
- ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
- ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
- ;
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
+ ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
+ ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray();
/** @var Account $account */
foreach ($accounts as $account) {
- // this array is PER account, so we wait a bit before we change code here.
- $return = [
+ $return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
- $currency = $currencies[$account->id];
+ $currency = $currencies[$account->id];
// second array
- $accountSum = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id);
+ $accountSum = array_filter($arrayOfSums, fn($entry) => $entry['account_id'] === $account->id);
if (0 === count($accountSum)) {
$result[$account->id] = $return;
continue;
}
- $accountSum = array_values($accountSum)[0];
- $sumOfAmount = (string)$accountSum['sum_of_amount'];
- $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
- $sumsByCode = [
+ $accountSum = array_values($accountSum)[0];
+ $sumOfAmount = (string)$accountSum['sum_of_amount'];
+ $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
+ $sumsByCode = [
$accountSum['code'] => $sumOfAmount,
];
// Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
- $return['balance'] = $sumsByCode[$currency->code] ?? '0';
+ $return['balance'] = $sumsByCode[$currency->code] ?? '0';
if (!$convertToPrimary) {
unset($return['pc_balance']);
// Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
@@ -103,7 +100,7 @@ class Steam
}
// either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
@@ -152,10 +149,10 @@ class Steam
// Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
if (str_contains($number, '.')) {
if ('-' !== $number[0]) {
- return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision);
+ return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
- return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision);
+ return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
return $number;
@@ -284,7 +281,8 @@ class Steam
}
/**
- * Returns smaller than or equal to, so be careful with END OF DAY.
+ * By default this method returns "smaller than or equal to", so be careful with END OF DAY.
+ * If you need end of day balance, use "inclusive = false".
*
* Returns the balance of an account at exact moment given. Array with at least one value.
* Always returns:
@@ -296,18 +294,17 @@ class Steam
* --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency.
* "EUR": balance in EUR (or whatever currencies the account has balance in)
*/
- public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{
- $cache = new CacheProperties();
+ $cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($date);
if ($cache->has()) {
- Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
-
- // return $cache->get();
+ Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s, inclusive:%s)', $account->id, $date->format('Y-m-d H:i:s'), var_export($inclusive, true)));
+ return $cache->get();
}
- // Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
+ Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
if (null === $convertToPrimary) {
$convertToPrimary = Amount::convertToPrimary($account->user);
}
@@ -315,7 +312,7 @@ class Steam
$primary = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
}
// account balance thing.
- $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
+ $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$accountCurrency = $account->meta['currency'];
}
@@ -323,20 +320,19 @@ class Steam
$accountCurrency = $this->getAccountCurrency($account);
}
- $hasCurrency = null !== $accountCurrency;
- $currency = $hasCurrency ? $accountCurrency : $primary;
- $return = [
+ $hasCurrency = null !== $accountCurrency;
+ $currency = $hasCurrency ? $accountCurrency : $primary;
+ $return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
// balance(s) in all currencies.
- $array = $account->transactions()
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
- ->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
- ;
- $others = $this->groupAndSumTransactions($array, 'code', 'amount');
+ $array = $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
+ ->get(['transaction_currencies.code', 'transactions.amount'])->toArray();
+ $others = $this->groupAndSumTransactions($array, 'code', 'amount');
// Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
$return['balance'] = $others[$currency->code] ?? '0';
@@ -351,7 +347,7 @@ class Steam
}
// either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
@@ -365,7 +361,7 @@ class Steam
$return['balance'] = bcadd($return['balance'], $virtualBalance);
// Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
}
- $final = array_merge($return, $others);
+ $final = array_merge($return, $others);
// Log::debug('Final balance is', $final);
$cache->store($final);
@@ -380,7 +376,7 @@ class Steam
Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// set up cache
- $cache = new CacheProperties();
+ $cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('final-balance-in-range');
$cache->addProperty($start);
@@ -390,22 +386,22 @@ class Steam
return $cache->get();
}
- $balances = [];
- $formatted = $start->format('Y-m-d');
+ $balances = [];
+ $formatted = $start->format('Y-m-d');
/*
* To make sure the start balance is correct, we need to get the balance at the exact end of the previous day.
* Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment.
* THAT will be the start balance.
*/
- $request = clone $start;
+ $request = clone $start;
$request->subDay()->endOfDay();
Log::debug('Get first balance to start.');
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
- $startBalance = $this->finalAccountBalance($account, $request);
- $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
- $accountCurrency = $this->getAccountCurrency($account);
- $hasCurrency = $accountCurrency instanceof TransactionCurrency;
- $currency = $accountCurrency ?? $primaryCurrency;
+ $startBalance = $this->finalAccountBalance($account, $request);
+ $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
+ $accountCurrency = $this->getAccountCurrency($account);
+ $hasCurrency = $accountCurrency instanceof TransactionCurrency;
+ $currency = $accountCurrency ?? $primaryCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
@@ -418,7 +414,7 @@ class Steam
Log::debug(sprintf('Also set start balance in %s', $primaryCurrency->code));
$startBalance[$primaryCurrency->code] ??= '0';
}
- $currencies = [
+ $currencies = [
$currency->id => $currency,
$primaryCurrency->id => $primaryCurrency,
];
@@ -428,48 +424,47 @@ class Steam
// sums up the balance changes per day.
Log::debug(sprintf('Date >= %s and <= %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
- $set = $account->transactions()
- ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
- ->groupBy('transaction_journals.date')
- ->groupBy('transactions.transaction_currency_id')
- ->orderBy('transaction_journals.date', 'ASC')
- ->whereNull('transaction_journals.deleted_at')
- ->get(
- [ // @phpstan-ignore-line
- 'transaction_journals.date',
- 'transactions.transaction_currency_id',
- DB::raw('SUM(transactions.amount) AS sum_of_day'),
- ]
- )
- ;
+ $set = $account->transactions()
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
+ ->groupBy('transaction_journals.date')
+ ->groupBy('transactions.transaction_currency_id')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ [ // @phpstan-ignore-line
+ 'transaction_journals.date',
+ 'transactions.transaction_currency_id',
+ DB::raw('SUM(transactions.amount) AS sum_of_day'),
+ ]
+ );
- $currentBalance = $startBalance;
- $converter = new ExchangeRateConverter();
+ $currentBalance = $startBalance;
+ $converter = new ExchangeRateConverter();
/** @var Transaction $entry */
foreach ($set as $entry) {
// get date object
- $carbon = new Carbon($entry->date, $entry->date_tz);
- $carbonKey = $carbon->format('Y-m-d');
+ $carbon = new Carbon($entry->date, $entry->date_tz);
+ $carbonKey = $carbon->format('Y-m-d');
// make sure sum is a string:
- $sumOfDay = (string)($entry->sum_of_day ?? '0');
+ $sumOfDay = (string)($entry->sum_of_day ?? '0');
// #10426 make sure sum is not in scientific notation.
- $sumOfDay = $this->floatalize($sumOfDay);
+ $sumOfDay = $this->floatalize($sumOfDay);
// find currency of this entry, does not have to exist.
$currencies[$entry->transaction_currency_id] ??= Amount::getTransactionCurrencyById($entry->transaction_currency_id);
// make sure this $entry has its own $entryCurrency
/** @var TransactionCurrency $entryCurrency */
- $entryCurrency = $currencies[$entry->transaction_currency_id];
+ $entryCurrency = $currencies[$entry->transaction_currency_id];
Log::debug(sprintf('Processing transaction(s) on moment %s', $carbon->format('Y-m-d H:i:s')));
// add amount to current balance in currency code.
- $currentBalance[$entryCurrency->code] ??= '0';
+ $currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string)$currentBalance[$entryCurrency->code]);
// if not requested to convert to primary currency, add the amount to "balance", do nothing else.
@@ -487,7 +482,7 @@ class Steam
}
}
// add to final array.
- $balances[$carbonKey] = $currentBalance;
+ $balances[$carbonKey] = $currentBalance;
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
}
$cache->store($balances);
@@ -504,7 +499,7 @@ class Steam
*/
public function floatalize(string $value): string
{
- $value = strtoupper($value);
+ $value = strtoupper($value);
if (!str_contains($value, 'E')) {
return $value;
}
@@ -528,8 +523,8 @@ class Steam
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
- $type = $account->accountType->type;
- $list = config('firefly.valid_currency_account_types');
+ $type = $account->accountType->type;
+ $list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
@@ -550,7 +545,7 @@ class Steam
try {
$hostName = gethostbyaddr($ipAddress);
} catch (Exception $e) {
- app('log')->error($e->getMessage());
+ Log::error($e->getMessage());
$hostName = $ipAddress;
}
@@ -582,15 +577,15 @@ class Steam
{
$list = [];
- $set = auth()->user()->transactions()
- ->whereIn('transactions.account_id', $accounts)
- ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
- ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
+ $set = auth()->user()->transactions()
+ ->whereIn('transactions.account_id', $accounts)
+ ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
+ ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
;
/** @var Transaction $entry */
foreach ($set as $entry) {
- $date = new Carbon($entry->max_date, config('app.timezone'));
+ $date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[(int)$entry->account_id] = $date;
}
@@ -608,14 +603,14 @@ class Steam
if (null !== $cached) {
return $cached;
}
- $locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
+ $locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
if (is_array($locale)) {
$locale = 'equal';
}
if ('equal' === $locale) {
$locale = $this->getLanguage();
}
- $locale = (string)$locale;
+ $locale = (string)$locale;
// Check for Windows to replace the locale correctly.
if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
@@ -656,9 +651,9 @@ class Steam
public function getSafeUrl(string $unknownUrl, string $safeUrl): string
{
// Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
- $returnUrl = $safeUrl;
- $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
- $safeHost = parse_url($safeUrl, PHP_URL_HOST);
+ $returnUrl = $safeUrl;
+ $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
+ $safeHost = parse_url($safeUrl, PHP_URL_HOST);
if (null !== $unknownHost && $unknownHost === $safeHost) {
$returnUrl = $unknownUrl;
@@ -760,12 +755,12 @@ class Steam
if (null === $preference) {
$singleton->setPreference($key, $currency);
}
- $current = $amount;
+ $current = $amount;
if ($currency->id !== $primary->id) {
$current = $converter->convert($currency, $primary, $date, $amount);
Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current));
}
- $total = bcadd((string) $current, $total);
+ $total = bcadd((string)$current, $total);
}
return $total;
@@ -779,8 +774,8 @@ class Steam
$primary = Amount::getPrimaryCurrency();
$currencies[$primary->id] = $primary;
- $ids = $accounts->pluck('id')->toArray();
- $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
+ $ids = $accounts->pluck('id')->toArray();
+ $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
@@ -790,7 +785,7 @@ class Steam
}
}
// collect those currencies, skip primary because we already have it.
- $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
+ $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
foreach ($set as $item) {
$currencies[$item->id] = $item;
}
@@ -801,7 +796,7 @@ class Steam
$currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$currencyId = $account->meta['currency']->id;
- $currencies[$currencyId] ??= $account->meta['currency'];
+ $currencies[$currencyId] ??= $account->meta['currency'];
$accountCurrencies[$accountId] = $account->meta['currency'];
}
if (!$currencyPresent && !array_key_exists($accountId, $accountPreferences)) {
From 0e0ec89b26649be9d640a215a8984044ec3a62ab Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 07:09:42 +0200
Subject: [PATCH 05/82] Remove all uses of "finalAccountBalance", move towards
one function to rule them all.
---
.../Report/Audit/MonthReportGenerator.php | 13 ++++++++----
.../Extensions/AccountCollection.php | 11 +++++-----
.../Account/ReconcileController.php | 20 ++++++++++++------
.../Controllers/Account/ShowController.php | 12 +++++++----
.../Controllers/Chart/AccountController.php | 6 +++---
.../Controllers/Json/ReconcileController.php | 14 +++++++++----
.../Transaction/ConvertController.php | 21 ++++++++++++-------
.../PiggyBank/PiggyBankRepository.php | 4 +++-
app/Support/Steam.php | 4 ++++
app/Support/Twig/General.php | 8 +++++--
10 files changed, 77 insertions(+), 36 deletions(-)
diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php
index a0aea0c9df..b920ab172e 100644
--- a/app/Generator/Report/Audit/MonthReportGenerator.php
+++ b/app/Generator/Report/Audit/MonthReportGenerator.php
@@ -137,9 +137,12 @@ class MonthReportGenerator implements ReportGeneratorInterface
;
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
- // this call is correct.
- Log::debug(sprintf('getAuditReport: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $dayBeforeBalance = Steam::finalAccountBalance($account, $date);
+
+ Log::debug(sprintf('getAuditReport: Call accountsBalancesOptimized with date/time "%s"', $date->toIso8601String()));
+ // 2025-10-08 replace with accountsBalancesOptimized.
+ // $dayBeforeBalance = Steam::finalAccountBalance($account, $date);
+ $dayBeforeBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
+
$startBalance = $dayBeforeBalance['balance'];
$primaryCurrency = app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup);
$currency = $accountRepository->getAccountCurrency($account) ?? $primaryCurrency;
@@ -176,12 +179,14 @@ class MonthReportGenerator implements ReportGeneratorInterface
// call is correct.
Log::debug(sprintf('getAuditReport end: Call finalAccountBalance with date/time "%s"', $this->end->toIso8601String()));
+ // 2025-10-08 replace with accountsBalancesOptimized:
return [
'journals' => $journals,
'currency' => $currency,
'exists' => 0 !== count($journals),
'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
- 'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'],
+ // 'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'],
+ 'endBalance' => Steam::accountsBalancesOptimized(new Collection()->push($account), $this->end)[$account->id]['balance'],
'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];
diff --git a/app/Helpers/Collector/Extensions/AccountCollection.php b/app/Helpers/Collector/Extensions/AccountCollection.php
index d56ee1de4b..837c353b41 100644
--- a/app/Helpers/Collector/Extensions/AccountCollection.php
+++ b/app/Helpers/Collector/Extensions/AccountCollection.php
@@ -62,12 +62,13 @@ trait AccountCollection
if (null === $account) {
continue;
}
+
+ // 2025-10-08 replace with accountsBalancesOptimized
// the balance must be found BEFORE the transaction date.
- // so sub one second. This is not perfect, but works well enough.
- $date = clone $transaction['date'];
- $date->subSecond();
- Log::debug(sprintf('accountBalanceIs: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $balance = Steam::finalAccountBalance($account, $date);
+ // so inclusive = false
+ Log::debug(sprintf('accountBalanceIs: Call accountsBalancesOptimized with date/time "%s"', $transaction['date']->toIso8601String()));
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id];
+ // $balance = Steam::finalAccountBalance($account, $date);
$result = bccomp((string) $balance['balance'], $value);
Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result));
diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php
index cee8c1c158..33da0dac0e 100644
--- a/app/Http/Controllers/Account/ReconcileController.php
+++ b/app/Http/Controllers/Account/ReconcileController.php
@@ -39,6 +39,7 @@ use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
@@ -113,13 +114,20 @@ class ReconcileController extends Controller
$start->startOfDay();
$end->endOfDay();
- $startDate = clone $start;
- $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
+// $startDate = clone $start;
+// $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
// both are validated and are correct.
- Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
- Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
- $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
- $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+// Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
+// Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
+// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
+// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+
+ // 2025-10-08 replace with accountsBalancesOptimized
+ // no longer need to do subday->endofday on $start, set inclusive = false for the same effect.
+ $startBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places);
+ $endBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places);
+
+
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]);
diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php
index 9b1a6922f6..bee687a89c 100644
--- a/app/Http/Controllers/Account/ShowController.php
+++ b/app/Http/Controllers/Account/ShowController.php
@@ -169,8 +169,9 @@ class ShowController extends Controller
$now = $end;
}
- Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String()));
- $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id];
+ // $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
return view(
'accounts.show',
@@ -237,8 +238,11 @@ class ShowController extends Controller
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
// correct
- Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
- $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency);
+ Log::debug(sprintf('showAll: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
+
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ // $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency);
+ $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
return view(
'accounts.show',
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 527f6daefb..81309854b7 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -522,9 +522,9 @@ class AccountController extends Controller
$range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency);
Log::debug('Get and filter balance for entire range end');
// temp, get end balance.
- Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
- Steam::finalAccountBalance($account, $end);
- Log::debug('END temp get end balance done');
+// Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
+// Steam::finalAccountBalance($account, $end);
+// Log::debug('END temp get end balance done');
$previous = array_values($range)[0];
$accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances.
diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php
index 3d88bb0a88..b2d33c7fe9 100644
--- a/app/Http/Controllers/Json/ReconcileController.php
+++ b/app/Http/Controllers/Json/ReconcileController.php
@@ -197,10 +197,16 @@ class ReconcileController extends Controller
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->primaryCurrency;
// correct
- Log::debug(sprintf('transactions: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
- Log::debug(sprintf('transactions2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
- $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
- $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+ Log::debug(sprintf('transactions: Call accountsBalancesOptimized with date/time "%s"', $startDate->toIso8601String()));
+ Log::debug(sprintf('transactions2: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
+
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized
+// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
+// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+
+ $startBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id];
+ $endBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
+
// get the transactions
$selectionStart = clone $start;
diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php
index bb5aa6f7d6..655aeeb594 100644
--- a/app/Http/Controllers/Transaction/ConvertController.php
+++ b/app/Http/Controllers/Transaction/ConvertController.php
@@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Update\JournalUpdateService;
+use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Transformers\TransactionGroupTransformer;
@@ -45,6 +46,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
@@ -228,11 +230,13 @@ class ConvertController extends Controller
foreach ($accountList as $account) {
$date = today()->endOfDay();
Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $balance = Steam::finalAccountBalance($account, $date)['balance'];
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ // $balance = Steam::finalAccountBalance($account, $date)['balance'];
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
- $role = 'l_'.$account->accountType->type;
- $key = (string) trans('firefly.opt_group_'.$role);
- $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
+ $role = sprintf('l_%s',$account->accountType->type);
+ $key = (string) trans(sprintf('firefly.opt_group_%s',$role));
+ $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
}
return $grouped;
@@ -252,15 +256,18 @@ class ConvertController extends Controller
foreach ($accountList as $account) {
$date = today()->endOfDay();
Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $balance = Steam::finalAccountBalance($account, $date)['balance'];
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ // $balance = Steam::finalAccountBalance($account, $date)['balance'];
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
+
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
if ('' === $role) {
$role = 'no_account_type';
}
- $key = (string) trans('firefly.opt_group_'.$role);
- $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
+ $key = (string) trans(sprintf('firefly.opt_group_%s',$role));
+ $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
}
return $grouped;
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index 056825e7d8..5ee5617130 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -375,7 +375,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
{
Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s')));
Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $balance = Steam::finalAccountBalance($account, $date)['balance'];
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
+ //$balance = Steam::finalAccountBalance($account, $date)['balance'];
Log::debug(sprintf('Balance is: %s', $balance));
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index df270a2a3c..4da915a0ee 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -122,6 +122,9 @@ class Steam
return $result;
}
+ /**
+ * Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range.
+ */
public function accountsBalancesInRange(Carbon $start, Carbon $end, Collection $accounts, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
return [
@@ -281,6 +284,7 @@ class Steam
}
/**
+ * @deprecated
* By default this method returns "smaller than or equal to", so be careful with END OF DAY.
* If you need end of day balance, use "inclusive = false".
*
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index 485dfe1bd9..9208875ed2 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -30,6 +30,7 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Search\OperatorQuerySearch;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use League\CommonMark\GithubFlavoredMarkdownConverter;
@@ -155,9 +156,12 @@ class General extends AbstractExtension
}
/** @var Carbon $date */
- $date = session('end', today(config('app.timezone'))->endOfMonth());
+ $date = now();
Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $info = Steam::finalAccountBalance($account, $date);
+
+ // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
+ $info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
+ // $info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account);
$primary = Amount::getPrimaryCurrency();
$convertToPrimary = Amount::convertToPrimary();
From 8b0ee7e20a303298cb112d9d3b5a94af9d3ce8a1 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 08:12:30 +0200
Subject: [PATCH 06/82] Support multi-currency accounts better, matches the old
method.
---
.../Controllers/Account/ShowController.php | 3 +-
app/Support/Steam.php | 31 ++++++++++---------
2 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php
index bee687a89c..104caabefd 100644
--- a/app/Http/Controllers/Account/ShowController.php
+++ b/app/Http/Controllers/Account/ShowController.php
@@ -241,7 +241,8 @@ class ShowController extends Controller
Log::debug(sprintf('showAll: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
- // $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency);
+ // $balances = Steam::finalAccountBalance($account, $end);
+ // $balances = Steam::filterAccountBalance($balances, $account, $this->convertToPrimary, $accountCurrency);
$balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
return view(
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 4da915a0ee..b5d14b5ead 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -64,6 +64,7 @@ class Steam
->groupBy(['transactions.account_id', 'transaction_currencies.code'])
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray();
+ Log::debug('Array of sums: ', $arrayOfSums);
/** @var Account $account */
foreach ($accounts as $account) {
$return = [
@@ -73,19 +74,19 @@ class Steam
$currency = $currencies[$account->id];
// second array
- $accountSum = array_filter($arrayOfSums, fn($entry) => $entry['account_id'] === $account->id);
- if (0 === count($accountSum)) {
+ $accountSums = array_filter($arrayOfSums, fn($entry) => $entry['account_id'] === $account->id);
+ if (0 === count($accountSums)) {
$result[$account->id] = $return;
continue;
}
- $accountSum = array_values($accountSum)[0];
- $sumOfAmount = (string)$accountSum['sum_of_amount'];
- $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
- $sumsByCode = [
- $accountSum['code'] => $sumOfAmount,
- ];
-
+ $sumsByCode = [];
+ foreach($accountSums as $accountSum) {
+ // $accountSum = array_values($accountSum)[0];
+ $sumOfAmount = (string)$accountSum['sum_of_amount'];
+ $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
+ $sumsByCode[$accountSum['code']] = $sumOfAmount;
+ }
// Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
$return['balance'] = $sumsByCode[$currency->code] ?? '0';
@@ -93,6 +94,7 @@ class Steam
unset($return['pc_balance']);
// Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
}
+
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
if ($convertToPrimary) {
$return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date);
@@ -116,7 +118,7 @@ class Steam
}
$final = array_merge($return, $sumsByCode);
$result[$account->id] = $final;
- // Log::debug('Final balance is', $final);
+ Log::debug(sprintf('Final balance for account #%d is',$account->id), $final);
}
return $result;
@@ -306,7 +308,7 @@ class Steam
$cache->addProperty($date);
if ($cache->has()) {
Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s, inclusive:%s)', $account->id, $date->format('Y-m-d H:i:s'), var_export($inclusive, true)));
- return $cache->get();
+ // return $cache->get();
}
Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
if (null === $convertToPrimary) {
@@ -321,7 +323,6 @@ class Steam
$accountCurrency = $account->meta['currency'];
}
if (!$currencyPresent) {
-
$accountCurrency = $this->getAccountCurrency($account);
}
$hasCurrency = null !== $accountCurrency;
@@ -337,7 +338,7 @@ class Steam
->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
->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 there is no request to convert, take this as "balance" and "pc_balance".
$return['balance'] = $others[$currency->code] ?? '0';
if (!$convertToPrimary) {
@@ -346,7 +347,7 @@ class Steam
}
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
if ($convertToPrimary) {
- $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
+ $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date);
// Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
}
@@ -366,7 +367,7 @@ class Steam
// Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
}
$final = array_merge($return, $others);
- // Log::debug('Final balance is', $final);
+ Log::debug('Final balance is', $final);
$cache->store($final);
return $final;
From 62e33a51bdfbbcebe4c55e0c9c983af72e45defa Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 08:54:26 +0200
Subject: [PATCH 07/82] Better order for method arguments.
---
app/Http/Controllers/Account/IndexController.php | 4 ++--
app/Http/Controllers/Chart/AccountController.php | 4 ++--
app/Repositories/Account/AccountTasker.php | 2 +-
app/Support/JsonApi/Enrichments/AccountEnrichment.php | 2 +-
app/Support/Steam.php | 5 ++++-
5 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php
index 55d8f886c4..64d8f37eb5 100644
--- a/app/Http/Controllers/Account/IndexController.php
+++ b/app/Http/Controllers/Account/IndexController.php
@@ -96,7 +96,7 @@ class IndexController extends Controller
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);
@@ -172,7 +172,7 @@ class IndexController extends Controller
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 81309854b7..5d09db1267 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -120,7 +120,7 @@ class AccountController extends Controller
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
Log::debug('Done collecting balances');
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
@@ -671,7 +671,7 @@ class AccountController extends Controller
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end , $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.
diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php
index 1b0cc75be1..119ea48426 100644
--- a/app/Repositories/Account/AccountTasker.php
+++ b/app/Repositories/Account/AccountTasker.php
@@ -54,7 +54,7 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
[
$startSet,
$endSet,
- ] = Steam::accountsBalancesInRange($yesterday, $end, $accounts);
+ ] = Steam::accountsBalancesInRange($accounts, $yesterday, $end);
Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */
diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
index d89bb2c704..43179333d6 100644
--- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
@@ -292,7 +292,7 @@ class AccountEnrichment implements EnrichmentInterface
[
$this->startBalances,
$this->endBalances,
- ] = Steam::accountsBalancesInRange($this->start, $this->end, $this->collection, $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($this->collection, $this->start, $this->end, $this->primaryCurrency, $this->convertToPrimary);
}
}
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index b5d14b5ead..b45f30a310 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -127,7 +127,7 @@ class Steam
/**
* Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range.
*/
- public function accountsBalancesInRange(Carbon $start, Carbon $end, Collection $accounts, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ public function accountsBalancesInRange(Collection $accounts, Carbon $start, Carbon $end, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
return [
$this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false),
@@ -373,6 +373,9 @@ class Steam
return $final;
}
+ /**
+ * @deprecated
+ */
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array
{
// expand period.
From 547a4e9dbb39f03352e56d3f01ab6e918ca784c2 Mon Sep 17 00:00:00 2001
From: codearena-bot
Date: Wed, 8 Oct 2025 00:14:59 -0700
Subject: [PATCH 08/82] Update database/seeders/TransactionCurrencySeeder.php
via CodeArena
---
database/seeders/TransactionCurrencySeeder.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/database/seeders/TransactionCurrencySeeder.php b/database/seeders/TransactionCurrencySeeder.php
index ce75ec253d..d1aa49de4f 100644
--- a/database/seeders/TransactionCurrencySeeder.php
+++ b/database/seeders/TransactionCurrencySeeder.php
@@ -53,6 +53,10 @@ class TransactionCurrencySeeder extends Seeder
$currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2];
$currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2];
$currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2];
++ $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2];
++ $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2];
++ $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2];
++ $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2];
// oceanian currencies
$currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2];
From b03ecab035b1b986ce985cc1f8ab3f8bcee59227 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 09:17:56 +0200
Subject: [PATCH 09/82] Move to another method here as well.
---
app/Support/Steam.php | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index b45f30a310..0078fdfdf1 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -374,14 +374,14 @@ class Steam
}
/**
- * @deprecated
+ * Returns the balance for the given account in the range, with daily precision.
*/
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array
{
// expand period.
$start->startOfDay();
$end->endOfDay();
- Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
+ Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// set up cache
$cache = new CacheProperties();
@@ -391,28 +391,21 @@ class Steam
$cache->addProperty($convertToPrimary);
$cache->addProperty($end);
if ($cache->has()) {
- return $cache->get();
+ Log::debug('Return cached finalAccountBalanceInRange');
+ // return $cache->get();
}
$balances = [];
$formatted = $start->format('Y-m-d');
- /*
- * To make sure the start balance is correct, we need to get the balance at the exact end of the previous day.
- * Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment.
- * THAT will be the start balance.
- */
- $request = clone $start;
- $request->subDay()->endOfDay();
Log::debug('Get first balance to start.');
- Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
- $startBalance = $this->finalAccountBalance($account, $request);
+ // 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized:
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
+ $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id];
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = $accountCurrency instanceof TransactionCurrency;
$currency = $accountCurrency ?? $primaryCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
-
// set start balances:
$startBalance[$currency->code] ??= '0';
if ($hasCurrency) {
From 252459c29bf3db65ad8d3138cb99e195b7024745 Mon Sep 17 00:00:00 2001
From: JC5
Date: Wed, 8 Oct 2025 09:22:12 +0200
Subject: [PATCH 10/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-08?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Autocomplete/AutocompleteRequest.php | 3 +-
app/Api/V1/Requests/Data/DateRequest.php | 1 -
.../Extensions/AccountCollection.php | 2 +-
app/Helpers/Report/NetWorth.php | 2 -
app/Helpers/Report/NetWorthInterface.php | 2 -
.../Controllers/Account/IndexController.php | 30 +--
.../Account/ReconcileController.php | 16 +-
.../Controllers/Account/ShowController.php | 4 +-
.../Controllers/Chart/AccountController.php | 8 +-
.../Controllers/Json/ReconcileController.php | 8 +-
.../Transaction/ConvertController.php | 10 +-
app/Repositories/Account/AccountTasker.php | 2 +-
.../PiggyBank/PiggyBankRepository.php | 2 +-
app/Support/Steam.php | 173 +++++++++---------
app/Support/Twig/General.php | 2 +-
config/firefly.php | 4 +-
package-lock.json | 18 +-
17 files changed, 144 insertions(+), 143 deletions(-)
diff --git a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
index 6e8432ef45..63cb22077d 100644
--- a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
+++ b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php
@@ -48,7 +48,8 @@ class AutocompleteRequest extends FormRequest
// remove 'initial balance' from allowed types. its internal
$array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]);
- $date = $this->getCarbonDate('date') ?? today(config('app.timezone'));
+ $date = $this->getCarbonDate('date') ?? today(config('app.timezone'));
+
return [
'types' => $array,
'query' => $this->convertString('query'),
diff --git a/app/Api/V1/Requests/Data/DateRequest.php b/app/Api/V1/Requests/Data/DateRequest.php
index f5b37f7ca7..c112495d46 100644
--- a/app/Api/V1/Requests/Data/DateRequest.php
+++ b/app/Api/V1/Requests/Data/DateRequest.php
@@ -28,7 +28,6 @@ use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
-use Illuminate\Validation\Validator;
/**
* Request class for end points that require date parameters.
diff --git a/app/Helpers/Collector/Extensions/AccountCollection.php b/app/Helpers/Collector/Extensions/AccountCollection.php
index 837c353b41..203cad80ab 100644
--- a/app/Helpers/Collector/Extensions/AccountCollection.php
+++ b/app/Helpers/Collector/Extensions/AccountCollection.php
@@ -67,7 +67,7 @@ trait AccountCollection
// the balance must be found BEFORE the transaction date.
// so inclusive = false
Log::debug(sprintf('accountBalanceIs: Call accountsBalancesOptimized with date/time "%s"', $transaction['date']->toIso8601String()));
- $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id];
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id];
// $balance = Steam::finalAccountBalance($account, $date);
$result = bccomp((string) $balance['balance'], $value);
Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result));
diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php
index 80c0171ad6..b45659415c 100644
--- a/app/Helpers/Report/NetWorth.php
+++ b/app/Helpers/Report/NetWorth.php
@@ -25,8 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Report;
use Carbon\Carbon;
-use Deprecated;
-use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\UserGroup;
diff --git a/app/Helpers/Report/NetWorthInterface.php b/app/Helpers/Report/NetWorthInterface.php
index 8660ab4e4c..3870359766 100644
--- a/app/Helpers/Report/NetWorthInterface.php
+++ b/app/Helpers/Report/NetWorthInterface.php
@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Report;
use Carbon\Carbon;
-use Deprecated;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
@@ -49,5 +48,4 @@ interface NetWorthInterface
public function setUser(Authenticatable|User|null $user): void;
public function setUserGroup(UserGroup $userGroup): void;
-
}
diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php
index 64d8f37eb5..41ee6981b3 100644
--- a/app/Http/Controllers/Account/IndexController.php
+++ b/app/Http/Controllers/Account/IndexController.php
@@ -74,30 +74,30 @@ class IndexController extends Controller
*/
public function inactive(Request $request, string $objectType)
{
- $inactivePage = true;
- $subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType));
- $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
- $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType));
- $collection = $this->repository->getInactiveAccountsByType($types);
- $total = $collection->count();
- $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
- $pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
- $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
+ $inactivePage = true;
+ $subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType));
+ $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
+ $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType));
+ $collection = $this->repository->getInactiveAccountsByType($types);
+ $total = $collection->count();
+ $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
+ $pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
+ $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
unset($collection);
/** @var Carbon $start */
- $start = clone session('start', today(config('app.timezone'))->startOfMonth());
+ $start = clone session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */
- $end = clone session('end', today(config('app.timezone'))->endOfMonth());
+ $end = clone session('end', today(config('app.timezone'))->endOfMonth());
- $ids = $accounts->pluck('id')->toArray();
+ $ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
- $activities = Steam::getLastActivities($ids);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
+ $activities = Steam::getLastActivities($ids);
$accounts->each(
@@ -117,7 +117,7 @@ class IndexController extends Controller
);
// make paginator:
- $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
+ $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
$accounts->setPath(route('accounts.inactive.index', [$objectType]));
return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php
index 33da0dac0e..c78696f776 100644
--- a/app/Http/Controllers/Account/ReconcileController.php
+++ b/app/Http/Controllers/Account/ReconcileController.php
@@ -114,18 +114,18 @@ class ReconcileController extends Controller
$start->startOfDay();
$end->endOfDay();
-// $startDate = clone $start;
-// $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
+ // $startDate = clone $start;
+ // $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
// both are validated and are correct.
-// Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
-// Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
-// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
-// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+ // Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
+ // Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
+ // $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
+ // $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
// 2025-10-08 replace with accountsBalancesOptimized
// no longer need to do subday->endofday on $start, set inclusive = false for the same effect.
- $startBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places);
- $endBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places);
+ $startBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places);
+ $endBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places);
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php
index 104caabefd..bcb0ba6a0e 100644
--- a/app/Http/Controllers/Account/ShowController.php
+++ b/app/Http/Controllers/Account/ShowController.php
@@ -170,7 +170,7 @@ class ShowController extends Controller
}
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
- $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id];
+ $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id];
// $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
return view(
@@ -243,7 +243,7 @@ class ShowController extends Controller
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
// $balances = Steam::finalAccountBalance($account, $end);
// $balances = Steam::filterAccountBalance($balances, $account, $this->convertToPrimary, $accountCurrency);
- $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
+ $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
return view(
'accounts.show',
diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php
index 5d09db1267..ada7f9014e 100644
--- a/app/Http/Controllers/Chart/AccountController.php
+++ b/app/Http/Controllers/Chart/AccountController.php
@@ -522,9 +522,9 @@ class AccountController extends Controller
$range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency);
Log::debug('Get and filter balance for entire range end');
// temp, get end balance.
-// Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
-// Steam::finalAccountBalance($account, $end);
-// Log::debug('END temp get end balance done');
+ // Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
+ // Steam::finalAccountBalance($account, $end);
+ // Log::debug('END temp get end balance done');
$previous = array_values($range)[0];
$accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances.
@@ -671,7 +671,7 @@ class AccountController extends Controller
[
$startBalances,
$endBalances,
- ] = Steam::accountsBalancesInRange($accounts, $start, $end , $this->primaryCurrency, $this->convertToPrimary);
+ ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.
diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php
index b2d33c7fe9..763d306275 100644
--- a/app/Http/Controllers/Json/ReconcileController.php
+++ b/app/Http/Controllers/Json/ReconcileController.php
@@ -201,11 +201,11 @@ class ReconcileController extends Controller
Log::debug(sprintf('transactions2: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized
-// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
-// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
+ // $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
+ // $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
- $startBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id];
- $endBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
+ $startBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id];
+ $endBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
// get the transactions
diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php
index 655aeeb594..5b26839211 100644
--- a/app/Http/Controllers/Transaction/ConvertController.php
+++ b/app/Http/Controllers/Transaction/ConvertController.php
@@ -232,10 +232,10 @@ class ConvertController extends Controller
Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
- $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
- $role = sprintf('l_%s',$account->accountType->type);
- $key = (string) trans(sprintf('firefly.opt_group_%s',$role));
+ $role = sprintf('l_%s', $account->accountType->type);
+ $key = (string) trans(sprintf('firefly.opt_group_%s', $role));
$grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
}
@@ -258,7 +258,7 @@ class ConvertController extends Controller
Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
- $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
@@ -266,7 +266,7 @@ class ConvertController extends Controller
$role = 'no_account_type';
}
- $key = (string) trans(sprintf('firefly.opt_group_%s',$role));
+ $key = (string) trans(sprintf('firefly.opt_group_%s', $role));
$grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
}
diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php
index 119ea48426..4278ac4183 100644
--- a/app/Repositories/Account/AccountTasker.php
+++ b/app/Repositories/Account/AccountTasker.php
@@ -54,7 +54,7 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
[
$startSet,
$endSet,
- ] = Steam::accountsBalancesInRange($accounts, $yesterday, $end);
+ ] = Steam::accountsBalancesInRange($accounts, $yesterday, $end);
Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index 5ee5617130..2dbdda5dc3 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -377,7 +377,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
$balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
- //$balance = Steam::finalAccountBalance($account, $date)['balance'];
+ // $balance = Steam::finalAccountBalance($account, $date)['balance'];
Log::debug(sprintf('Balance is: %s', $balance));
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 0078fdfdf1..586ecd4a7b 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -40,6 +40,7 @@ use Illuminate\Support\Str;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use ValueError;
+
use function Safe\parse_url;
use function Safe\preg_replace;
@@ -51,45 +52,47 @@ class Steam
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{
Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true)));
- $result = [];
+ $result = [];
$convertToPrimary ??= Amount::convertToPrimary();
$primary ??= Amount::getPrimaryCurrency();
- $currencies = $this->getCurrencies($accounts);
+ $currencies = $this->getCurrencies($accounts);
// balance(s) in all currencies for ALL accounts.
$arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
- ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
- ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray();
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
+ ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
+ ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
+ ;
Log::debug('Array of sums: ', $arrayOfSums);
+
/** @var Account $account */
foreach ($accounts as $account) {
- $return = [
+ $return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
- $currency = $currencies[$account->id];
+ $currency = $currencies[$account->id];
// second array
- $accountSums = array_filter($arrayOfSums, fn($entry) => $entry['account_id'] === $account->id);
+ $accountSums = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id);
if (0 === count($accountSums)) {
$result[$account->id] = $return;
continue;
}
- $sumsByCode = [];
- foreach($accountSums as $accountSum) {
+ $sumsByCode = [];
+ foreach ($accountSums as $accountSum) {
// $accountSum = array_values($accountSum)[0];
- $sumOfAmount = (string)$accountSum['sum_of_amount'];
- $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
+ $sumOfAmount = (string)$accountSum['sum_of_amount'];
+ $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
$sumsByCode[$accountSum['code']] = $sumOfAmount;
}
// Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
- $return['balance'] = $sumsByCode[$currency->code] ?? '0';
+ $return['balance'] = $sumsByCode[$currency->code] ?? '0';
if (!$convertToPrimary) {
unset($return['pc_balance']);
// Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
@@ -102,7 +105,7 @@ class Steam
}
// either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
@@ -118,7 +121,7 @@ class Steam
}
$final = array_merge($return, $sumsByCode);
$result[$account->id] = $final;
- Log::debug(sprintf('Final balance for account #%d is',$account->id), $final);
+ Log::debug(sprintf('Final balance for account #%d is', $account->id), $final);
}
return $result;
@@ -154,10 +157,10 @@ class Steam
// Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
if (str_contains($number, '.')) {
if ('-' !== $number[0]) {
- return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
+ return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision);
}
- return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
+ return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision);
}
return $number;
@@ -303,7 +306,7 @@ class Steam
public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{
- $cache = new CacheProperties();
+ $cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty($date);
if ($cache->has()) {
@@ -318,26 +321,27 @@ class Steam
$primary = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
}
// account balance thing.
- $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
+ $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$accountCurrency = $account->meta['currency'];
}
if (!$currencyPresent) {
$accountCurrency = $this->getAccountCurrency($account);
}
- $hasCurrency = null !== $accountCurrency;
- $currency = $hasCurrency ? $accountCurrency : $primary;
- $return = [
+ $hasCurrency = null !== $accountCurrency;
+ $currency = $hasCurrency ? $accountCurrency : $primary;
+ $return = [
'pc_balance' => '0',
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
];
// balance(s) in all currencies.
- $array = $account->transactions()
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
- ->get(['transaction_currencies.code', 'transactions.amount'])->toArray();
- $others = $this->groupAndSumTransactions($array, 'code', 'amount');
+ $array = $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
+ ->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
+ ;
+ $others = $this->groupAndSumTransactions($array, 'code', 'amount');
Log::debug('All balances are (joined)', $others);
// if there is no request to convert, take this as "balance" and "pc_balance".
$return['balance'] = $others[$currency->code] ?? '0';
@@ -352,7 +356,7 @@ class Steam
}
// either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
if ($convertToPrimary) {
// the primary currency balance is combined with a converted virtual_balance:
@@ -366,7 +370,7 @@ class Steam
$return['balance'] = bcadd($return['balance'], $virtualBalance);
// Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
}
- $final = array_merge($return, $others);
+ $final = array_merge($return, $others);
Log::debug('Final balance is', $final);
$cache->store($final);
@@ -384,7 +388,7 @@ class Steam
Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// set up cache
- $cache = new CacheProperties();
+ $cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('final-balance-in-range');
$cache->addProperty($start);
@@ -395,15 +399,15 @@ class Steam
// return $cache->get();
}
- $balances = [];
- $formatted = $start->format('Y-m-d');
+ $balances = [];
+ $formatted = $start->format('Y-m-d');
Log::debug('Get first balance to start.');
// 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized:
- $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
- $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id];
- $accountCurrency = $this->getAccountCurrency($account);
- $hasCurrency = $accountCurrency instanceof TransactionCurrency;
- $currency = $accountCurrency ?? $primaryCurrency;
+ $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
+ $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id];
+ $accountCurrency = $this->getAccountCurrency($account);
+ $hasCurrency = $accountCurrency instanceof TransactionCurrency;
+ $currency = $accountCurrency ?? $primaryCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
// set start balances:
@@ -415,7 +419,7 @@ class Steam
Log::debug(sprintf('Also set start balance in %s', $primaryCurrency->code));
$startBalance[$primaryCurrency->code] ??= '0';
}
- $currencies = [
+ $currencies = [
$currency->id => $currency,
$primaryCurrency->id => $primaryCurrency,
];
@@ -425,47 +429,48 @@ class Steam
// sums up the balance changes per day.
Log::debug(sprintf('Date >= %s and <= %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
- $set = $account->transactions()
- ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
- ->groupBy('transaction_journals.date')
- ->groupBy('transactions.transaction_currency_id')
- ->orderBy('transaction_journals.date', 'ASC')
- ->whereNull('transaction_journals.deleted_at')
- ->get(
- [ // @phpstan-ignore-line
- 'transaction_journals.date',
- 'transactions.transaction_currency_id',
- DB::raw('SUM(transactions.amount) AS sum_of_day'),
- ]
- );
+ $set = $account->transactions()
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
+ ->groupBy('transaction_journals.date')
+ ->groupBy('transactions.transaction_currency_id')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ [ // @phpstan-ignore-line
+ 'transaction_journals.date',
+ 'transactions.transaction_currency_id',
+ DB::raw('SUM(transactions.amount) AS sum_of_day'),
+ ]
+ )
+ ;
- $currentBalance = $startBalance;
- $converter = new ExchangeRateConverter();
+ $currentBalance = $startBalance;
+ $converter = new ExchangeRateConverter();
/** @var Transaction $entry */
foreach ($set as $entry) {
// get date object
- $carbon = new Carbon($entry->date, $entry->date_tz);
- $carbonKey = $carbon->format('Y-m-d');
+ $carbon = new Carbon($entry->date, $entry->date_tz);
+ $carbonKey = $carbon->format('Y-m-d');
// make sure sum is a string:
- $sumOfDay = (string)($entry->sum_of_day ?? '0');
+ $sumOfDay = (string)($entry->sum_of_day ?? '0');
// #10426 make sure sum is not in scientific notation.
- $sumOfDay = $this->floatalize($sumOfDay);
+ $sumOfDay = $this->floatalize($sumOfDay);
// find currency of this entry, does not have to exist.
$currencies[$entry->transaction_currency_id] ??= Amount::getTransactionCurrencyById($entry->transaction_currency_id);
// make sure this $entry has its own $entryCurrency
/** @var TransactionCurrency $entryCurrency */
- $entryCurrency = $currencies[$entry->transaction_currency_id];
+ $entryCurrency = $currencies[$entry->transaction_currency_id];
Log::debug(sprintf('Processing transaction(s) on moment %s', $carbon->format('Y-m-d H:i:s')));
// add amount to current balance in currency code.
- $currentBalance[$entryCurrency->code] ??= '0';
+ $currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string)$currentBalance[$entryCurrency->code]);
// if not requested to convert to primary currency, add the amount to "balance", do nothing else.
@@ -483,7 +488,7 @@ class Steam
}
}
// add to final array.
- $balances[$carbonKey] = $currentBalance;
+ $balances[$carbonKey] = $currentBalance;
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
}
$cache->store($balances);
@@ -500,7 +505,7 @@ class Steam
*/
public function floatalize(string $value): string
{
- $value = strtoupper($value);
+ $value = strtoupper($value);
if (!str_contains($value, 'E')) {
return $value;
}
@@ -524,8 +529,8 @@ class Steam
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
- $type = $account->accountType->type;
- $list = config('firefly.valid_currency_account_types');
+ $type = $account->accountType->type;
+ $list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
@@ -578,15 +583,15 @@ class Steam
{
$list = [];
- $set = auth()->user()->transactions()
- ->whereIn('transactions.account_id', $accounts)
- ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
- ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
+ $set = auth()->user()->transactions()
+ ->whereIn('transactions.account_id', $accounts)
+ ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
+ ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
;
/** @var Transaction $entry */
foreach ($set as $entry) {
- $date = new Carbon($entry->max_date, config('app.timezone'));
+ $date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[(int)$entry->account_id] = $date;
}
@@ -604,14 +609,14 @@ class Steam
if (null !== $cached) {
return $cached;
}
- $locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
+ $locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
if (is_array($locale)) {
$locale = 'equal';
}
if ('equal' === $locale) {
$locale = $this->getLanguage();
}
- $locale = (string)$locale;
+ $locale = (string)$locale;
// Check for Windows to replace the locale correctly.
if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
@@ -652,9 +657,9 @@ class Steam
public function getSafeUrl(string $unknownUrl, string $safeUrl): string
{
// Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
- $returnUrl = $safeUrl;
- $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
- $safeHost = parse_url($safeUrl, PHP_URL_HOST);
+ $returnUrl = $safeUrl;
+ $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
+ $safeHost = parse_url($safeUrl, PHP_URL_HOST);
if (null !== $unknownHost && $unknownHost === $safeHost) {
$returnUrl = $unknownUrl;
@@ -756,12 +761,12 @@ class Steam
if (null === $preference) {
$singleton->setPreference($key, $currency);
}
- $current = $amount;
+ $current = $amount;
if ($currency->id !== $primary->id) {
$current = $converter->convert($currency, $primary, $date, $amount);
Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current));
}
- $total = bcadd((string)$current, $total);
+ $total = bcadd((string)$current, $total);
}
return $total;
@@ -775,8 +780,8 @@ class Steam
$primary = Amount::getPrimaryCurrency();
$currencies[$primary->id] = $primary;
- $ids = $accounts->pluck('id')->toArray();
- $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
+ $ids = $accounts->pluck('id')->toArray();
+ $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
@@ -786,7 +791,7 @@ class Steam
}
}
// collect those currencies, skip primary because we already have it.
- $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
+ $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
foreach ($set as $item) {
$currencies[$item->id] = $item;
}
@@ -797,7 +802,7 @@ class Steam
$currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$currencyId = $account->meta['currency']->id;
- $currencies[$currencyId] ??= $account->meta['currency'];
+ $currencies[$currencyId] ??= $account->meta['currency'];
$accountCurrencies[$accountId] = $account->meta['currency'];
}
if (!$currencyPresent && !array_key_exists($accountId, $accountPreferences)) {
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index 9208875ed2..80d72a9f75 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -160,7 +160,7 @@ class General extends AbstractExtension
Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
- $info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
+ $info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
// $info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account);
$primary = Amount::getPrimaryCurrency();
diff --git a/config/firefly.php b/config/firefly.php
index db7f7d8cb4..c2872fa1b5 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-07',
- 'build_time' => 1759859933,
+ 'version' => 'develop/2025-10-08',
+ 'build_time' => 1759908015,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 670e032192..af16b7af1a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4521,9 +4521,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001748",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
- "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
+ "version": "1.0.30001749",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
+ "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
"dev": true,
"funding": [
{
@@ -5736,9 +5736,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.232",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz",
- "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==",
+ "version": "1.5.233",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz",
+ "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==",
"dev": true,
"license": "ISC"
},
@@ -10290,9 +10290,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
From a3cac6fd0fd1753e9b69bd79ed290a293fabc916 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 15:16:10 +0200
Subject: [PATCH 11/82] Fix #11031
---
.../RuleGroup/RuleGroupRepository.php | 31 ++++++++++---------
.../Engine/SearchRuleEngine.php | 19 ++++++++++--
2 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php
index a3256ccf50..4e99d53728 100644
--- a/app/Repositories/RuleGroup/RuleGroupRepository.php
+++ b/app/Repositories/RuleGroup/RuleGroupRepository.php
@@ -139,7 +139,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
/** @var Rule $entry */
foreach ($set as $entry) {
if ($entry->order !== $count) {
- app('log')->debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count));
+ Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count));
$entry->order = $count;
$entry->save();
}
@@ -167,7 +167,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
if ($action->order !== $index) {
$action->order = $index;
$action->save();
- app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
+ Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
}
++$index;
}
@@ -189,7 +189,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
if ($order !== $index) {
$trigger->order = $index;
$trigger->save();
- app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
+ Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
}
++$index;
}
@@ -235,6 +235,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function getActiveStoreRules(RuleGroup $group): Collection
{
return $group->rules()
+ ->orderBy('rules.order', 'ASC')
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'store-journal')
@@ -275,23 +276,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
if (null === $filter) {
return $groups;
}
- app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
+ Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
return $groups->map(
static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
- app('log')->debug(sprintf('Now filtering group #%d', $group->id));
+ Log::debug(sprintf('Now filtering group #%d', $group->id));
// filter the rules in the rule group:
$group->rules = $group->rules->filter(
static function (Rule $rule) use ($filter) {
- app('log')->debug(sprintf('Now filtering rule #%d', $rule->id));
+ Log::debug(sprintf('Now filtering rule #%d', $rule->id));
foreach ($rule->ruleTriggers as $trigger) {
if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) {
- app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
+ Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
return true;
}
}
- app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
+ Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
return false;
}
@@ -331,23 +332,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
if (null === $filter) {
return $groups;
}
- app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
+ Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
return $groups->map(
static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
- app('log')->debug(sprintf('Now filtering group #%d', $group->id));
+ Log::debug(sprintf('Now filtering group #%d', $group->id));
// filter the rules in the rule group:
$group->rules = $group->rules->filter(
static function (Rule $rule) use ($filter) {
- app('log')->debug(sprintf('Now filtering rule #%d', $rule->id));
+ Log::debug(sprintf('Now filtering rule #%d', $rule->id));
foreach ($rule->ruleTriggers as $trigger) {
if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) {
- app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
+ Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
return true;
}
}
- app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
+ Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
return false;
}
@@ -414,7 +415,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
->decrement('order')
;
$ruleGroup->order = $newOrder;
- app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
+ Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
$ruleGroup->save();
return;
@@ -425,7 +426,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
->increment('order')
;
$ruleGroup->order = $newOrder;
- app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
+ Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
$ruleGroup->save();
}
diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php
index 7843d1ffca..f5802bc2ed 100644
--- a/app/TransactionRules/Engine/SearchRuleEngine.php
+++ b/app/TransactionRules/Engine/SearchRuleEngine.php
@@ -506,9 +506,22 @@ class SearchRuleEngine implements RuleEngineInterface
*/
private function fireGroup(RuleGroup $group): void
{
- Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count()));
-
- $rules = $group->rules()->orderBy('order', 'ASC')->get();
+ $rules =[];
+ if($group->relationLoaded('rules')) {
+ Log::debug('Group rules have been pre-loaded, do not reload them.');
+ $rules = $group->rules;
+ }
+ if(!$group->relationLoaded('rules')) {
+ Log::debug('Group rules have NOT been pre-loaded, load them NOW.');
+ $rules = $group->rules()
+ ->orderBy('rules.order', 'ASC')
+// ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
+// ->where('rule_triggers.trigger_type', 'user_action')
+// ->where('rule_triggers.trigger_value', 'store-journal')
+ ->where('rules.active', true)
+ ->get(['rules.*']);
+ }
+ Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count()));
/** @var Rule $rule */
foreach ($rules as $rule) {
From 3ab65c27acdc7cb618567ecc6517c295de7d023c Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 15:47:03 +0200
Subject: [PATCH 12/82] Fix missing array indicator.
---
app/Repositories/PiggyBank/PiggyBankRepository.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php
index 2dbdda5dc3..022de3a98c 100644
--- a/app/Repositories/PiggyBank/PiggyBankRepository.php
+++ b/app/Repositories/PiggyBank/PiggyBankRepository.php
@@ -376,7 +376,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s')));
Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
- $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
+ $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'];
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
Log::debug(sprintf('Balance is: %s', $balance));
From 8979e5ad5a9131c971ce2d8a7baaa7f7bf86432e Mon Sep 17 00:00:00 2001
From: JC5
Date: Wed, 8 Oct 2025 15:51:04 +0200
Subject: [PATCH 13/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-08?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
THANKS.md | 1 +
app/TransactionRules/Engine/SearchRuleEngine.php | 13 +++++++------
config/firefly.php | 2 +-
package-lock.json | 10 +++++++---
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/THANKS.md b/THANKS.md
index 2839b6f7f6..e4e9d01869 100755
--- a/THANKS.md
+++ b/THANKS.md
@@ -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.
## 2025
+- codearena-bot
- Nicky De Maeyer
- Denis Iskandarov
- Lompi
diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php
index f5802bc2ed..72f7228f20 100644
--- a/app/TransactionRules/Engine/SearchRuleEngine.php
+++ b/app/TransactionRules/Engine/SearchRuleEngine.php
@@ -506,20 +506,21 @@ class SearchRuleEngine implements RuleEngineInterface
*/
private function fireGroup(RuleGroup $group): void
{
- $rules =[];
- if($group->relationLoaded('rules')) {
+ $rules = [];
+ if ($group->relationLoaded('rules')) {
Log::debug('Group rules have been pre-loaded, do not reload them.');
$rules = $group->rules;
}
- if(!$group->relationLoaded('rules')) {
+ if (!$group->relationLoaded('rules')) {
Log::debug('Group rules have NOT been pre-loaded, load them NOW.');
$rules = $group->rules()
- ->orderBy('rules.order', 'ASC')
+ ->orderBy('rules.order', 'ASC')
// ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
// ->where('rule_triggers.trigger_type', 'user_action')
// ->where('rule_triggers.trigger_value', 'store-journal')
- ->where('rules.active', true)
- ->get(['rules.*']);
+ ->where('rules.active', true)
+ ->get(['rules.*'])
+ ;
}
Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count()));
diff --git a/config/firefly.php b/config/firefly.php
index c2872fa1b5..ef01270bb8 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-08',
- 'build_time' => 1759908015,
+ 'build_time' => 1759931348,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index af16b7af1a..00f94f6f7a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7908,13 +7908,17 @@
"license": "MIT"
},
"node_modules/loader-runner": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
- "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
+ "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.11.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
"node_modules/loader-utils": {
From 469319a24094e130cc2ef728f5f1509c73425c1b Mon Sep 17 00:00:00 2001
From: James Cole
Date: Wed, 8 Oct 2025 15:56:33 +0200
Subject: [PATCH 14/82] Remove plusses.
---
database/seeders/TransactionCurrencySeeder.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/database/seeders/TransactionCurrencySeeder.php b/database/seeders/TransactionCurrencySeeder.php
index d1aa49de4f..5673362389 100644
--- a/database/seeders/TransactionCurrencySeeder.php
+++ b/database/seeders/TransactionCurrencySeeder.php
@@ -53,10 +53,10 @@ class TransactionCurrencySeeder extends Seeder
$currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2];
$currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2];
$currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2];
-+ $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2];
-+ $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2];
-+ $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2];
-+ $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2];
+ $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2];
+ $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2];
+ $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2];
+ $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2];
// oceanian currencies
$currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2];
From 77b88b7758067703a5712795c0f9d2b281d98d3a Mon Sep 17 00:00:00 2001
From: JC5
Date: Wed, 8 Oct 2025 16:04:50 +0200
Subject: [PATCH 15/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-08?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/firefly.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/firefly.php b/config/firefly.php
index ef01270bb8..9f209a8c1b 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-08',
- 'build_time' => 1759931348,
+ 'build_time' => 1759932174,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From 132d7d9ff8b68640c40a628ac52940b16c64250d Mon Sep 17 00:00:00 2001
From: James Cole
Date: Thu, 9 Oct 2025 06:27:15 +0200
Subject: [PATCH 16/82] Update some logging.
---
app/Factory/PiggyBankEventFactory.php | 6 +++---
app/Repositories/RuleGroup/RuleGroupRepository.php | 2 ++
app/TransactionRules/Actions/UpdatePiggyBank.php | 4 +---
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php
index 52643c55f7..09a80af0ff 100644
--- a/app/Factory/PiggyBankEventFactory.php
+++ b/app/Factory/PiggyBankEventFactory.php
@@ -36,9 +36,9 @@ class PiggyBankEventFactory
{
public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): void
{
- app('log')->debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type));
+ Log::debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type));
if (!$piggyBank instanceof PiggyBank) {
- app('log')->debug('Piggy bank is null');
+ Log::debug('Piggy bank is null');
return;
}
@@ -49,7 +49,7 @@ class PiggyBankEventFactory
$amount = $piggyRepos->getExactAmount($piggyBank, $journal);
if (0 === bccomp($amount, '0')) {
- app('log')->debug('Amount is zero, will not create event.');
+ Log::debug('Amount is zero, will not create event.');
return;
}
diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php
index 4e99d53728..8ef670d2b2 100644
--- a/app/Repositories/RuleGroup/RuleGroupRepository.php
+++ b/app/Repositories/RuleGroup/RuleGroupRepository.php
@@ -263,6 +263,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
+ $query->where('rules.active', true);
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
@@ -319,6 +320,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
+ $query->where('rules.active', true);
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
diff --git a/app/TransactionRules/Actions/UpdatePiggyBank.php b/app/TransactionRules/Actions/UpdatePiggyBank.php
index 9e21497d97..7a0bd2daaf 100644
--- a/app/TransactionRules/Actions/UpdatePiggyBank.php
+++ b/app/TransactionRules/Actions/UpdatePiggyBank.php
@@ -59,9 +59,7 @@ class UpdatePiggyBank implements ActionInterface
$piggyBank = $this->findPiggyBank($user, $actionValue);
if (!$piggyBank instanceof PiggyBank) {
- Log::info(
- sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)
- );
+ 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])));
return false;
From b77a8591dc64971589feb8b6a7a3814955377554 Mon Sep 17 00:00:00 2001
From: JC5
Date: Thu, 9 Oct 2025 10:52:43 +0200
Subject: [PATCH 17/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-09?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
composer.lock | 26 +++++++++++++-------------
config/firefly.php | 4 ++--
package-lock.json | 6 +++---
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/composer.lock b/composer.lock
index cc8d7c5359..5f9c29b59a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2485,34 +2485,34 @@
},
{
"name": "lcobucci/clock",
- "version": "3.3.1",
+ "version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/clock.git",
- "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b"
+ "reference": "f91d84f65cb3e974988bbe872b5da8ca132a155f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b",
- "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b",
+ "url": "https://api.github.com/repos/lcobucci/clock/zipball/f91d84f65cb3e974988bbe872b5da8ca132a155f",
+ "reference": "f91d84f65cb3e974988bbe872b5da8ca132a155f",
"shasum": ""
},
"require": {
- "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
+ "php": "~8.3.0 || ~8.4.0",
"psr/clock": "^1.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
- "infection/infection": "^0.29",
+ "infection/infection": "^0.31",
"lcobucci/coding-standard": "^11.1.0",
"phpstan/extension-installer": "^1.3.1",
- "phpstan/phpstan": "^1.10.25",
- "phpstan/phpstan-deprecation-rules": "^1.1.3",
- "phpstan/phpstan-phpunit": "^1.3.13",
- "phpstan/phpstan-strict-rules": "^1.5.1",
- "phpunit/phpunit": "^11.3.6"
+ "phpstan/phpstan": "^2.0.0",
+ "phpstan/phpstan-deprecation-rules": "^2.0.0",
+ "phpstan/phpstan-phpunit": "^2.0.0",
+ "phpstan/phpstan-strict-rules": "^2.0.0",
+ "phpunit/phpunit": "^12.0.0"
},
"type": "library",
"autoload": {
@@ -2533,7 +2533,7 @@
"description": "Yet another clock abstraction",
"support": {
"issues": "https://github.com/lcobucci/clock/issues",
- "source": "https://github.com/lcobucci/clock/tree/3.3.1"
+ "source": "https://github.com/lcobucci/clock/tree/3.4.0"
},
"funding": [
{
@@ -2545,7 +2545,7 @@
"type": "patreon"
}
],
- "time": "2024-09-24T20:45:14+00:00"
+ "time": "2025-10-08T18:00:48+00:00"
},
{
"name": "lcobucci/jwt",
diff --git a/config/firefly.php b/config/firefly.php
index 9f209a8c1b..e11aabd870 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-08',
- 'build_time' => 1759932174,
+ 'version' => 'develop/2025-10-09',
+ 'build_time' => 1759999856,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 00f94f6f7a..1979ad9a2e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.13",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz",
- "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==",
+ "version": "2.8.14",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz",
+ "integrity": "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
From d4978a09eec7e41e6bc3263b3a76f091186470d0 Mon Sep 17 00:00:00 2001
From: Sander Dorigo
Date: Thu, 9 Oct 2025 12:35:51 +0200
Subject: [PATCH 18/82] Fix #11038
---
app/Support/Navigation.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index dd5981e327..85c50ed24c 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -420,6 +420,7 @@ class Navigation
'week' => (string)trans('config.week_in_year_js'),
'weekly' => (string)trans('config.week_in_year_js'),
'1M' => (string)trans('config.month_js'),
+ 'MTD' => (string)trans('config.month_js'),
'month' => (string)trans('config.month_js'),
'monthly' => (string)trans('config.month_js'),
'1Y' => (string)trans('config.year_js'),
From 52f3ec7d3d45113450f0df451953b9dae9f03e0b Mon Sep 17 00:00:00 2001
From: Nicky De Maeyer
Date: Tue, 7 Oct 2025 14:32:23 +0200
Subject: [PATCH 19/82] improved request handling
---
app/Api/V1/Controllers/Controller.php | 2 +
.../Models/Account/ShowController.php | 50 +++++-----
.../Models/UserGroup/IndexController.php | 16 ++--
.../Controllers/Summary/BasicController.php | 6 +-
app/Api/V1/Requests/AggregateFormRequest.php | 92 +++++++++++++++++++
app/Api/V1/Requests/ApiRequest.php | 45 +++++++++
app/Api/V1/Requests/Data/DateRequest.php | 82 -----------------
app/Api/V1/Requests/DateRangeRequest.php | 56 +++++++++++
app/Api/V1/Requests/DateRequest.php | 57 ++++++++++++
.../Models/Account/AccountTypeApiRequest.php | 57 ++++++++++++
.../Requests/Models/Account/ShowRequest.php | 76 ++-------------
app/Api/V1/Requests/PaginationRequest.php | 85 +++++++++++++++++
12 files changed, 442 insertions(+), 182 deletions(-)
create mode 100644 app/Api/V1/Requests/AggregateFormRequest.php
create mode 100644 app/Api/V1/Requests/ApiRequest.php
delete mode 100644 app/Api/V1/Requests/Data/DateRequest.php
create mode 100644 app/Api/V1/Requests/DateRangeRequest.php
create mode 100644 app/Api/V1/Requests/DateRequest.php
create mode 100644 app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
create mode 100644 app/Api/V1/Requests/PaginationRequest.php
diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php
index 298bc20cc7..d83c713aa1 100644
--- a/app/Api/V1/Controllers/Controller.php
+++ b/app/Api/V1/Controllers/Controller.php
@@ -67,6 +67,7 @@ abstract class Controller extends BaseController
protected bool $convertToPrimary = false;
protected TransactionCurrency $primaryCurrency;
+ /** @deprecated use Request classes */
protected ParameterBag $parameters;
/**
@@ -98,6 +99,7 @@ abstract class Controller extends BaseController
}
/**
+ * @deprecated use Request classes
* Method to grab all parameters from the URL.
*/
private function getParameters(): ParameterBag
diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php
index 587076d0db..cb0097062e 100644
--- a/app/Api/V1/Controllers/Models/Account/ShowController.php
+++ b/app/Api/V1/Controllers/Models/Account/ShowController.php
@@ -71,40 +71,40 @@ class ShowController extends Controller
public function index(ShowRequest $request): JsonResponse
{
$manager = $this->getManager();
- $params = $request->getParameters();
- $this->parameters->set('type', $params['type']);
-
- // types to get, page size:
- $types = $this->mapAccountTypes($params['type']);
+ [
+ 'types' => $types,
+ 'page' => $page,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'sort' => $sort,
+ 'start' => $start,
+ 'end' => $end,
+ 'date' => $date,
+ ] = $request->attributes->all();
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
- $collection = $this->repository->getAccountsByType($types, $params['sort']);
+ $collection = $this->repository->getAccountsByType($types, $sort);
$count = $collection->count();
// continue sort:
// TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after.
// TODO still need to figure out how to do this easily.
- $accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']);
-
- // #11007 go to the end of the previous day.
- $this->parameters->set('start', $this->parameters->get('start')?->subSecond());
- // #11018 also end of the day.
- $this->parameters->set('end', $this->parameters->get('end')?->endOfDay());
+ $accounts = $collection->slice($offset, $limit);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
- $enrichment->setSort($params['sort']);
- $enrichment->setDate($this->parameters->get('date'));
- $enrichment->setStart($this->parameters->get('start'));
- $enrichment->setEnd($this->parameters->get('end'));
+ $enrichment->setSort($sort);
+ $enrichment->setDate($date);
+ $enrichment->setStart($start);
+ $enrichment->setEnd($end);
$enrichment->setUser($admin);
$accounts = $enrichment->enrich($accounts);
// make paginator:
- $paginator = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page'));
+ $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page);
$paginator->setPath(route('api.v1.accounts.index').$this->buildParams());
/** @var AccountTransformer $transformer */
@@ -129,19 +129,19 @@ class ShowController extends Controller
$this->repository->resetAccountOrder();
$account->refresh();
$manager = $this->getManager();
-
- // #11007 go to the end of the previous day.
- $this->parameters->set('start', $this->parameters->get('start')?->subSecond());
- // #11018 also end of the day.
- $this->parameters->set('end', $this->parameters->get('end')?->endOfDay());
+ [
+ 'start' => $start,
+ 'end' => $end,
+ 'date' => $date,
+ ] = $request->attributes->all();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new AccountEnrichment();
- $enrichment->setDate($this->parameters->get('date'));
- $enrichment->setStart($this->parameters->get('start'));
- $enrichment->setEnd($this->parameters->get('end'));
+ $enrichment->setDate($date);
+ $enrichment->setStart($start);
+ $enrichment->setEnd($end);
$enrichment->setUser($admin);
$account = $enrichment->enrichSingle($account);
diff --git a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
index e689687467..8c4ba0ddbb 100644
--- a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
+++ b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\UserGroup;
use FireflyIII\Api\V1\Controllers\Controller;
-use FireflyIII\Api\V1\Requests\Data\DateRequest;
+use FireflyIII\Api\V1\Requests\PaginationRequest;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\Transformers\UserGroupTransformer;
use Illuminate\Http\JsonResponse;
@@ -52,16 +52,20 @@ class IndexController extends Controller
);
}
- public function index(DateRequest $request): JsonResponse
+ public function index(PaginationRequest $request): JsonResponse
{
$administrations = $this->repository->get();
- $pageSize = $this->parameters->get('limit');
+ [
+ 'page' => $page,
+ 'limit' => $limit,
+ 'offset' => $offset,
+ ] = $request->attributes->all();
$count = $administrations->count();
- $administrations = $administrations->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
- $paginator = new LengthAwarePaginator($administrations, $count, $pageSize, $this->parameters->get('page'));
+ $administrations = $administrations->slice($offset, $limit);
+ $paginator = new LengthAwarePaginator($administrations, $count, $limit, $page);
$transformer = new UserGroupTransformer();
- $transformer->setParameters($this->parameters); // give params to transformer
+ $transformer->setParameters($request->attributes); // give params to transformer
return response()
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php
index 64c39e5766..6822674139 100644
--- a/app/Api/V1/Controllers/Summary/BasicController.php
+++ b/app/Api/V1/Controllers/Summary/BasicController.php
@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Summary;
use Carbon\Carbon;
use Exception;
use FireflyIII\Api\V1\Controllers\Controller;
-use FireflyIII\Api\V1\Requests\Data\DateRequest;
+use FireflyIII\Api\V1\Requests\DateRangeRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -94,10 +94,10 @@ class BasicController extends Controller
*
* @throws Exception
*/
- public function basic(DateRequest $request): JsonResponse
+ public function basic(DateRangeRequest $request): JsonResponse
{
// parameters for boxes:
- $dates = $request->getAll();
+ $dates = $request->attributes->all();
$start = $dates['start'];
$end = $dates['end'];
$code = $request->get('currency_code');
diff --git a/app/Api/V1/Requests/AggregateFormRequest.php b/app/Api/V1/Requests/AggregateFormRequest.php
new file mode 100644
index 0000000000..207ce0640d
--- /dev/null
+++ b/app/Api/V1/Requests/AggregateFormRequest.php
@@ -0,0 +1,92 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace FireflyIII\Api\V1\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Validation\Validator;
+
+abstract class AggregateFormRequest extends ApiRequest
+{
+ /**
+ * @var ApiRequest[]
+ */
+ protected array $requests = [];
+
+ /** @return class-string[] */
+ abstract protected function getRequests(): array;
+
+ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): void
+ {
+ parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
+
+ // instantiate all subrequests and share current requests' bags with them
+ foreach ($this->getRequests() as $config) {
+ $requestClass = is_array($config) ? array_shift($config) : $config;
+
+ if (!is_a($requestClass, Request::class, true)) {
+ throw new \RuntimeException('getRequests() must return class-strings of subclasses of Request');
+ }
+
+ $instance = $this->requests[] = new $requestClass();
+ $instance->request = $this->request;
+ $instance->query = $this->query;
+ $instance->attributes = $this->attributes;
+ $instance->cookies = $this->cookies;
+ $instance->files = $this->files;
+ $instance->server = $this->server;
+ $instance->headers = $this->headers;
+
+ if ($instance instanceof ApiRequest) {
+ $instance->handleConfig(is_array($config) ? $config : []);
+ }
+ }
+ }
+
+ public function rules(): array
+ {
+ // check all subrequests for rules and combine them
+ return array_reduce(
+ $this->requests,
+ static fn (array $rules, FormRequest $request) =>
+ $rules +
+ (method_exists($request, 'rules')
+ ? $request->rules()
+ : []
+ ),
+ [],
+ );
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ // register all subrequests' validators
+ foreach ($this->requests as $request) {
+ if (method_exists($request, 'withValidator')) {
+ $request->withValidator($validator);
+ }
+ }
+ }
+}
diff --git a/app/Api/V1/Requests/ApiRequest.php b/app/Api/V1/Requests/ApiRequest.php
new file mode 100644
index 0000000000..cb9a7b6046
--- /dev/null
+++ b/app/Api/V1/Requests/ApiRequest.php
@@ -0,0 +1,45 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace FireflyIII\Api\V1\Requests;
+
+use FireflyIII\Exceptions\ValidationException;
+use FireflyIII\Support\Request\ChecksLogin;
+use FireflyIII\Support\Request\ConvertsDataTypes;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Validator;
+
+class ApiRequest extends FormRequest
+{
+ use ChecksLogin;
+ use ConvertsDataTypes;
+
+ protected string $required = '';
+
+ public function handleConfig(array $config): void
+ {
+ if (in_array('required', $config)) {
+ $this->required = 'required';
+ }
+ }
+}
diff --git a/app/Api/V1/Requests/Data/DateRequest.php b/app/Api/V1/Requests/Data/DateRequest.php
deleted file mode 100644
index c112495d46..0000000000
--- a/app/Api/V1/Requests/Data/DateRequest.php
+++ /dev/null
@@ -1,82 +0,0 @@
-.
- */
-
-declare(strict_types=1);
-
-namespace FireflyIII\Api\V1\Requests\Data;
-
-use FireflyIII\Exceptions\ValidationException;
-use FireflyIII\Support\Request\ChecksLogin;
-use FireflyIII\Support\Request\ConvertsDataTypes;
-use Illuminate\Foundation\Http\FormRequest;
-
-/**
- * Request class for end points that require date parameters.
- *
- * Class DateRequest
- */
-class DateRequest extends FormRequest
-{
- use ChecksLogin;
- use ConvertsDataTypes;
-
- /**
- * Get all data from the request.
- */
- public function getAll(): array
- {
- $start = $this->getCarbonDate('start');
- $end = $this->getCarbonDate('end');
- if (null === $start) {
- $start = now()->startOfMonth();
- }
- if (null === $end) {
- $end = now()->endOfMonth();
- }
- // sanity check on dates:
- [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
- $start->startOfDay();
- $end->endOfDay();
- if ($start->diffInYears($end, true) > 5) {
- throw new ValidationException('Date range out of range.');
- }
-
- return [
- 'start' => $start,
- 'end' => $end,
- 'date' => $this->getCarbonDate('date'),
- ];
- }
-
- /**
- * The rules that the incoming request must be matched against.
- */
- public function rules(): array
- {
- return [
- 'date' => 'date|after:1970-01-02|before:2038-01-17',
- 'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
- 'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
- ];
- }
-}
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
new file mode 100644
index 0000000000..c4e2dc87ec
--- /dev/null
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -0,0 +1,56 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace FireflyIII\Api\V1\Requests;
+
+use FireflyIII\Exceptions\ValidationException;
+use FireflyIII\Support\Request\ChecksLogin;
+use FireflyIII\Support\Request\ConvertsDataTypes;
+use Illuminate\Foundation\Http\FormRequest;
+
+class DateRangeRequest extends ApiRequest
+{
+ public function rules(): array
+ {
+ return [
+ 'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|' . $this->required,
+ 'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|' . $this->required,
+ ];
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ $validator->after(
+ function (Validator $validator): void {
+ if (!$validator->valid()) {
+ return;
+ }
+ $start = $this->getCarbonDate('start')?->startOfDay();
+ $end = $this->getCarbonDate('end')?->endOfDay();
+
+ $this->attributes->set('start', $start);
+ $this->attributes->set('end', $end);
+ }
+ );
+ }
+}
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
new file mode 100644
index 0000000000..c59dd65b13
--- /dev/null
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -0,0 +1,57 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace FireflyIII\Api\V1\Requests;
+
+use Illuminate\Validation\Validator;
+
+class DateRequest extends ApiRequest
+{
+ public function rules(): array
+ {
+ return [
+ 'date' => 'date|after:1970-01-02|before:2038-01-17|' . $this->required,
+ ];
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ $validator->after(
+ function (Validator $validator): void {
+ if (!$validator->valid()) {
+ return;
+ }
+ $date = $this->getCarbonDate('date')?->endOfDay();
+
+ // if we also have a range, date must be in that range
+ $start = $this->attributes->get('start');
+ $end = $this->attributes->get('end');
+ if ($date && $start && $end && !$date->between($start, $end)) {
+ $validator->errors()->add('date', (string)trans('validation.between_date'));
+ }
+
+ $this->attributes->set('date', $date);
+ }
+ );
+ }
+}
diff --git a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
new file mode 100644
index 0000000000..10576e5717
--- /dev/null
+++ b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
@@ -0,0 +1,57 @@
+.
+ */
+
+namespace FireflyIII\Api\V1\Requests\Models\Account;
+
+use FireflyIII\Api\V1\Requests\ApiRequest;
+use FireflyIII\Support\Http\Api\AccountFilter;
+use Illuminate\Validation\Validator;
+
+class AccountTypeApiRequest extends ApiRequest
+{
+ use AccountFilter;
+
+ public function rules(): array
+ {
+ return [
+ 'type' => sprintf('in:%s', implode(',', array_keys($this->types))),
+ ];
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ $validator->after(
+ function (Validator $validator): void {
+ if (!$validator->valid()) {
+ return;
+ }
+
+ $type = $this->convertString('type', 'all');
+ $this->attributes->add([
+ 'type' => $type,
+ 'types' => $this->mapAccountTypes($type),
+ ]);
+ }
+ );
+ }
+}
diff --git a/app/Api/V1/Requests/Models/Account/ShowRequest.php b/app/Api/V1/Requests/Models/Account/ShowRequest.php
index f4f7d73332..ed0a7dafe1 100644
--- a/app/Api/V1/Requests/Models/Account/ShowRequest.php
+++ b/app/Api/V1/Requests/Models/Account/ShowRequest.php
@@ -23,77 +23,21 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\Account;
-use Carbon\Carbon;
+use FireflyIII\Api\V1\Requests\AggregateFormRequest;
+use FireflyIII\Api\V1\Requests\DateRangeRequest;
+use FireflyIII\Api\V1\Requests\DateRequest;
+use FireflyIII\Api\V1\Requests\PaginationRequest;
use FireflyIII\Models\Account;
-use FireflyIII\Rules\IsValidSortInstruction;
-use FireflyIII\Support\Facades\Preferences;
-use FireflyIII\Support\Http\Api\AccountFilter;
-use FireflyIII\Support\Request\ConvertsDataTypes;
-use FireflyIII\User;
-use Illuminate\Foundation\Http\FormRequest;
-use Illuminate\Validation\Validator;
-class ShowRequest extends FormRequest
+class ShowRequest extends AggregateFormRequest
{
- use AccountFilter;
- use ConvertsDataTypes;
-
- public function getParameters(): array
+ protected function getRequests(): array
{
- $limit = $this->convertInteger('limit');
- if (0 === $limit) {
- // get default for user:
- /** @var User $user */
- $user = auth()->user();
- $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
- }
-
- $page = $this->convertInteger('page');
- $page = min(max(1, $page), 2 ** 16);
-
return [
- 'type' => $this->convertString('type', 'all'),
- 'limit' => $limit,
- 'sort' => $this->convertSortParameters('sort', Account::class),
- 'page' => $page,
+ [PaginationRequest::class, 'sort_class' => Account::class],
+ DateRangeRequest::class,
+ DateRequest::class,
+ AccountTypeApiRequest::class,
];
}
-
- public function rules(): array
- {
- $keys = implode(',', array_keys($this->types));
-
- return [
- 'date' => 'date',
- 'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02',
- 'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02',
- 'sort' => ['nullable', new IsValidSortInstruction(Account::class)],
- 'type' => sprintf('in:%s', $keys),
- 'limit' => 'numeric|min:1|max:131337',
- 'page' => 'numeric|min:1|max:131337',
- ];
- }
-
- public function withValidator(Validator $validator): void
- {
- $validator->after(
- function (Validator $validator): void {
- if (count($validator->failed()) > 0) {
- return;
- }
- $data = $validator->getData();
-
-
- if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) {
- // assume valid dates, before we got here.
- $start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay();
- $end = Carbon::parse($data['end'], config('app.timezone'))->endOfDay();
- $date = Carbon::parse($data['date'], config('app.timezone'));
- if (!$date->between($start, $end)) {
- $validator->errors()->add('date', (string)trans('validation.between_date'));
- }
- }
- }
- );
- }
}
diff --git a/app/Api/V1/Requests/PaginationRequest.php b/app/Api/V1/Requests/PaginationRequest.php
new file mode 100644
index 0000000000..0dc9719929
--- /dev/null
+++ b/app/Api/V1/Requests/PaginationRequest.php
@@ -0,0 +1,85 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace FireflyIII\Api\V1\Requests;
+
+use FireflyIII\Models\Account;
+use FireflyIII\Rules\IsValidSortInstruction;
+use FireflyIII\Support\Facades\Preferences;
+use FireflyIII\User;
+use Illuminate\Validation\Validator;
+
+class PaginationRequest extends ApiRequest
+{
+ private ?string $sortClass = null;
+
+ public function handleConfig(array $config): void
+ {
+ parent::handleConfig($config);
+
+ $this->sortClass = $config['sort_class'] ?? null;
+
+ if (!$this->sortClass) {
+ throw new \RuntimeException('PaginationRequest requires a sort_class config');
+ }
+ }
+
+
+ public function rules(): array
+ {
+ return [
+ 'sort' => ['nullable', new IsValidSortInstruction($this->sortClass)],
+ 'limit' => 'numeric|min:1|max:131337',
+ 'page' => 'numeric|min:1|max:131337',
+ ];
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ $validator->after(
+ function (Validator $validator): void {
+ if (!$validator->valid()) {
+ return;
+ }
+
+ $limit = $this->convertInteger('limit');
+ if (0 === $limit) {
+ // get default for user:
+ /** @var User $user */
+ $user = auth()->user();
+ $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
+ }
+
+ $page = $this->convertInteger('page');
+ $page = min(max(1, $page), 2 ** 16);
+ $offset = ($page - 1) * $limit;
+ $sort = $this->sortClass ? $this->convertSortParameters('sort', $this->sortClass) : $this->get('sort');
+
+ $this->attributes->set('limit', $limit);
+ $this->attributes->set('sort', $sort);
+ $this->attributes->set('page', $page);
+ $this->attributes->set('offset', $offset);
+ }
+ );
+ }
+}
From d800a01e332368a98da8ffb7fff1fbc2e3c319aa Mon Sep 17 00:00:00 2001
From: jreyesr
Date: Thu, 9 Oct 2025 15:40:07 -0500
Subject: [PATCH 20/82] Add XML mimetypes to the allowedMimes list
Signed-off-by: jreyesr
---
config/firefly.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/config/firefly.php b/config/firefly.php
index e11aabd870..3952957ac6 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -225,6 +225,8 @@ return [
// plain files
'text/plain',
'text/html',
+ 'text/xml',
+ 'application/xml',
// images
'image/jpeg',
From 70b8ea0acbfcc050807c5091770b984cd12f6011 Mon Sep 17 00:00:00 2001
From: Sander Dorigo
Date: Fri, 10 Oct 2025 08:46:11 +0200
Subject: [PATCH 21/82] Basic check on numbers, needs improv still.
---
app/Http/Controllers/Json/ReconcileController.php | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php
index 763d306275..436fe0f347 100644
--- a/app/Http/Controllers/Json/ReconcileController.php
+++ b/app/Http/Controllers/Json/ReconcileController.php
@@ -81,6 +81,12 @@ class ReconcileController extends Controller
if (!$start instanceof Carbon && !$end instanceof Carbon) {
throw new FireflyException('Invalid dates submitted.');
}
+ if(!is_numeric($startBalance)) {
+ $startBalance = '0';
+ }
+ if(!is_numeric($endBalance)) {
+ $endBalance = '0';
+ }
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
From 63aa8adab76b45f1d4a12a6ee0dd04e9e9f1fa41 Mon Sep 17 00:00:00 2001
From: Sander Dorigo
Date: Fri, 10 Oct 2025 08:46:47 +0200
Subject: [PATCH 22/82] Import forgotten class
---
app/Factory/PiggyBankEventFactory.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php
index 09a80af0ff..e4635fb93c 100644
--- a/app/Factory/PiggyBankEventFactory.php
+++ b/app/Factory/PiggyBankEventFactory.php
@@ -26,6 +26,7 @@ namespace FireflyIII\Factory;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
+use Illuminate\Support\Facades\Log;
/**
* Create piggy bank events.
From ecf498cc815ca547e8dddb074cff4850ebc97fd6 Mon Sep 17 00:00:00 2001
From: Sander Dorigo
Date: Fri, 10 Oct 2025 13:18:47 +0200
Subject: [PATCH 23/82] Add more options
---
app/Support/Navigation.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index 85c50ed24c..8eccba774b 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -428,6 +428,11 @@ class Navigation
'year' => (string)trans('config.year_js'),
'yearly' => (string)trans('config.year_js'),
'6M' => (string)trans('config.half_year_js'),
+ 'last7' => (string)trans('config.specific_day_js'),
+ 'last30' => (string)trans('config.month_js'),
+ 'last90' => (string)trans('config.month_js'),
+ 'last365' => (string)trans('config.year_js'),
+ 'QTD' => (string)trans('config.month_js'),
];
if (array_key_exists($repeatFrequency, $formatMap)) {
From e09b6034f73d5f44b2b4b71600937eae4036918b Mon Sep 17 00:00:00 2001
From: JC5
Date: Fri, 10 Oct 2025 13:32:57 +0200
Subject: [PATCH 24/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-10?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
THANKS.md | 1 +
.../Controllers/Json/ReconcileController.php | 6 ++---
app/Support/Navigation.php | 10 +++----
composer.lock | 24 ++++++++---------
config/firefly.php | 4 +--
package-lock.json | 26 +++++++++----------
6 files changed, 36 insertions(+), 35 deletions(-)
diff --git a/THANKS.md b/THANKS.md
index e4e9d01869..4b590fc92b 100755
--- a/THANKS.md
+++ b/THANKS.md
@@ -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.
## 2025
+- jreyesr
- codearena-bot
- Nicky De Maeyer
- Denis Iskandarov
diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php
index 436fe0f347..ed85b21885 100644
--- a/app/Http/Controllers/Json/ReconcileController.php
+++ b/app/Http/Controllers/Json/ReconcileController.php
@@ -81,12 +81,12 @@ class ReconcileController extends Controller
if (!$start instanceof Carbon && !$end instanceof Carbon) {
throw new FireflyException('Invalid dates submitted.');
}
- if(!is_numeric($startBalance)) {
+ if (!is_numeric($startBalance)) {
$startBalance = '0';
}
- if(!is_numeric($endBalance)) {
+ if (!is_numeric($endBalance)) {
$endBalance = '0';
- }
+ }
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index 8eccba774b..b80548d990 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -428,11 +428,11 @@ class Navigation
'year' => (string)trans('config.year_js'),
'yearly' => (string)trans('config.year_js'),
'6M' => (string)trans('config.half_year_js'),
- 'last7' => (string)trans('config.specific_day_js'),
- 'last30' => (string)trans('config.month_js'),
- 'last90' => (string)trans('config.month_js'),
- 'last365' => (string)trans('config.year_js'),
- 'QTD' => (string)trans('config.month_js'),
+ 'last7' => (string)trans('config.specific_day_js'),
+ 'last30' => (string)trans('config.month_js'),
+ 'last90' => (string)trans('config.month_js'),
+ 'last365' => (string)trans('config.year_js'),
+ 'QTD' => (string)trans('config.month_js'),
];
if (array_key_exists($repeatFrequency, $formatMap)) {
diff --git a/composer.lock b/composer.lock
index 5f9c29b59a..26c250c50c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -11815,16 +11815,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.4.0",
+ "version": "12.4.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9"
+ "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f62aab5794e36ccd26860db2d1bbf89ac19028d9",
- "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc5413a2e6d240d2f6d9317bdf7f0a24e73de194",
+ "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194",
"shasum": ""
},
"require": {
@@ -11892,7 +11892,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.0"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.1"
},
"funding": [
{
@@ -11916,20 +11916,20 @@
"type": "tidelift"
}
],
- "time": "2025-10-03T04:28:03+00:00"
+ "time": "2025-10-09T14:08:29+00:00"
},
{
"name": "rector/rector",
- "version": "2.2.1",
+ "version": "2.2.2",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb"
+ "reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/e1aaf3061e9ae9342ed0824865e3a3360defddeb",
- "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/5b353f7457b9a0c63fc91ef340f5d119a40991ed",
+ "reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed",
"shasum": ""
},
"require": {
@@ -11968,7 +11968,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.2.1"
+ "source": "https://github.com/rectorphp/rector/tree/2.2.2"
},
"funding": [
{
@@ -11976,7 +11976,7 @@
"type": "github"
}
],
- "time": "2025-10-06T21:25:14+00:00"
+ "time": "2025-10-09T19:50:20+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/config/firefly.php b/config/firefly.php
index 3952957ac6..66670ca4cd 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-09',
- 'build_time' => 1759999856,
+ 'version' => 'develop/2025-10-10',
+ 'build_time' => 1760095871,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 1979ad9a2e..c95f60d7b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3173,9 +3173,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.7.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
- "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
+ "version": "24.7.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
+ "integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.14",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz",
- "integrity": "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ==",
+ "version": "2.8.15",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.15.tgz",
+ "integrity": "sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4966,13 +4966,13 @@
"license": "MIT"
},
"node_modules/core-js-compat": {
- "version": "3.45.1",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz",
- "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==",
+ "version": "3.46.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz",
+ "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==",
"dev": true,
"license": "MIT",
"dependencies": {
- "browserslist": "^4.25.3"
+ "browserslist": "^4.26.3"
},
"funding": {
"type": "opencollective",
@@ -5736,9 +5736,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.233",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz",
- "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==",
+ "version": "1.5.234",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
+ "integrity": "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg==",
"dev": true,
"license": "ISC"
},
From 4f4576e4586039868e76d028cd932c9a56aae39f Mon Sep 17 00:00:00 2001
From: James Cole
Date: Fri, 10 Oct 2025 16:41:46 +0200
Subject: [PATCH 25/82] Add option to select date, fix #11042
---
.../Controllers/Rule/SelectController.php | 68 +++++++++++--------
.../RuleGroup/ExecutionController.php | 11 +++
.../rules/rule-group/select-transactions.twig | 2 +
.../views/rules/rule/select-transactions.twig | 2 +
4 files changed, 54 insertions(+), 29 deletions(-)
diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php
index 1084a9f2c8..c16572851b 100644
--- a/app/Http/Controllers/Rule/SelectController.php
+++ b/app/Http/Controllers/Rule/SelectController.php
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Rule;
+use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SelectTransactionsRequest;
@@ -56,7 +57,7 @@ class SelectController extends Controller
$this->middleware(
static function ($request, $next) {
- app('view')->share('title', (string) trans('firefly.rules'));
+ app('view')->share('title', (string)trans('firefly.rules'));
app('view')->share('mainTitleIcon', 'fa-random');
return $next($request);
@@ -71,20 +72,29 @@ class SelectController extends Controller
{
// Get parameters specified by the user
/** @var User $user */
- $user = auth()->user();
- $accounts = implode(',', $request->get('accounts'));
-
+ $user = auth()->user();
+ $accounts = implode(',', $request->get('accounts'));
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
+ // add date operators.
+ if (null !== $request->get('start')) {
+ $startDate = new Carbon($request->get('start'));
+ $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
+ }
+ if (null !== $request->get('end')) {
+ $endDate = new Carbon($request->get('end'));
+ $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
+ }
+
// add extra operators:
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
$newRuleEngine->fire();
- $resultCount = $newRuleEngine->getResults();
+ $resultCount = $newRuleEngine->getResults();
session()->flash('success', trans_choice('firefly.applied_rule_selection', $resultCount, ['title' => $rule->title]));
@@ -94,7 +104,7 @@ class SelectController extends Controller
/**
* View to select transactions by a rule.
*/
- public function selectTransactions(Rule $rule): Factory|RedirectResponse|View
+ public function selectTransactions(Rule $rule): Factory | RedirectResponse | View
{
if (false === $rule->active) {
session()->flash('warning', trans('firefly.cannot_fire_inactive_rules'));
@@ -102,7 +112,7 @@ class SelectController extends Controller
return redirect(route('rules.index'));
}
// does the user have shared accounts?
- $subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]);
+ $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]);
return view('rules.rule.select-transactions', compact('rule', 'subTitle'));
}
@@ -116,25 +126,25 @@ class SelectController extends Controller
public function testTriggers(TestRuleFormRequest $request): JsonResponse
{
// build fake rule
- $rule = new Rule();
+ $rule = new Rule();
/** @var \Illuminate\Database\Eloquent\Collection $triggers */
- $triggers = new Collection();
- $rule->strict = '1' === $request->get('strict');
+ $triggers = new Collection();
+ $rule->strict = '1' === $request->get('strict');
// build trigger array from response
- $textTriggers = $this->getValidTriggerList($request);
+ $textTriggers = $this->getValidTriggerList($request);
// warn if nothing.
if (0 === count($textTriggers)) {
- return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]);
+ return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]);
}
foreach ($textTriggers as $textTrigger) {
- $needsContext = config(sprintf('search.operators.%s.needs_context', $textTrigger['type'])) ?? true;
- $trigger = new RuleTrigger();
- $trigger->trigger_type = $textTrigger['type'];
- $trigger->trigger_value = $textTrigger['value'];
+ $needsContext = config(sprintf('search.operators.%s.needs_context', $textTrigger['type'])) ?? true;
+ $trigger = new RuleTrigger();
+ $trigger->trigger_type = $textTrigger['type'];
+ $trigger->trigger_value = $textTrigger['value'];
if (false === $needsContext) {
$trigger->trigger_value = 'true';
}
@@ -149,22 +159,22 @@ class SelectController extends Controller
// create new rule engine:
/** @var RuleEngineInterface $newRuleEngine */
- $newRuleEngine = app(RuleEngineInterface::class);
+ $newRuleEngine = app(RuleEngineInterface::class);
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
$newRuleEngine->setRefreshTriggers(false);
- $collection = $newRuleEngine->find();
- $collection = $collection->slice(0, 20);
+ $collection = $newRuleEngine->find();
+ $collection = $collection->slice(0, 20);
// Warn the user if only a subset of transactions is returned
- $warning = '';
+ $warning = '';
if (0 === count($collection)) {
- $warning = (string) trans('firefly.warning_no_matching_transactions');
+ $warning = (string)trans('firefly.warning_no_matching_transactions');
}
// Return json response
- $view = 'ERROR, see logs.';
+ $view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
@@ -187,26 +197,26 @@ class SelectController extends Controller
*/
public function testTriggersByRule(Rule $rule): JsonResponse
{
- $triggers = $rule->ruleTriggers;
+ $triggers = $rule->ruleTriggers;
if (0 === count($triggers)) {
- return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]);
+ return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]);
}
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
- $collection = $newRuleEngine->find();
- $collection = $collection->slice(0, 20);
+ $collection = $newRuleEngine->find();
+ $collection = $collection->slice(0, 20);
- $warning = '';
+ $warning = '';
if (0 === count($collection)) {
- $warning = (string) trans('firefly.warning_no_matching_transactions');
+ $warning = (string)trans('firefly.warning_no_matching_transactions');
}
// Return json response
- $view = 'ERROR, see logs.';
+ $view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
diff --git a/app/Http/Controllers/RuleGroup/ExecutionController.php b/app/Http/Controllers/RuleGroup/ExecutionController.php
index a0191cba3f..14ee17209a 100644
--- a/app/Http/Controllers/RuleGroup/ExecutionController.php
+++ b/app/Http/Controllers/RuleGroup/ExecutionController.php
@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\RuleGroup;
+use Carbon\Carbon;
use Exception;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SelectTransactionsRequest;
@@ -73,6 +74,16 @@ class ExecutionController extends Controller
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
+ // add date operators.
+ if (null !== $request->get('start')) {
+ $startDate = new Carbon($request->get('start'));
+ $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
+ }
+ if (null !== $request->get('end')) {
+ $endDate = new Carbon($request->get('end'));
+ $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
+ }
+
// add extra operators:
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
diff --git a/resources/views/rules/rule-group/select-transactions.twig b/resources/views/rules/rule-group/select-transactions.twig
index 81abe48208..4988fc45ed 100644
--- a/resources/views/rules/rule-group/select-transactions.twig
+++ b/resources/views/rules/rule-group/select-transactions.twig
@@ -24,6 +24,8 @@
+ {{ ExpandedForm.date('start') }}
+ {{ ExpandedForm.date('end') }}
{{ AccountForm.assetAccountCheckList('accounts', {'select_all': true,'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
diff --git a/resources/views/rules/rule/select-transactions.twig b/resources/views/rules/rule/select-transactions.twig
index a6575e53f9..1338ce3c39 100644
--- a/resources/views/rules/rule/select-transactions.twig
+++ b/resources/views/rules/rule/select-transactions.twig
@@ -23,6 +23,8 @@
+ {{ ExpandedForm.date('start') }}
+ {{ ExpandedForm.date('end') }}
{{ AccountForm.assetAccountCheckList('accounts', {'select_all': true, 'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
From 83f3eddf441332e11ef3856fa057141b6ec4fbdf Mon Sep 17 00:00:00 2001
From: James Cole
Date: Fri, 10 Oct 2025 18:50:39 +0200
Subject: [PATCH 26/82] Small code changes for @ctrl-f5's PR.
- Dropped some "setParameters" calls if unused in transformer.
- Add correct copyright
- Sprintf instead of string concat
- Small ident changes courtesy of phpstorm.
---
.../Models/Account/ShowController.php | 53 +++++++++----------
.../Models/UserGroup/IndexController.php | 2 -
app/Api/V1/Requests/AggregateFormRequest.php | 2 +-
app/Api/V1/Requests/ApiRequest.php | 2 +-
app/Api/V1/Requests/DateRangeRequest.php | 7 +--
app/Api/V1/Requests/DateRequest.php | 2 +-
.../Models/Account/AccountTypeApiRequest.php | 4 +-
app/Api/V1/Requests/PaginationRequest.php | 2 +-
app/Transformers/AbstractTransformer.php | 6 +++
app/Transformers/AccountTransformer.php | 2 -
10 files changed, 40 insertions(+), 42 deletions(-)
diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php
index cb0097062e..ab0e510911 100644
--- a/app/Api/V1/Controllers/Models/Account/ShowController.php
+++ b/app/Api/V1/Controllers/Models/Account/ShowController.php
@@ -70,48 +70,48 @@ class ShowController extends Controller
*/
public function index(ShowRequest $request): JsonResponse
{
- $manager = $this->getManager();
+ $manager = $this->getManager();
[
- 'types' => $types,
- 'page' => $page,
- 'limit' => $limit,
+ 'types' => $types,
+ 'page' => $page,
+ 'limit' => $limit,
'offset' => $offset,
- 'sort' => $sort,
- 'start' => $start,
- 'end' => $end,
- 'date' => $date,
- ] = $request->attributes->all();
+ 'sort' => $sort,
+ 'start' => $start,
+ 'end' => $end,
+ 'date' => $date,
+ ]
+ = $request->attributes->all();
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
- $collection = $this->repository->getAccountsByType($types, $sort);
- $count = $collection->count();
+ $collection = $this->repository->getAccountsByType($types, $sort);
+ $count = $collection->count();
// continue sort:
// TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after.
// TODO still need to figure out how to do this easily.
- $accounts = $collection->slice($offset, $limit);
+ $accounts = $collection->slice($offset, $limit);
// enrich
/** @var User $admin */
- $admin = auth()->user();
- $enrichment = new AccountEnrichment();
+ $admin = auth()->user();
+ $enrichment = new AccountEnrichment();
$enrichment->setSort($sort);
$enrichment->setDate($date);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$enrichment->setUser($admin);
- $accounts = $enrichment->enrich($accounts);
+ $accounts = $enrichment->enrich($accounts);
// make paginator:
- $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page);
- $paginator->setPath(route('api.v1.accounts.index').$this->buildParams());
+ $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page);
+ $paginator->setPath(route('api.v1.accounts.index') . $this->buildParams());
/** @var AccountTransformer $transformer */
$transformer = app(AccountTransformer::class);
- $transformer->setParameters($this->parameters);
- $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY);
+ $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
@@ -128,27 +128,22 @@ class ShowController extends Controller
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
$account->refresh();
- $manager = $this->getManager();
- [
- 'start' => $start,
- 'end' => $end,
- 'date' => $date,
- ] = $request->attributes->all();
+ $manager = $this->getManager();
+ [$start, $end, $date,] = $request->attributes->all();
// enrich
/** @var User $admin */
- $admin = auth()->user();
- $enrichment = new AccountEnrichment();
+ $admin = auth()->user();
+ $enrichment = new AccountEnrichment();
$enrichment->setDate($date);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$enrichment->setUser($admin);
- $account = $enrichment->enrichSingle($account);
+ $account = $enrichment->enrichSingle($account);
/** @var AccountTransformer $transformer */
$transformer = app(AccountTransformer::class);
- $transformer->setParameters($this->parameters);
$resource = new Item($account, $transformer, self::RESOURCE_KEY);
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
diff --git a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
index 8c4ba0ddbb..cda1faa21a 100644
--- a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
+++ b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
@@ -65,8 +65,6 @@ class IndexController extends Controller
$paginator = new LengthAwarePaginator($administrations, $count, $limit, $page);
$transformer = new UserGroupTransformer();
- $transformer->setParameters($request->attributes); // give params to transformer
-
return response()
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
->header('Content-Type', self::CONTENT_TYPE)
diff --git a/app/Api/V1/Requests/AggregateFormRequest.php b/app/Api/V1/Requests/AggregateFormRequest.php
index 207ce0640d..9e9fbf7e92 100644
--- a/app/Api/V1/Requests/AggregateFormRequest.php
+++ b/app/Api/V1/Requests/AggregateFormRequest.php
@@ -1,7 +1,7 @@
'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|' . $this->required,
- 'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|' . $this->required,
+ 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|' , $this->required),
+ 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|' , $this->required),
];
}
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index c59dd65b13..5a87fb3086 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -1,7 +1,7 @@
parameters;
}
+ /**
+ * @deprecated
+ */
final public function setParameters(ParameterBag $parameters): void
{
$this->parameters = $parameters;
diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php
index c9e5d3aeee..5538e745e7 100644
--- a/app/Transformers/AccountTransformer.php
+++ b/app/Transformers/AccountTransformer.php
@@ -30,7 +30,6 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
-use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class AccountTransformer
@@ -46,7 +45,6 @@ class AccountTransformer extends AbstractTransformer
*/
public function __construct()
{
- $this->parameters = new ParameterBag();
$this->repository = app(AccountRepositoryInterface::class);
$this->convertToPrimary = Amount::convertToPrimary();
$this->primary = Amount::getPrimaryCurrency();
From a709e224d4bdfe51d7e5b104cbbff8e9696395f9 Mon Sep 17 00:00:00 2001
From: JC5
Date: Fri, 10 Oct 2025 18:55:02 +0200
Subject: [PATCH 27/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-10?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Api/V1/Controllers/Controller.php | 3 +-
.../Models/Account/ShowController.php | 36 +++++++--------
.../Models/UserGroup/IndexController.php | 4 +-
app/Api/V1/Requests/AggregateFormRequest.php | 26 +++++------
app/Api/V1/Requests/ApiRequest.php | 4 +-
app/Api/V1/Requests/DateRangeRequest.php | 8 +---
app/Api/V1/Requests/DateRequest.php | 2 +-
.../Models/Account/AccountTypeApiRequest.php | 2 +-
app/Api/V1/Requests/PaginationRequest.php | 13 +++---
.../Controllers/Rule/SelectController.php | 44 +++++++++----------
composer.lock | 8 ++--
config/firefly.php | 2 +-
package-lock.json | 12 ++---
13 files changed, 79 insertions(+), 85 deletions(-)
diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php
index d83c713aa1..4603e9497d 100644
--- a/app/Api/V1/Controllers/Controller.php
+++ b/app/Api/V1/Controllers/Controller.php
@@ -67,6 +67,7 @@ abstract class Controller extends BaseController
protected bool $convertToPrimary = false;
protected TransactionCurrency $primaryCurrency;
+
/** @deprecated use Request classes */
protected ParameterBag $parameters;
@@ -100,7 +101,7 @@ abstract class Controller extends BaseController
/**
* @deprecated use Request classes
- * Method to grab all parameters from the URL.
+ * Method to grab all parameters from the URL
*/
private function getParameters(): ParameterBag
{
diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php
index ab0e510911..e756551e19 100644
--- a/app/Api/V1/Controllers/Models/Account/ShowController.php
+++ b/app/Api/V1/Controllers/Models/Account/ShowController.php
@@ -70,7 +70,7 @@ class ShowController extends Controller
*/
public function index(ShowRequest $request): JsonResponse
{
- $manager = $this->getManager();
+ $manager = $this->getManager();
[
'types' => $types,
'page' => $page,
@@ -81,37 +81,37 @@ class ShowController extends Controller
'end' => $end,
'date' => $date,
]
- = $request->attributes->all();
+ = $request->attributes->all();
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
- $collection = $this->repository->getAccountsByType($types, $sort);
- $count = $collection->count();
+ $collection = $this->repository->getAccountsByType($types, $sort);
+ $count = $collection->count();
// continue sort:
// TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after.
// TODO still need to figure out how to do this easily.
- $accounts = $collection->slice($offset, $limit);
+ $accounts = $collection->slice($offset, $limit);
// enrich
/** @var User $admin */
- $admin = auth()->user();
- $enrichment = new AccountEnrichment();
+ $admin = auth()->user();
+ $enrichment = new AccountEnrichment();
$enrichment->setSort($sort);
$enrichment->setDate($date);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$enrichment->setUser($admin);
- $accounts = $enrichment->enrich($accounts);
+ $accounts = $enrichment->enrich($accounts);
// make paginator:
- $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page);
- $paginator->setPath(route('api.v1.accounts.index') . $this->buildParams());
+ $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page);
+ $paginator->setPath(route('api.v1.accounts.index').$this->buildParams());
/** @var AccountTransformer $transformer */
$transformer = app(AccountTransformer::class);
- $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY);
+ $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
@@ -128,23 +128,23 @@ class ShowController extends Controller
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
$account->refresh();
- $manager = $this->getManager();
- [$start, $end, $date,] = $request->attributes->all();
+ $manager = $this->getManager();
+ [$start, $end, $date] = $request->attributes->all();
// enrich
/** @var User $admin */
- $admin = auth()->user();
- $enrichment = new AccountEnrichment();
+ $admin = auth()->user();
+ $enrichment = new AccountEnrichment();
$enrichment->setDate($date);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$enrichment->setUser($admin);
- $account = $enrichment->enrichSingle($account);
+ $account = $enrichment->enrichSingle($account);
/** @var AccountTransformer $transformer */
- $transformer = app(AccountTransformer::class);
- $resource = new Item($account, $transformer, self::RESOURCE_KEY);
+ $transformer = app(AccountTransformer::class);
+ $resource = new Item($account, $transformer, self::RESOURCE_KEY);
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
diff --git a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
index cda1faa21a..5cdbb9023f 100644
--- a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
+++ b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php
@@ -56,8 +56,8 @@ class IndexController extends Controller
{
$administrations = $this->repository->get();
[
- 'page' => $page,
- 'limit' => $limit,
+ 'page' => $page,
+ 'limit' => $limit,
'offset' => $offset,
] = $request->attributes->all();
$count = $administrations->count();
diff --git a/app/Api/V1/Requests/AggregateFormRequest.php b/app/Api/V1/Requests/AggregateFormRequest.php
index 9e9fbf7e92..85579fffc3 100644
--- a/app/Api/V1/Requests/AggregateFormRequest.php
+++ b/app/Api/V1/Requests/AggregateFormRequest.php
@@ -25,8 +25,8 @@ namespace FireflyIII\Api\V1\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
+use RuntimeException;
abstract class AggregateFormRequest extends ApiRequest
{
@@ -44,20 +44,20 @@ abstract class AggregateFormRequest extends ApiRequest
// instantiate all subrequests and share current requests' bags with them
foreach ($this->getRequests() as $config) {
- $requestClass = is_array($config) ? array_shift($config) : $config;
+ $requestClass = is_array($config) ? array_shift($config) : $config;
if (!is_a($requestClass, Request::class, true)) {
- throw new \RuntimeException('getRequests() must return class-strings of subclasses of Request');
+ throw new RuntimeException('getRequests() must return class-strings of subclasses of Request');
}
- $instance = $this->requests[] = new $requestClass();
- $instance->request = $this->request;
- $instance->query = $this->query;
+ $instance = $this->requests[] = new $requestClass();
+ $instance->request = $this->request;
+ $instance->query = $this->query;
$instance->attributes = $this->attributes;
- $instance->cookies = $this->cookies;
- $instance->files = $this->files;
- $instance->server = $this->server;
- $instance->headers = $this->headers;
+ $instance->cookies = $this->cookies;
+ $instance->files = $this->files;
+ $instance->server = $this->server;
+ $instance->headers = $this->headers;
if ($instance instanceof ApiRequest) {
$instance->handleConfig(is_array($config) ? $config : []);
@@ -70,9 +70,9 @@ abstract class AggregateFormRequest extends ApiRequest
// check all subrequests for rules and combine them
return array_reduce(
$this->requests,
- static fn (array $rules, FormRequest $request) =>
- $rules +
- (method_exists($request, 'rules')
+ static fn (array $rules, FormRequest $request) => $rules
+ + (
+ method_exists($request, 'rules')
? $request->rules()
: []
),
diff --git a/app/Api/V1/Requests/ApiRequest.php b/app/Api/V1/Requests/ApiRequest.php
index e5ee028d8d..0d2a343f81 100644
--- a/app/Api/V1/Requests/ApiRequest.php
+++ b/app/Api/V1/Requests/ApiRequest.php
@@ -23,11 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
-use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
-use Illuminate\Validation\Validator;
class ApiRequest extends FormRequest
{
@@ -38,7 +36,7 @@ class ApiRequest extends FormRequest
public function handleConfig(array $config): void
{
- if (in_array('required', $config)) {
+ if (in_array('required', $config, true)) {
$this->required = 'required';
}
}
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index a5c2a22443..27d6bc7baa 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -23,10 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
-use FireflyIII\Exceptions\ValidationException;
-use FireflyIII\Support\Request\ChecksLogin;
-use FireflyIII\Support\Request\ConvertsDataTypes;
-use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
class DateRangeRequest extends ApiRequest
@@ -34,8 +30,8 @@ class DateRangeRequest extends ApiRequest
public function rules(): array
{
return [
- 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|' , $this->required),
- 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|' , $this->required),
+ 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|', $this->required),
+ 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|', $this->required),
];
}
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index 5a87fb3086..ad9372fee9 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -30,7 +30,7 @@ class DateRequest extends ApiRequest
public function rules(): array
{
return [
- 'date' => 'date|after:1970-01-02|before:2038-01-17|' . $this->required,
+ 'date' => 'date|after:1970-01-02|before:2038-01-17|'.$this->required,
];
}
diff --git a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
index fb2348017a..e24f6da655 100644
--- a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
+++ b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
@@ -48,7 +48,7 @@ class AccountTypeApiRequest extends ApiRequest
$type = $this->convertString('type', 'all');
$this->attributes->add([
- 'type' => $type,
+ 'type' => $type,
'types' => $this->mapAccountTypes($type),
]);
}
diff --git a/app/Api/V1/Requests/PaginationRequest.php b/app/Api/V1/Requests/PaginationRequest.php
index cf88f238e7..93374a2d84 100644
--- a/app/Api/V1/Requests/PaginationRequest.php
+++ b/app/Api/V1/Requests/PaginationRequest.php
@@ -23,11 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
-use FireflyIII\Models\Account;
use FireflyIII\Rules\IsValidSortInstruction;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Validation\Validator;
+use RuntimeException;
class PaginationRequest extends ApiRequest
{
@@ -40,11 +40,10 @@ class PaginationRequest extends ApiRequest
$this->sortClass = $config['sort_class'] ?? null;
if (!$this->sortClass) {
- throw new \RuntimeException('PaginationRequest requires a sort_class config');
+ throw new RuntimeException('PaginationRequest requires a sort_class config');
}
}
-
public function rules(): array
{
return [
@@ -62,7 +61,7 @@ class PaginationRequest extends ApiRequest
return;
}
- $limit = $this->convertInteger('limit');
+ $limit = $this->convertInteger('limit');
if (0 === $limit) {
// get default for user:
/** @var User $user */
@@ -70,10 +69,10 @@ class PaginationRequest extends ApiRequest
$limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
}
- $page = $this->convertInteger('page');
- $page = min(max(1, $page), 2 ** 16);
+ $page = $this->convertInteger('page');
+ $page = min(max(1, $page), 2 ** 16);
$offset = ($page - 1) * $limit;
- $sort = $this->sortClass ? $this->convertSortParameters('sort', $this->sortClass) : $this->get('sort');
+ $sort = $this->sortClass ? $this->convertSortParameters('sort', $this->sortClass) : $this->get('sort');
$this->attributes->set('limit', $limit);
$this->attributes->set('sort', $sort);
diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php
index c16572851b..458d866378 100644
--- a/app/Http/Controllers/Rule/SelectController.php
+++ b/app/Http/Controllers/Rule/SelectController.php
@@ -72,8 +72,8 @@ class SelectController extends Controller
{
// Get parameters specified by the user
/** @var User $user */
- $user = auth()->user();
- $accounts = implode(',', $request->get('accounts'));
+ $user = auth()->user();
+ $accounts = implode(',', $request->get('accounts'));
// create new rule engine:
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
@@ -94,7 +94,7 @@ class SelectController extends Controller
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
$newRuleEngine->fire();
- $resultCount = $newRuleEngine->getResults();
+ $resultCount = $newRuleEngine->getResults();
session()->flash('success', trans_choice('firefly.applied_rule_selection', $resultCount, ['title' => $rule->title]));
@@ -104,7 +104,7 @@ class SelectController extends Controller
/**
* View to select transactions by a rule.
*/
- public function selectTransactions(Rule $rule): Factory | RedirectResponse | View
+ public function selectTransactions(Rule $rule): Factory|RedirectResponse|View
{
if (false === $rule->active) {
session()->flash('warning', trans('firefly.cannot_fire_inactive_rules'));
@@ -126,14 +126,14 @@ class SelectController extends Controller
public function testTriggers(TestRuleFormRequest $request): JsonResponse
{
// build fake rule
- $rule = new Rule();
+ $rule = new Rule();
/** @var \Illuminate\Database\Eloquent\Collection $triggers */
- $triggers = new Collection();
- $rule->strict = '1' === $request->get('strict');
+ $triggers = new Collection();
+ $rule->strict = '1' === $request->get('strict');
// build trigger array from response
- $textTriggers = $this->getValidTriggerList($request);
+ $textTriggers = $this->getValidTriggerList($request);
// warn if nothing.
if (0 === count($textTriggers)) {
@@ -141,10 +141,10 @@ class SelectController extends Controller
}
foreach ($textTriggers as $textTrigger) {
- $needsContext = config(sprintf('search.operators.%s.needs_context', $textTrigger['type'])) ?? true;
- $trigger = new RuleTrigger();
- $trigger->trigger_type = $textTrigger['type'];
- $trigger->trigger_value = $textTrigger['value'];
+ $needsContext = config(sprintf('search.operators.%s.needs_context', $textTrigger['type'])) ?? true;
+ $trigger = new RuleTrigger();
+ $trigger->trigger_type = $textTrigger['type'];
+ $trigger->trigger_value = $textTrigger['value'];
if (false === $needsContext) {
$trigger->trigger_value = 'true';
}
@@ -159,22 +159,22 @@ class SelectController extends Controller
// create new rule engine:
/** @var RuleEngineInterface $newRuleEngine */
- $newRuleEngine = app(RuleEngineInterface::class);
+ $newRuleEngine = app(RuleEngineInterface::class);
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
$newRuleEngine->setRefreshTriggers(false);
- $collection = $newRuleEngine->find();
- $collection = $collection->slice(0, 20);
+ $collection = $newRuleEngine->find();
+ $collection = $collection->slice(0, 20);
// Warn the user if only a subset of transactions is returned
- $warning = '';
+ $warning = '';
if (0 === count($collection)) {
$warning = (string)trans('firefly.warning_no_matching_transactions');
}
// Return json response
- $view = 'ERROR, see logs.';
+ $view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
@@ -197,7 +197,7 @@ class SelectController extends Controller
*/
public function testTriggersByRule(Rule $rule): JsonResponse
{
- $triggers = $rule->ruleTriggers;
+ $triggers = $rule->ruleTriggers;
if (0 === count($triggers)) {
return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]);
@@ -207,16 +207,16 @@ class SelectController extends Controller
// set rules:
$newRuleEngine->setRules(new Collection()->push($rule));
- $collection = $newRuleEngine->find();
- $collection = $collection->slice(0, 20);
+ $collection = $newRuleEngine->find();
+ $collection = $collection->slice(0, 20);
- $warning = '';
+ $warning = '';
if (0 === count($collection)) {
$warning = (string)trans('firefly.warning_no_matching_transactions');
}
// Return json response
- $view = 'ERROR, see logs.';
+ $view = 'ERROR, see logs.';
try {
$view = view('list.journals-array-tiny', ['groups' => $collection])->render();
diff --git a/composer.lock b/composer.lock
index 26c250c50c..8ea016fcc7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -11333,11 +11333,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "2.1.30",
+ "version": "2.1.31",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
- "reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96",
+ "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96",
"shasum": ""
},
"require": {
@@ -11382,7 +11382,7 @@
"type": "github"
}
],
- "time": "2025-10-02T16:07:52+00:00"
+ "time": "2025-10-10T14:14:11+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
diff --git a/config/firefly.php b/config/firefly.php
index 66670ca4cd..09649ade7b 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-10',
- 'build_time' => 1760095871,
+ 'build_time' => 1760115185,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index c95f60d7b8..7e058505f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.15",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.15.tgz",
- "integrity": "sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==",
+ "version": "2.8.16",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
+ "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -7088,9 +7088,9 @@
}
},
"node_modules/i18next": {
- "version": "25.5.3",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.3.tgz",
- "integrity": "sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==",
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz",
+ "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==",
"funding": [
{
"type": "individual",
From 02e24fc91933dcec0768a334b53c3d4b54e663db Mon Sep 17 00:00:00 2001
From: James Cole
Date: Fri, 10 Oct 2025 19:07:28 +0200
Subject: [PATCH 28/82] Whoops.
---
app/Api/V1/Controllers/Models/Account/ShowController.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php
index e756551e19..30fdf6a6db 100644
--- a/app/Api/V1/Controllers/Models/Account/ShowController.php
+++ b/app/Api/V1/Controllers/Models/Account/ShowController.php
@@ -129,7 +129,9 @@ class ShowController extends Controller
$this->repository->resetAccountOrder();
$account->refresh();
$manager = $this->getManager();
- [$start, $end, $date] = $request->attributes->all();
+ [ 'start' => $start,
+ 'end' => $end,
+ 'date' => $date,] = $request->attributes->all();
// enrich
/** @var User $admin */
From f6411fdc5a7387c1c2bb1a58a0b929b330ce001c Mon Sep 17 00:00:00 2001
From: JC5
Date: Fri, 10 Oct 2025 19:12:15 +0200
Subject: [PATCH 29/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-10?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Models/Account/ShowController.php | 18 +++++++++---------
config/firefly.php | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php
index 30fdf6a6db..5616869a8d 100644
--- a/app/Api/V1/Controllers/Models/Account/ShowController.php
+++ b/app/Api/V1/Controllers/Models/Account/ShowController.php
@@ -128,25 +128,25 @@ class ShowController extends Controller
// get list of accounts. Count it and split it.
$this->repository->resetAccountOrder();
$account->refresh();
- $manager = $this->getManager();
- [ 'start' => $start,
- 'end' => $end,
- 'date' => $date,] = $request->attributes->all();
+ $manager = $this->getManager();
+ ['start' => $start,
+ 'end' => $end,
+ 'date' => $date,] = $request->attributes->all();
// enrich
/** @var User $admin */
- $admin = auth()->user();
- $enrichment = new AccountEnrichment();
+ $admin = auth()->user();
+ $enrichment = new AccountEnrichment();
$enrichment->setDate($date);
$enrichment->setStart($start);
$enrichment->setEnd($end);
$enrichment->setUser($admin);
- $account = $enrichment->enrichSingle($account);
+ $account = $enrichment->enrichSingle($account);
/** @var AccountTransformer $transformer */
- $transformer = app(AccountTransformer::class);
- $resource = new Item($account, $transformer, self::RESOURCE_KEY);
+ $transformer = app(AccountTransformer::class);
+ $resource = new Item($account, $transformer, self::RESOURCE_KEY);
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
diff --git a/config/firefly.php b/config/firefly.php
index 09649ade7b..6984305564 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-10',
- 'build_time' => 1760115185,
+ 'build_time' => 1760116211,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From 9d1fb2cd6aee56836d5c8293185fbcd70025d6bb Mon Sep 17 00:00:00 2001
From: James Cole
Date: Fri, 10 Oct 2025 20:54:31 +0200
Subject: [PATCH 30/82] Make sure that null|string passed is always a string
---
app/Api/V1/Requests/PaginationRequest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Api/V1/Requests/PaginationRequest.php b/app/Api/V1/Requests/PaginationRequest.php
index 93374a2d84..8fe7cf351f 100644
--- a/app/Api/V1/Requests/PaginationRequest.php
+++ b/app/Api/V1/Requests/PaginationRequest.php
@@ -47,7 +47,7 @@ class PaginationRequest extends ApiRequest
public function rules(): array
{
return [
- 'sort' => ['nullable', new IsValidSortInstruction($this->sortClass)],
+ 'sort' => ['nullable', new IsValidSortInstruction((string)$this->sortClass)],
'limit' => 'numeric|min:1|max:131337',
'page' => 'numeric|min:1|max:131337',
];
From ed2e0e86dcb2226bcf2e9e5e3622dc480647c71d Mon Sep 17 00:00:00 2001
From: James Cole
Date: Fri, 10 Oct 2025 20:54:51 +0200
Subject: [PATCH 31/82] Don't validate on empty string.
---
app/Rules/IsValidSortInstruction.php | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/app/Rules/IsValidSortInstruction.php b/app/Rules/IsValidSortInstruction.php
index 4d9e7b0cf7..6194c6668b 100644
--- a/app/Rules/IsValidSortInstruction.php
+++ b/app/Rules/IsValidSortInstruction.php
@@ -32,19 +32,24 @@ class IsValidSortInstruction implements ValidationRule
public function validate(string $attribute, mixed $value, Closure $fail): void
{
- $shortClass = str_replace('FireflyIII\Models\\', '', $this->class);
+ $shortClass = str_replace('FireflyIII\Models\\', '', $this->class);
if (!is_string($value)) {
$fail('validation.invalid_sort_instruction')->translate(['object' => $shortClass]);
return;
}
+ if ('' === $value) {
+ // don't validate.
+
+ return;
+ }
$validParameters = config(sprintf('firefly.allowed_sort_parameters.%s', $shortClass));
if (!is_array($validParameters)) {
$fail('validation.no_sort_instructions')->translate(['object' => $shortClass]);
return;
}
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
foreach ($parts as $i => $part) {
$part = trim($part);
if (strlen($part) < 2) {
From 0a55e9fb4e5b080e93105cb6b3ae0424de616473 Mon Sep 17 00:00:00 2001
From: JC5
Date: Fri, 10 Oct 2025 21:22:57 +0200
Subject: [PATCH 32/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-10?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Rules/IsValidSortInstruction.php | 4 ++--
config/firefly.php | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/Rules/IsValidSortInstruction.php b/app/Rules/IsValidSortInstruction.php
index 6194c6668b..66ed508fc3 100644
--- a/app/Rules/IsValidSortInstruction.php
+++ b/app/Rules/IsValidSortInstruction.php
@@ -32,7 +32,7 @@ class IsValidSortInstruction implements ValidationRule
public function validate(string $attribute, mixed $value, Closure $fail): void
{
- $shortClass = str_replace('FireflyIII\Models\\', '', $this->class);
+ $shortClass = str_replace('FireflyIII\Models\\', '', $this->class);
if (!is_string($value)) {
$fail('validation.invalid_sort_instruction')->translate(['object' => $shortClass]);
@@ -49,7 +49,7 @@ class IsValidSortInstruction implements ValidationRule
return;
}
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
foreach ($parts as $i => $part) {
$part = trim($part);
if (strlen($part) < 2) {
diff --git a/config/firefly.php b/config/firefly.php
index 6984305564..ad18f33ef0 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-10',
- 'build_time' => 1760116211,
+ 'build_time' => 1760124066,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From cf7ee79c1cbd23264680d4a6a91f1308279065ef Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 11 Oct 2025 06:27:55 +0200
Subject: [PATCH 33/82] Fix two bugs in requests.
---
app/Api/V1/Requests/DateRangeRequest.php | 6 ++++--
app/Api/V1/Requests/DateRequest.php | 1 +
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index 27d6bc7baa..a2e3738489 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -30,8 +30,8 @@ class DateRangeRequest extends ApiRequest
public function rules(): array
{
return [
- 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|', $this->required),
- 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|', $this->required),
+ 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|%s', $this->required),
+ 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|%s', $this->required),
];
}
@@ -39,6 +39,8 @@ class DateRangeRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
+ $this->attributes->set('start', null);
+ $this->attributes->set('end', null);
if (!$validator->valid()) {
return;
}
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index ad9372fee9..c894cfa2b5 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -38,6 +38,7 @@ class DateRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
+ $this->attributes->set('date', null);
if (!$validator->valid()) {
return;
}
From 8c3e6c0189dc076861b865e40238eafe7177e3fd Mon Sep 17 00:00:00 2001
From: JC5
Date: Sat, 11 Oct 2025 06:32:47 +0200
Subject: [PATCH 34/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-11?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/firefly.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/firefly.php b/config/firefly.php
index ad18f33ef0..874bbfee6d 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-10',
- 'build_time' => 1760124066,
+ 'version' => 'develop/2025-10-11',
+ 'build_time' => 1760157052,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From 1fe0aebacbd20204bf5f77ea905c8278b1650064 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 11 Oct 2025 06:45:16 +0200
Subject: [PATCH 35/82] Validate version using build_time.
---
.../Controllers/System/InstallController.php | 15 ++++----
app/Support/System/IsOldVersion.php | 36 +++++--------------
2 files changed, 18 insertions(+), 33 deletions(-)
diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php
index 6e1e168d0a..f07f47758b 100644
--- a/app/Http/Controllers/System/InstallController.php
+++ b/app/Http/Controllers/System/InstallController.php
@@ -34,6 +34,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Laravel\Passport\Passport;
use phpseclib3\Crypt\RSA;
@@ -81,8 +82,10 @@ class InstallController extends Controller
public function index()
{
app('view')->share('FF_VERSION', config('firefly.version'));
+
// index will set FF3 version.
FireflyConfig::set('ff3_version', (string) config('firefly.version'));
+ FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time'));
return view('install.index');
}
@@ -98,18 +101,18 @@ class InstallController extends Controller
'errorMessage' => null,
];
- app('log')->debug(sprintf('Will now run commands. Request index is %d', $requestIndex));
+ Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex));
$indexes = array_keys($this->upgradeCommands);
if (array_key_exists($requestIndex, $indexes)) {
$command = $indexes[$requestIndex];
$parameters = $this->upgradeCommands[$command];
- app('log')->debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters);
+ Log::debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters);
try {
$result = $this->executeCommand($command, $parameters);
} catch (FireflyException $e) {
- app('log')->error($e->getMessage());
- app('log')->error($e->getTraceAsString());
+ Log::error($e->getMessage());
+ Log::error($e->getTraceAsString());
if (str_contains($e->getMessage(), 'open_basedir restriction in effect')) {
$this->lastError = self::BASEDIR_ERROR;
}
@@ -134,7 +137,7 @@ class InstallController extends Controller
*/
private function executeCommand(string $command, array $args): bool
{
- app('log')->debug(sprintf('Will now call command %s with args.', $command), $args);
+ Log::debug(sprintf('Will now call command %s with args.', $command), $args);
try {
if ('generate-keys' === $command) {
@@ -142,7 +145,7 @@ class InstallController extends Controller
}
if ('generate-keys' !== $command) {
Artisan::call($command, $args);
- app('log')->debug(Artisan::output());
+ Log::debug(Artisan::output());
}
} catch (Exception $e) { // intentional generic exception
throw new FireflyException($e->getMessage(), 0, $e);
diff --git a/app/Support/System/IsOldVersion.php b/app/Support/System/IsOldVersion.php
index 2499debbe8..73fcf809ba 100644
--- a/app/Support/System/IsOldVersion.php
+++ b/app/Support/System/IsOldVersion.php
@@ -43,8 +43,8 @@ trait IsOldVersion
return 0;
}
- $currentDate = Carbon::createFromFormat('!Y-m-d', $currentParts[1]);
- $latestDate = Carbon::createFromFormat('!Y-m-d', $latestParts[1]);
+ $currentDate = Carbon::createFromFormat('!Y-m-d', $currentParts[1]);
+ $latestDate = Carbon::createFromFormat('!Y-m-d', $latestParts[1]);
if ($currentDate->lt($latestDate)) {
Log::debug(sprintf('This current version is older, current = %s, latest version %s.', $current, $latest));
@@ -67,33 +67,15 @@ trait IsOldVersion
protected function isOldVersionInstalled(): bool
{
// version compare thing.
- $configVersion = (string)config('firefly.version');
- $dbVersion = (string)FireflyConfig::getFresh('ff3_version', '1.0')->data;
- $compare = 0;
- // compare develop to develop
- if (str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) {
- $compare = $this->compareDevelopVersions($configVersion, $dbVersion);
- }
- // user has develop installed, goes to normal version.
- if (!str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) {
+ $configBuildTime = (int)config('firefly.build_time');
+ $dbBuildTime = (int)FireflyConfig::getFresh('ff3_build_time', 123)->data;
+ $configTime = Carbon::createFromTimestamp($configBuildTime, config('app.timezone'));
+ $dbTime = Carbon::createFromTimestamp($dbBuildTime, config('app.timezone'));
+ if ($dbBuildTime < $configBuildTime) {
+ Log::warning(sprintf('Your database was last managed by an older version of Firefly III (I see %s, I expect %s). Redirect to migrate routine.', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'),));
return true;
}
-
- // user has normal, goes to develop version.
- if (str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) {
- return true;
- }
-
- // compare normal with normal.
- if (!str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) {
- $compare = version_compare($configVersion, $dbVersion);
- }
- if (-1 === $compare) {
- Log::warning(sprintf('The current configured Firefly III version (%s) is older than the required version (%s). Redirect to migrate routine.', $dbVersion, $configVersion));
-
- return true;
- }
-
+ Log::debug(sprintf('Your database is up to date (I see %s, I expect %s).', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'),));
return false;
}
}
From 6980717075123ec4d02b7dbd49f93edd2a0f075e Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 11 Oct 2025 06:47:55 +0200
Subject: [PATCH 36/82] Set version in more places.
---
app/Console/Commands/System/SetsLatestVersion.php | 2 +-
app/Console/Commands/Upgrade/UpgradesDatabase.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/Console/Commands/System/SetsLatestVersion.php b/app/Console/Commands/System/SetsLatestVersion.php
index 3c6f6b5244..823f69fec6 100644
--- a/app/Console/Commands/System/SetsLatestVersion.php
+++ b/app/Console/Commands/System/SetsLatestVersion.php
@@ -46,7 +46,7 @@ class SetsLatestVersion extends Command
return 0;
}
- FireflyConfig::set('ff3_version', config('firefly.version'));
+ FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time'));
$this->friendlyInfo('Updated version.');
return 0;
diff --git a/app/Console/Commands/Upgrade/UpgradesDatabase.php b/app/Console/Commands/Upgrade/UpgradesDatabase.php
index 32ff857c9e..877232ee58 100644
--- a/app/Console/Commands/Upgrade/UpgradesDatabase.php
+++ b/app/Console/Commands/Upgrade/UpgradesDatabase.php
@@ -87,7 +87,7 @@ class UpgradesDatabase extends Command
$this->call($command, $args);
}
// index will set FF3 version.
- FireflyConfig::set('ff3_version', (string) config('firefly.version'));
+ FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time'));
return 0;
}
From 1870345ddf1532b3e8d6b9d317a1fb49c90e9908 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 11 Oct 2025 07:05:32 +0200
Subject: [PATCH 37/82] Add new request in style of aggregated requests, fix
issue with missing field.
---
.../Controllers/Summary/BasicController.php | 16 ++----
app/Api/V1/Requests/AggregateFormRequest.php | 5 ++
app/Api/V1/Requests/DateRangeRequest.php | 2 -
.../CurrencyCodeRequest.php | 49 +++++++++++++++++++
app/Api/V1/Requests/Summary/BasicRequest.php | 37 ++++++++++++++
5 files changed, 95 insertions(+), 14 deletions(-)
create mode 100644 app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
create mode 100644 app/Api/V1/Requests/Summary/BasicRequest.php
diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php
index 6822674139..9c44083daa 100644
--- a/app/Api/V1/Controllers/Summary/BasicController.php
+++ b/app/Api/V1/Controllers/Summary/BasicController.php
@@ -28,6 +28,7 @@ use Carbon\Carbon;
use Exception;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRangeRequest;
+use FireflyIII\Api\V1\Requests\Summary\BasicRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -88,19 +89,10 @@ class BasicController extends Controller
);
}
- /**
- * This endpoint is documented at:
- * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/summary/getBasicSummary
- *
- * @throws Exception
- */
- public function basic(DateRangeRequest $request): JsonResponse
+ public function basic(BasicRequest $request): JsonResponse
{
// parameters for boxes:
- $dates = $request->attributes->all();
- $start = $dates['start'];
- $end = $dates['end'];
- $code = $request->get('currency_code');
+ ['start' => $start, 'end' => $end, 'code' => $code] = $request->attributes->all();
// balance information:
$balanceData = $this->getBalanceInformation($start, $end);
$billData = $this->getSubscriptionInformation($start, $end);
@@ -115,7 +107,7 @@ class BasicController extends Controller
// give new keys
$return = [];
foreach ($total as $entry) {
- if (null === $code || ($code === $entry['currency_code'])) {
+ if ('' === $code || ($code === $entry['currency_code'])) {
$return[$entry['key']] = $entry;
}
}
diff --git a/app/Api/V1/Requests/AggregateFormRequest.php b/app/Api/V1/Requests/AggregateFormRequest.php
index 85579fffc3..001c902fda 100644
--- a/app/Api/V1/Requests/AggregateFormRequest.php
+++ b/app/Api/V1/Requests/AggregateFormRequest.php
@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
use RuntimeException;
@@ -43,12 +44,14 @@ abstract class AggregateFormRequest extends ApiRequest
parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
// instantiate all subrequests and share current requests' bags with them
+ Log::debug('Initializing AggregateFormRequest.');
foreach ($this->getRequests() as $config) {
$requestClass = is_array($config) ? array_shift($config) : $config;
if (!is_a($requestClass, Request::class, true)) {
throw new RuntimeException('getRequests() must return class-strings of subclasses of Request');
}
+ Log::debug(sprintf('Initializing subrequest %s', $requestClass));
$instance = $this->requests[] = new $requestClass();
$instance->request = $this->request;
@@ -63,6 +66,7 @@ abstract class AggregateFormRequest extends ApiRequest
$instance->handleConfig(is_array($config) ? $config : []);
}
}
+ Log::debug('Done initializing AggregateFormRequest.');
}
public function rules(): array
@@ -85,6 +89,7 @@ abstract class AggregateFormRequest extends ApiRequest
// register all subrequests' validators
foreach ($this->requests as $request) {
if (method_exists($request, 'withValidator')) {
+ Log::debug(sprintf('Process withValidator from class %s', get_class($request)));
$request->withValidator($validator);
}
}
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index a2e3738489..831e7b625c 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -39,8 +39,6 @@ class DateRangeRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
- $this->attributes->set('start', null);
- $this->attributes->set('end', null);
if (!$validator->valid()) {
return;
}
diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
new file mode 100644
index 0000000000..56d434b57a
--- /dev/null
+++ b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
@@ -0,0 +1,49 @@
+.
+ */
+
+namespace FireflyIII\Api\V1\Requests\Models\TransactionCurrency;
+
+use FireflyIII\Api\V1\Requests\ApiRequest;
+use Illuminate\Validation\Validator;
+
+class CurrencyCodeRequest extends ApiRequest
+{
+
+ public function rules(): array
+ {
+ return [
+ 'code' => sprintf('exists:transaction_currencies,code|%s', $this->required),
+ ];
+ }
+
+ public function withValidator(Validator $validator): void
+ {
+ $validator->after(
+ function (Validator $validator): void {
+ if (!$validator->valid()) {
+ return;
+ }
+ $code = $this->convertString('code', '');
+ $this->attributes->set('code', $code);
+ }
+ );
+ }
+}
diff --git a/app/Api/V1/Requests/Summary/BasicRequest.php b/app/Api/V1/Requests/Summary/BasicRequest.php
new file mode 100644
index 0000000000..0725b129c9
--- /dev/null
+++ b/app/Api/V1/Requests/Summary/BasicRequest.php
@@ -0,0 +1,37 @@
+.
+ */
+
+namespace FireflyIII\Api\V1\Requests\Summary;
+
+use FireflyIII\Api\V1\Requests\AggregateFormRequest;
+use FireflyIII\Api\V1\Requests\DateRangeRequest;
+use FireflyIII\Api\V1\Requests\Models\TransactionCurrency\CurrencyCodeRequest;
+
+class BasicRequest extends AggregateFormRequest
+{
+ protected function getRequests(): array
+ {
+ return [
+ [DateRangeRequest::class, 'required'],
+ CurrencyCodeRequest::class,
+ ];
+ }
+}
From 977946064d01d8c17e45679cbcbaad4c6d5d08ef Mon Sep 17 00:00:00 2001
From: JC5
Date: Sat, 11 Oct 2025 07:11:46 +0200
Subject: [PATCH 38/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-11?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Api/V1/Controllers/Summary/BasicController.php | 13 ++++++-------
.../TransactionCurrency/CurrencyCodeRequest.php | 3 ++-
app/Api/V1/Requests/Summary/BasicRequest.php | 2 ++
app/Support/System/IsOldVersion.php | 10 ++++++----
config/firefly.php | 2 +-
5 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php
index 9c44083daa..86b65b9e34 100644
--- a/app/Api/V1/Controllers/Summary/BasicController.php
+++ b/app/Api/V1/Controllers/Summary/BasicController.php
@@ -27,7 +27,6 @@ namespace FireflyIII\Api\V1\Controllers\Summary;
use Carbon\Carbon;
use Exception;
use FireflyIII\Api\V1\Controllers\Controller;
-use FireflyIII\Api\V1\Requests\DateRangeRequest;
use FireflyIII\Api\V1\Requests\Summary\BasicRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
@@ -94,18 +93,18 @@ class BasicController extends Controller
// parameters for boxes:
['start' => $start, 'end' => $end, 'code' => $code] = $request->attributes->all();
// balance information:
- $balanceData = $this->getBalanceInformation($start, $end);
- $billData = $this->getSubscriptionInformation($start, $end);
- $spentData = $this->getLeftToSpendInfo($start, $end);
- $netWorthData = $this->getNetWorthInfo($end);
+ $balanceData = $this->getBalanceInformation($start, $end);
+ $billData = $this->getSubscriptionInformation($start, $end);
+ $spentData = $this->getLeftToSpendInfo($start, $end);
+ $netWorthData = $this->getNetWorthInfo($end);
// $balanceData = [];
// $billData = [];
// $spentData = [];
// $netWorthData = [];
- $total = array_merge($balanceData, $billData, $spentData, $netWorthData);
+ $total = array_merge($balanceData, $billData, $spentData, $netWorthData);
// give new keys
- $return = [];
+ $return = [];
foreach ($total as $entry) {
if ('' === $code || ($code === $entry['currency_code'])) {
$return[$entry['key']] = $entry;
diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
index 56d434b57a..72a2c6bc59 100644
--- a/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
+++ b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php
@@ -1,4 +1,6 @@
lt($latestDate)) {
Log::debug(sprintf('This current version is older, current = %s, latest version %s.', $current, $latest));
@@ -72,10 +72,12 @@ trait IsOldVersion
$configTime = Carbon::createFromTimestamp($configBuildTime, config('app.timezone'));
$dbTime = Carbon::createFromTimestamp($dbBuildTime, config('app.timezone'));
if ($dbBuildTime < $configBuildTime) {
- Log::warning(sprintf('Your database was last managed by an older version of Firefly III (I see %s, I expect %s). Redirect to migrate routine.', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'),));
+ Log::warning(sprintf('Your database was last managed by an older version of Firefly III (I see %s, I expect %s). Redirect to migrate routine.', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s')));
+
return true;
}
- Log::debug(sprintf('Your database is up to date (I see %s, I expect %s).', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'),));
+ Log::debug(sprintf('Your database is up to date (I see %s, I expect %s).', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s')));
+
return false;
}
}
diff --git a/config/firefly.php b/config/firefly.php
index 874bbfee6d..dc7f378c56 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-11',
- 'build_time' => 1760157052,
+ 'build_time' => 1760159380,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From d47e4c4f24fc10e15e916290fa77d15a702b5c32 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 11 Oct 2025 15:17:51 +0200
Subject: [PATCH 39/82] Fix #11050
---
app/Api/V1/Requests/DateRangeRequest.php | 3 +++
app/Api/V1/Requests/DateRequest.php | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index 831e7b625c..2ed73be358 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -40,6 +40,9 @@ class DateRangeRequest extends ApiRequest
$validator->after(
function (Validator $validator): void {
if (!$validator->valid()) {
+ // set null values
+ $this->attributes->set('start', null);
+ $this->attributes->set('end', null);
return;
}
$start = $this->getCarbonDate('start')?->startOfDay();
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index c894cfa2b5..9b8e8221d7 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
+use Carbon\Carbon;
use Illuminate\Validation\Validator;
class DateRequest extends ApiRequest
@@ -47,7 +48,7 @@ class DateRequest extends ApiRequest
// if we also have a range, date must be in that range
$start = $this->attributes->get('start');
$end = $this->attributes->get('end');
- if ($date && $start && $end && !$date->between($start, $end)) {
+ if ($date instanceOf Carbon && $start instanceOf Carbon && $end instanceOf Carbon && !$date->between($start, $end)) {
$validator->errors()->add('date', (string)trans('validation.between_date'));
}
From 78e832cdbaca5427d418dc4e16901bda8a83e5a7 Mon Sep 17 00:00:00 2001
From: JC5
Date: Sat, 11 Oct 2025 15:23:33 +0200
Subject: [PATCH 40/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-11?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Api/V1/Requests/DateRangeRequest.php | 1 +
app/Api/V1/Requests/DateRequest.php | 2 +-
config/firefly.php | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index 2ed73be358..cb912f5293 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -43,6 +43,7 @@ class DateRangeRequest extends ApiRequest
// set null values
$this->attributes->set('start', null);
$this->attributes->set('end', null);
+
return;
}
$start = $this->getCarbonDate('start')?->startOfDay();
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index 9b8e8221d7..db74d86098 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -48,7 +48,7 @@ class DateRequest extends ApiRequest
// if we also have a range, date must be in that range
$start = $this->attributes->get('start');
$end = $this->attributes->get('end');
- if ($date instanceOf Carbon && $start instanceOf Carbon && $end instanceOf Carbon && !$date->between($start, $end)) {
+ if ($date instanceof Carbon && $start instanceof Carbon && $end instanceof Carbon && !$date->between($start, $end)) {
$validator->errors()->add('date', (string)trans('validation.between_date'));
}
diff --git a/config/firefly.php b/config/firefly.php
index dc7f378c56..e8b8df4e28 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-10-11',
- 'build_time' => 1760159380,
+ 'build_time' => 1760188898,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
From 2a4a98dd104f8e4281531c231c9af1661a9aa800 Mon Sep 17 00:00:00 2001
From: Nicky De Maeyer
Date: Sat, 11 Oct 2025 23:02:54 +0200
Subject: [PATCH 41/82] use the correct validator function to check for errors,
add a test for ShowController
---
app/Api/V1/Requests/DateRangeRequest.php | 2 +-
app/Api/V1/Requests/DateRequest.php | 3 +-
.../Models/Account/AccountTypeApiRequest.php | 2 +-
app/Api/V1/Requests/PaginationRequest.php | 2 +-
database/factories/AccountFactory.php | 24 +++++
.../Api/Models/Account/ShowControllerTest.php | 90 +++++++++++++++++++
6 files changed, 118 insertions(+), 5 deletions(-)
create mode 100644 database/factories/AccountFactory.php
create mode 100644 tests/integration/Api/Models/Account/ShowControllerTest.php
diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php
index cb912f5293..bfefb467a6 100644
--- a/app/Api/V1/Requests/DateRangeRequest.php
+++ b/app/Api/V1/Requests/DateRangeRequest.php
@@ -39,7 +39,7 @@ class DateRangeRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
- if (!$validator->valid()) {
+ if ($validator->failed()) {
// set null values
$this->attributes->set('start', null);
$this->attributes->set('end', null);
diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php
index db74d86098..b73970786d 100644
--- a/app/Api/V1/Requests/DateRequest.php
+++ b/app/Api/V1/Requests/DateRequest.php
@@ -39,8 +39,7 @@ class DateRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
- $this->attributes->set('date', null);
- if (!$validator->valid()) {
+ if ($validator->failed()) {
return;
}
$date = $this->getCarbonDate('date')?->endOfDay();
diff --git a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
index e24f6da655..e1e6bc686f 100644
--- a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
+++ b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
@@ -42,7 +42,7 @@ class AccountTypeApiRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
- if (!$validator->valid()) {
+ if ($validator->failed()) {
return;
}
diff --git a/app/Api/V1/Requests/PaginationRequest.php b/app/Api/V1/Requests/PaginationRequest.php
index 8fe7cf351f..9b080007dc 100644
--- a/app/Api/V1/Requests/PaginationRequest.php
+++ b/app/Api/V1/Requests/PaginationRequest.php
@@ -57,7 +57,7 @@ class PaginationRequest extends ApiRequest
{
$validator->after(
function (Validator $validator): void {
- if (!$validator->valid()) {
+ if ($validator->failed()) {
return;
}
diff --git a/database/factories/AccountFactory.php b/database/factories/AccountFactory.php
new file mode 100644
index 0000000000..dd2eb4ac04
--- /dev/null
+++ b/database/factories/AccountFactory.php
@@ -0,0 +1,24 @@
+ $this->faker->name(),
+ 'active' => true,
+ ];
+ }
+
+ public function withType(AccountTypeEnum $type): static
+ {
+ return $this->for(AccountType::where('type', $type->value)->first());
+ }
+}
diff --git a/tests/integration/Api/Models/Account/ShowControllerTest.php b/tests/integration/Api/Models/Account/ShowControllerTest.php
new file mode 100644
index 0000000000..6975558388
--- /dev/null
+++ b/tests/integration/Api/Models/Account/ShowControllerTest.php
@@ -0,0 +1,90 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace Tests\integration\Api\Models\Account;
+
+use FireflyIII\Enums\AccountTypeEnum;
+use FireflyIII\Models\Account;
+use FireflyIII\User;
+use Override;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Tests\integration\TestCase;
+
+/**
+ * @internal
+ *
+ * @covers \FireflyIII\Api\V1\Controllers\Models\Account\ShowController
+ */
+final class ShowControllerTest extends TestCase
+{
+ use RefreshDatabase;
+ private User $user;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->user = $this->createAuthenticatedUser();
+ $this->actingAs($this->user);
+
+ Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
+ Account::factory()->for($this->user)->withType(AccountTypeEnum::REVENUE)->create();
+ Account::factory()->for($this->user)->withType(AccountTypeEnum::EXPENSE)->create();
+ Account::factory()->for($this->user)->withType(AccountTypeEnum::DEBT)->create();
+ Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
+ }
+
+ public function testIndex(): void
+ {
+ $this->actingAs($this->user);
+ $response = $this->getJson(route('api.v1.accounts.index'));
+ $response->assertStatus(200);
+ $response->assertJson([
+ 'meta' => ['pagination' => ['total' => 5]],
+ ]);
+ }
+
+ public function testIndexFailsOnUnknownAccountType(): void
+ {
+ $this->actingAs($this->user);
+ $response = $this->getJson(route('api.v1.accounts.index').'?type=foobar');
+ $response->assertStatus(422);
+ $response->assertJson(['errors' => ['type' => ['The selected type is invalid.']]]);
+ }
+
+ public function testIndexCanFilterOnAccountType(): void
+ {
+ $this->actingAs($this->user);
+ $response = $this->getJson(route('api.v1.accounts.index').'?type=asset');
+ $response->assertStatus(200);
+ $response->assertJson([
+ 'data' => [
+ ['attributes' => ['type' => 'asset']],
+ ['attributes' => ['type' => 'asset']],
+ ],
+ 'meta' => ['pagination' => ['total' => 2]],
+ ]);
+ }
+}
From df3e4a65548c9d7479e55033b27350c6ae5cfc44 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sun, 12 Oct 2025 12:25:21 +0200
Subject: [PATCH 42/82] Fix #11054
---
app/Factory/TransactionJournalFactory.php | 5 +++++
app/Services/Internal/Support/JournalServiceTrait.php | 3 +++
2 files changed, 8 insertions(+)
diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php
index d11753a2bf..f7dfa70f5c 100644
--- a/app/Factory/TransactionJournalFactory.php
+++ b/app/Factory/TransactionJournalFactory.php
@@ -225,6 +225,11 @@ class TransactionJournalFactory
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
Log::debug('Done with getAccount(2x)');
+ // there is a safety catch here. If either account is NULL, they will be replaced with the cash account.
+ if(null === $destinationAccount) {
+ Log::warning('Destination account is NULL, will replace with cash account.');
+ $destinationAccount = $this->accountRepository->getCashAccount();
+ }
// this is the moment for a reconciliation sanity check (again).
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php
index a4ce044ebf..992aa8777d 100644
--- a/app/Services/Internal/Support/JournalServiceTrait.php
+++ b/app/Services/Internal/Support/JournalServiceTrait.php
@@ -88,6 +88,7 @@ trait JournalServiceTrait
// the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case
$creatableType = $this->getCreatableType($expectedTypes[$transactionType]);
+ Log::debug(sprintf('Creatable type is "%s"', $creatableType ), $expectedTypes[$transactionType]);
// if the result is NULL but the ID is set, an account could exist of the wrong type.
// that data can be used to create a new account of the right type.
@@ -227,9 +228,11 @@ trait JournalServiceTrait
}
// find by preferred type.
+ Log::debug('Find by preferred type.');
$result = $this->accountRepository->findByName($data['name'], [$types[0]]);
// or any expected type.
+ Log::debug('Find by any expected type.');
$result ??= $this->accountRepository->findByName($data['name'], $types);
if (null !== $result) {
From 55cf9247947bbf60d3bba041603a54c2daffca90 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sun, 12 Oct 2025 16:02:14 +0200
Subject: [PATCH 43/82] Another fix for #11054
---
app/Factory/TransactionJournalFactory.php | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php
index f7dfa70f5c..ed9249f72b 100644
--- a/app/Factory/TransactionJournalFactory.php
+++ b/app/Factory/TransactionJournalFactory.php
@@ -221,8 +221,19 @@ class TransactionJournalFactory
];
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
- $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
- $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
+ $destinationAccount = null;
+ $sourceAccount = null;
+ if(TransactionTypeEnum::DEPOSIT->value === $type->type) {
+ Log::debug(sprintf('Transaction type is deposit, start with destination first.'));
+ $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
+ $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount);
+ }
+ if(TransactionTypeEnum::DEPOSIT->value !== $type->type) {
+ Log::debug(sprintf('Transaction type is not deposit, start with source first.'));
+ $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
+ $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
+ }
+
Log::debug('Done with getAccount(2x)');
// there is a safety catch here. If either account is NULL, they will be replaced with the cash account.
From f52bc0e242b0d04df3f667a108bd63265fd6808a Mon Sep 17 00:00:00 2001
From: JC5
Date: Sun, 12 Oct 2025 16:06:57 +0200
Subject: [PATCH 44/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-12?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Factory/TransactionJournalFactory.php | 22 +++++++++----------
.../Internal/Support/JournalServiceTrait.php | 2 +-
composer.lock | 12 +++++-----
config/firefly.php | 4 ++--
package-lock.json | 12 +++++-----
.../Api/Models/Account/ShowControllerTest.php | 1 -
6 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php
index ed9249f72b..cd47828c9d 100644
--- a/app/Factory/TransactionJournalFactory.php
+++ b/app/Factory/TransactionJournalFactory.php
@@ -221,23 +221,23 @@ class TransactionJournalFactory
];
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
- $destinationAccount = null;
- $sourceAccount = null;
- if(TransactionTypeEnum::DEPOSIT->value === $type->type) {
- Log::debug(sprintf('Transaction type is deposit, start with destination first.'));
- $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
- $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount);
+ $destinationAccount = null;
+ $sourceAccount = null;
+ if (TransactionTypeEnum::DEPOSIT->value === $type->type) {
+ Log::debug('Transaction type is deposit, start with destination first.');
+ $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
+ $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount);
}
- if(TransactionTypeEnum::DEPOSIT->value !== $type->type) {
- Log::debug(sprintf('Transaction type is not deposit, start with source first.'));
- $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
- $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
+ if (TransactionTypeEnum::DEPOSIT->value !== $type->type) {
+ Log::debug('Transaction type is not deposit, start with source first.');
+ $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
+ $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
}
Log::debug('Done with getAccount(2x)');
// there is a safety catch here. If either account is NULL, they will be replaced with the cash account.
- if(null === $destinationAccount) {
+ if (null === $destinationAccount) {
Log::warning('Destination account is NULL, will replace with cash account.');
$destinationAccount = $this->accountRepository->getCashAccount();
}
diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php
index 992aa8777d..ec9a7dec3e 100644
--- a/app/Services/Internal/Support/JournalServiceTrait.php
+++ b/app/Services/Internal/Support/JournalServiceTrait.php
@@ -88,7 +88,7 @@ trait JournalServiceTrait
// the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case
$creatableType = $this->getCreatableType($expectedTypes[$transactionType]);
- Log::debug(sprintf('Creatable type is "%s"', $creatableType ), $expectedTypes[$transactionType]);
+ Log::debug(sprintf('Creatable type is "%s"', $creatableType), $expectedTypes[$transactionType]);
// if the result is NULL but the ID is set, an account could exist of the wrong type.
// that data can be used to create a new account of the right type.
diff --git a/composer.lock b/composer.lock
index 8ea016fcc7..0797b98e0e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -11920,16 +11920,16 @@
},
{
"name": "rector/rector",
- "version": "2.2.2",
+ "version": "2.2.3",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed"
+ "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/5b353f7457b9a0c63fc91ef340f5d119a40991ed",
- "reference": "5b353f7457b9a0c63fc91ef340f5d119a40991ed",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f",
+ "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f",
"shasum": ""
},
"require": {
@@ -11968,7 +11968,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.2.2"
+ "source": "https://github.com/rectorphp/rector/tree/2.2.3"
},
"funding": [
{
@@ -11976,7 +11976,7 @@
"type": "github"
}
],
- "time": "2025-10-09T19:50:20+00:00"
+ "time": "2025-10-11T21:50:23+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/config/firefly.php b/config/firefly.php
index e8b8df4e28..c33eba108d 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-11',
- 'build_time' => 1760188898,
+ 'version' => 'develop/2025-10-12',
+ 'build_time' => 1760277899,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 7e058505f3..4b64ccf86c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3173,9 +3173,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.7.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.1.tgz",
- "integrity": "sha512-CmyhGZanP88uuC5GpWU9q+fI61j2SkhO3UGMUdfYRE6Bcy0ccyzn1Rqj9YAB/ZY4kOXmNf0ocah5GtphmLMP6Q==",
+ "version": "24.7.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
+ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4521,9 +4521,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001749",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
- "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
+ "version": "1.0.30001750",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
+ "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
"dev": true,
"funding": [
{
diff --git a/tests/integration/Api/Models/Account/ShowControllerTest.php b/tests/integration/Api/Models/Account/ShowControllerTest.php
index 6975558388..e952e63b16 100644
--- a/tests/integration/Api/Models/Account/ShowControllerTest.php
+++ b/tests/integration/Api/Models/Account/ShowControllerTest.php
@@ -28,7 +28,6 @@ namespace Tests\integration\Api\Models\Account;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Models\Account;
use FireflyIII\User;
-use Override;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
From 7bd915930c7cd2678779a92e75bd8d672d9bda38 Mon Sep 17 00:00:00 2001
From: Nicky De Maeyer
Date: Sun, 12 Oct 2025 19:53:12 +0200
Subject: [PATCH 45/82] account/attachments endpoint use request object for
pagination, add test
---
.../Models/Account/ListController.php | 14 +--
app/Factory/AttachmentFactory.php | 4 +-
.../Api/Models/Account/ListControllerTest.php | 88 +++++++++++++++++++
3 files changed, 100 insertions(+), 6 deletions(-)
create mode 100644 tests/integration/Api/Models/Account/ListControllerTest.php
diff --git a/app/Api/V1/Controllers/Models/Account/ListController.php b/app/Api/V1/Controllers/Models/Account/ListController.php
index b230572b93..c63a3a9251 100644
--- a/app/Api/V1/Controllers/Models/Account/ListController.php
+++ b/app/Api/V1/Controllers/Models/Account/ListController.php
@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Account;
use FireflyIII\Api\V1\Controllers\Controller;
+use FireflyIII\Api\V1\Requests\PaginationRequest;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -69,22 +70,25 @@ class ListController extends Controller
);
}
- public function attachments(Account $account): JsonResponse
+ public function attachments(Account $account, PaginationRequest $request): JsonResponse
{
$manager = $this->getManager();
- $pageSize = $this->parameters->get('limit');
+ [
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'page' => $page,
+ ] = $request->attributes->all();
$collection = $this->repository->getAttachments($account);
$count = $collection->count();
- $attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
+ $attachments = $collection->slice($offset, $limit);
// make paginator:
- $paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page'));
+ $paginator = new LengthAwarePaginator($attachments, $count, $limit, $page);
$paginator->setPath(route('api.v1.accounts.attachments', [$account->id]).$this->buildParams());
/** @var AttachmentTransformer $transformer */
$transformer = app(AttachmentTransformer::class);
- $transformer->setParameters($this->parameters);
$resource = new FractalCollection($attachments, $transformer, 'attachments');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php
index 421af6dca2..74e8643886 100644
--- a/app/Factory/AttachmentFactory.php
+++ b/app/Factory/AttachmentFactory.php
@@ -84,8 +84,10 @@ class AttachmentFactory
return $attachment;
}
- public function setUser(User $user): void
+ public function setUser(User $user): static
{
$this->user = $user;
+
+ return $this;
}
}
diff --git a/tests/integration/Api/Models/Account/ListControllerTest.php b/tests/integration/Api/Models/Account/ListControllerTest.php
new file mode 100644
index 0000000000..269234e54b
--- /dev/null
+++ b/tests/integration/Api/Models/Account/ListControllerTest.php
@@ -0,0 +1,88 @@
+.
+ */
+
+declare(strict_types=1);
+
+namespace Tests\integration\Api\Models\Account;
+
+use FireflyIII\Enums\AccountTypeEnum;
+use FireflyIII\Factory\AttachmentFactory;
+use FireflyIII\Models\Account;
+use FireflyIII\Models\Category;
+use FireflyIII\User;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Tests\integration\TestCase;
+
+/**
+ * @internal
+ *
+ * @covers \FireflyIII\Api\V1\Controllers\Models\Account\ListController
+ */
+final class ListControllerTest extends TestCase
+{
+ use RefreshDatabase;
+ private User $user;
+ private Account $account;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->user = $this->createAuthenticatedUser();
+ $this->actingAs($this->user);
+
+ $this->account = Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
+ app(AttachmentFactory::class)->setUser($this->user)->create([
+ 'filename' => 'test 1',
+ 'title' => 'test 1',
+ 'attachable_type' => Account::class,
+ 'attachable_id' => $this->account->id,
+ ]);
+ app(AttachmentFactory::class)->setUser($this->user)->create([
+ 'filename' => 'test 2',
+ 'title' => 'test 2',
+ 'attachable_type' => Account::class,
+ 'attachable_id' => $this->account->id,
+ ]);
+ }
+
+ public function testIndex(): void
+ {
+ $this->actingAs($this->user);
+ $response = $this->getJson(route('api.v1.accounts.attachments', ['account' => $this->account->id]));
+ $response->assertStatus(200);
+ $response->assertJson([
+ 'meta' => ['pagination' => ['total' => 2, 'total_pages' => 1]],
+ ]);
+ }
+
+ public function testIndexCanChangePageSize(): void
+ {
+ $this->actingAs($this->user);
+ $response = $this->getJson(route('api.v1.accounts.attachments', ['account' => $this->account->id, 'limit' => 1]));
+ $response->assertStatus(200);
+ $response->assertJson([
+ 'meta' => ['pagination' => ['total' => 2, 'total_pages' => 2]],
+ ]);
+ }
+}
From e1cf9f7a794d838d53d47d5f16535189ac9f611c Mon Sep 17 00:00:00 2001
From: JC5
Date: Mon, 13 Oct 2025 05:25:17 +0200
Subject: [PATCH 46/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-13?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../V1/Controllers/Models/Account/ListController.php | 6 +++---
composer.lock | 12 ++++++------
config/firefly.php | 4 ++--
package-lock.json | 6 +++---
.../Api/Models/Account/ListControllerTest.php | 3 +--
5 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/app/Api/V1/Controllers/Models/Account/ListController.php b/app/Api/V1/Controllers/Models/Account/ListController.php
index c63a3a9251..ef295776f6 100644
--- a/app/Api/V1/Controllers/Models/Account/ListController.php
+++ b/app/Api/V1/Controllers/Models/Account/ListController.php
@@ -74,10 +74,10 @@ class ListController extends Controller
{
$manager = $this->getManager();
[
- 'limit' => $limit,
+ 'limit' => $limit,
'offset' => $offset,
- 'page' => $page,
- ] = $request->attributes->all();
+ 'page' => $page,
+ ] = $request->attributes->all();
$collection = $this->repository->getAttachments($account);
$count = $collection->count();
diff --git a/composer.lock b/composer.lock
index 0797b98e0e..2c92afce0d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -10549,16 +10549,16 @@
},
{
"name": "driftingly/rector-laravel",
- "version": "2.0.7",
+ "version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/driftingly/rector-laravel.git",
- "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e"
+ "reference": "efb636a08dfddfa2a3f4527b1dd970a898a075a4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/625dc02cee08d47ecf0ac86de2f02a55026cf34e",
- "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e",
+ "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/efb636a08dfddfa2a3f4527b1dd970a898a075a4",
+ "reference": "efb636a08dfddfa2a3f4527b1dd970a898a075a4",
"shasum": ""
},
"require": {
@@ -10578,9 +10578,9 @@
"description": "Rector upgrades rules for Laravel Framework",
"support": {
"issues": "https://github.com/driftingly/rector-laravel/issues",
- "source": "https://github.com/driftingly/rector-laravel/tree/2.0.7"
+ "source": "https://github.com/driftingly/rector-laravel/tree/2.1.0"
},
- "time": "2025-08-19T20:49:47+00:00"
+ "time": "2025-10-12T21:51:39+00:00"
},
{
"name": "fakerphp/faker",
diff --git a/config/firefly.php b/config/firefly.php
index c33eba108d..72ba229f99 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-12',
- 'build_time' => 1760277899,
+ 'version' => 'develop/2025-10-13',
+ 'build_time' => 1760325798,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index 4b64ccf86c..c1496a7e4f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5820,9 +5820,9 @@
}
},
"node_modules/envinfo": {
- "version": "7.17.0",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.17.0.tgz",
- "integrity": "sha512-GpfViocsFM7viwClFgxK26OtjMlKN67GCR5v6ASFkotxtpBWd9d+vNy+AH7F2E1TUkMDZ8P/dDPZX71/NG8xnQ==",
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.18.0.tgz",
+ "integrity": "sha512-02QGCLRW+Jb8PC270ic02lat+N57iBaWsvHjcJViqp6UVupRB+Vsg7brYPTqEFXvsdTql3KnSczv5ModZFpl8Q==",
"dev": true,
"license": "MIT",
"bin": {
diff --git a/tests/integration/Api/Models/Account/ListControllerTest.php b/tests/integration/Api/Models/Account/ListControllerTest.php
index 269234e54b..d22823fefb 100644
--- a/tests/integration/Api/Models/Account/ListControllerTest.php
+++ b/tests/integration/Api/Models/Account/ListControllerTest.php
@@ -28,7 +28,6 @@ namespace Tests\integration\Api\Models\Account;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Factory\AttachmentFactory;
use FireflyIII\Models\Account;
-use FireflyIII\Models\Category;
use FireflyIII\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\integration\TestCase;
@@ -48,7 +47,7 @@ final class ListControllerTest extends TestCase
{
parent::setUp();
- $this->user = $this->createAuthenticatedUser();
+ $this->user = $this->createAuthenticatedUser();
$this->actingAs($this->user);
$this->account = Account::factory()->for($this->user)->withType(AccountTypeEnum::ASSET)->create();
From 9eb3ad62ddd9ebc1aae23eed8549b3b981238f9d Mon Sep 17 00:00:00 2001
From: JC5
Date: Mon, 20 Oct 2025 05:28:22 +0200
Subject: [PATCH 47/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-20?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.ci/php-cs-fixer/composer.lock | 13 +-
composer.lock | 70 ++---
config/firefly.php | 4 +-
package-lock.json | 456 ++++++++++++++++-----------------
4 files changed, 271 insertions(+), 272 deletions(-)
diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock
index 4e1d937036..71a3131cac 100644
--- a/.ci/php-cs-fixer/composer.lock
+++ b/.ci/php-cs-fixer/composer.lock
@@ -402,16 +402,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.88.2",
+ "version": "v3.89.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99"
+ "reference": "4dd6768cb7558440d27d18f54909eee417317ce9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99",
- "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4dd6768cb7558440d27d18f54909eee417317ce9",
+ "reference": "4dd6768cb7558440d27d18f54909eee417317ce9",
"shasum": ""
},
"require": {
@@ -426,7 +426,6 @@
"php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6",
"react/event-loop": "^1.5",
- "react/promise": "^3.3",
"react/socket": "^1.16",
"react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
@@ -494,7 +493,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.0"
},
"funding": [
{
@@ -502,7 +501,7 @@
"type": "github"
}
],
- "time": "2025-09-27T00:24:15+00:00"
+ "time": "2025-10-18T19:30:16+00:00"
},
{
"name": "psr/container",
diff --git a/composer.lock b/composer.lock
index 2c92afce0d..5782a664d6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
- "version": "v12.33.0",
+ "version": "v12.34.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1"
+ "reference": "f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/124efc5f09d4668a4dc13f94a1018c524a58bcb1",
- "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687",
+ "reference": "f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687",
"shasum": ""
},
"require": {
@@ -1999,7 +1999,7 @@
"league/flysystem-sftp-v3": "^3.25.1",
"mockery/mockery": "^1.6.10",
"opis/json-schema": "^2.4.1",
- "orchestra/testbench-core": "^10.6.5",
+ "orchestra/testbench-core": "^10.7.0",
"pda/pheanstalk": "^5.0.6|^7.0.0",
"php-http/discovery": "^1.15",
"phpstan/phpstan": "^2.0",
@@ -2093,7 +2093,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2025-10-07T14:30:39+00:00"
+ "time": "2025-10-14T13:58:31+00:00"
},
{
"name": "laravel/passport",
@@ -2296,16 +2296,16 @@
},
{
"name": "laravel/serializable-closure",
- "version": "v2.0.5",
+ "version": "v2.0.6",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed"
+ "reference": "038ce42edee619599a1debb7e81d7b3759492819"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3832547db6e0e2f8bb03d4093857b378c66eceed",
- "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819",
+ "reference": "038ce42edee619599a1debb7e81d7b3759492819",
"shasum": ""
},
"require": {
@@ -2353,7 +2353,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2025-09-22T17:29:40+00:00"
+ "time": "2025-10-09T13:42:30+00:00"
},
{
"name": "laravel/slack-notification-channel",
@@ -2549,22 +2549,22 @@
},
{
"name": "lcobucci/jwt",
- "version": "5.5.0",
+ "version": "5.6.0",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
- "reference": "a835af59b030d3f2967725697cf88300f579088e"
+ "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e",
- "reference": "a835af59b030d3f2967725697cf88300f579088e",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e",
+ "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"ext-sodium": "*",
- "php": "~8.2.0 || ~8.3.0 || ~8.4.0",
+ "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"psr/clock": "^1.0"
},
"require-dev": {
@@ -2606,7 +2606,7 @@
],
"support": {
"issues": "https://github.com/lcobucci/jwt/issues",
- "source": "https://github.com/lcobucci/jwt/tree/5.5.0"
+ "source": "https://github.com/lcobucci/jwt/tree/5.6.0"
},
"funding": [
{
@@ -2618,7 +2618,7 @@
"type": "patreon"
}
],
- "time": "2025-01-26T21:29:45+00:00"
+ "time": "2025-10-17T11:30:53+00:00"
},
{
"name": "league/commonmark",
@@ -2811,16 +2811,16 @@
},
{
"name": "league/csv",
- "version": "9.26.0",
+ "version": "9.27.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
- "reference": "7fce732754d043f3938899e5183e2d0f3d31b571"
+ "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571",
- "reference": "7fce732754d043f3938899e5183e2d0f3d31b571",
+ "url": "https://api.github.com/repos/thephpleague/csv/zipball/cb491b1ba3c42ff2bcd0113814f4256b42bae845",
+ "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845",
"shasum": ""
},
"require": {
@@ -2898,7 +2898,7 @@
"type": "github"
}
],
- "time": "2025-10-01T11:24:54+00:00"
+ "time": "2025-10-16T08:22:09+00:00"
},
{
"name": "league/event",
@@ -4069,31 +4069,31 @@
},
{
"name": "nunomaduro/termwind",
- "version": "v2.3.1",
+ "version": "v2.3.2",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/termwind.git",
- "reference": "dfa08f390e509967a15c22493dc0bac5733d9123"
+ "reference": "eb61920a53057a7debd718a5b89c2178032b52c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123",
- "reference": "dfa08f390e509967a15c22493dc0bac5733d9123",
+ "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0",
+ "reference": "eb61920a53057a7debd718a5b89c2178032b52c0",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^8.2",
- "symfony/console": "^7.2.6"
+ "symfony/console": "^7.3.4"
},
"require-dev": {
- "illuminate/console": "^11.44.7",
- "laravel/pint": "^1.22.0",
+ "illuminate/console": "^11.46.1",
+ "laravel/pint": "^1.25.1",
"mockery/mockery": "^1.6.12",
- "pestphp/pest": "^2.36.0 || ^3.8.2",
- "phpstan/phpstan": "^1.12.25",
+ "pestphp/pest": "^2.36.0 || ^3.8.4",
+ "phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-strict-rules": "^1.6.2",
- "symfony/var-dumper": "^7.2.6",
+ "symfony/var-dumper": "^7.3.4",
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@@ -4136,7 +4136,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/termwind/issues",
- "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1"
+ "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2"
},
"funding": [
{
@@ -4152,7 +4152,7 @@
"type": "github"
}
],
- "time": "2025-05-08T08:14:37+00:00"
+ "time": "2025-10-18T11:10:27+00:00"
},
{
"name": "nyholm/psr7",
diff --git a/config/firefly.php b/config/firefly.php
index 72ba229f99..94dbbd6545 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-13',
- 'build_time' => 1760325798,
+ 'version' => 'develop/2025-10-20',
+ 'build_time' => 1760930795,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index c1496a7e4f..f71ec96e8f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1693,9 +1693,9 @@
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
- "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
+ "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==",
"cpu": [
"ppc64"
],
@@ -1710,9 +1710,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz",
- "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz",
+ "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==",
"cpu": [
"arm"
],
@@ -1727,9 +1727,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz",
- "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz",
+ "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==",
"cpu": [
"arm64"
],
@@ -1744,9 +1744,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz",
- "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz",
+ "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==",
"cpu": [
"x64"
],
@@ -1761,9 +1761,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz",
- "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz",
+ "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==",
"cpu": [
"arm64"
],
@@ -1778,9 +1778,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz",
- "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz",
+ "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==",
"cpu": [
"x64"
],
@@ -1795,9 +1795,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz",
- "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==",
"cpu": [
"arm64"
],
@@ -1812,9 +1812,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz",
- "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz",
+ "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==",
"cpu": [
"x64"
],
@@ -1829,9 +1829,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz",
- "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz",
+ "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==",
"cpu": [
"arm"
],
@@ -1846,9 +1846,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz",
- "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==",
"cpu": [
"arm64"
],
@@ -1863,9 +1863,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz",
- "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz",
+ "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==",
"cpu": [
"ia32"
],
@@ -1880,9 +1880,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz",
- "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz",
+ "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==",
"cpu": [
"loong64"
],
@@ -1897,9 +1897,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz",
- "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz",
+ "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==",
"cpu": [
"mips64el"
],
@@ -1914,9 +1914,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz",
- "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz",
+ "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==",
"cpu": [
"ppc64"
],
@@ -1931,9 +1931,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz",
- "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz",
+ "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==",
"cpu": [
"riscv64"
],
@@ -1948,9 +1948,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz",
- "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz",
+ "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==",
"cpu": [
"s390x"
],
@@ -1965,9 +1965,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz",
- "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz",
+ "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==",
"cpu": [
"x64"
],
@@ -1982,9 +1982,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz",
- "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==",
"cpu": [
"arm64"
],
@@ -1999,9 +1999,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz",
- "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==",
"cpu": [
"x64"
],
@@ -2016,9 +2016,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz",
- "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz",
+ "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==",
"cpu": [
"arm64"
],
@@ -2033,9 +2033,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz",
- "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz",
+ "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==",
"cpu": [
"x64"
],
@@ -2050,9 +2050,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz",
- "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz",
+ "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==",
"cpu": [
"arm64"
],
@@ -2067,9 +2067,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz",
- "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz",
+ "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==",
"cpu": [
"x64"
],
@@ -2084,9 +2084,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz",
- "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz",
+ "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==",
"cpu": [
"arm64"
],
@@ -2101,9 +2101,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz",
- "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz",
+ "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==",
"cpu": [
"ia32"
],
@@ -2118,9 +2118,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
- "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz",
+ "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==",
"cpu": [
"x64"
],
@@ -2589,9 +2589,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
- "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
+ "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
"cpu": [
"arm"
],
@@ -2603,9 +2603,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
- "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
+ "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
"cpu": [
"arm64"
],
@@ -2617,9 +2617,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
- "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
+ "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
"cpu": [
"arm64"
],
@@ -2631,9 +2631,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
- "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
+ "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
"cpu": [
"x64"
],
@@ -2645,9 +2645,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
- "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
+ "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
"cpu": [
"arm64"
],
@@ -2659,9 +2659,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
- "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
+ "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
"cpu": [
"x64"
],
@@ -2673,9 +2673,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
- "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
+ "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
"cpu": [
"arm"
],
@@ -2687,9 +2687,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
- "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
+ "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
"cpu": [
"arm"
],
@@ -2701,9 +2701,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
- "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
+ "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
"cpu": [
"arm64"
],
@@ -2715,9 +2715,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
- "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
+ "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
"cpu": [
"arm64"
],
@@ -2729,9 +2729,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
- "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
+ "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
"cpu": [
"loong64"
],
@@ -2743,9 +2743,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
- "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
+ "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
"cpu": [
"ppc64"
],
@@ -2757,9 +2757,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
- "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
+ "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
"cpu": [
"riscv64"
],
@@ -2771,9 +2771,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
- "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
+ "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
"cpu": [
"riscv64"
],
@@ -2785,9 +2785,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
- "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
+ "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
"cpu": [
"s390x"
],
@@ -2799,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
- "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
"cpu": [
"x64"
],
@@ -2813,9 +2813,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
- "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
+ "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
"cpu": [
"x64"
],
@@ -2827,9 +2827,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
- "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
+ "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
"cpu": [
"arm64"
],
@@ -2841,9 +2841,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
- "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
+ "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
"cpu": [
"arm64"
],
@@ -2855,9 +2855,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
- "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
+ "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
"cpu": [
"ia32"
],
@@ -2869,9 +2869,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
- "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
+ "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
"cpu": [
"x64"
],
@@ -2883,9 +2883,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
- "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
+ "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
"cpu": [
"x64"
],
@@ -3173,9 +3173,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.7.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz",
- "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
+ "version": "24.8.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz",
+ "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.16",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz",
- "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==",
+ "version": "2.8.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz",
+ "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4521,9 +4521,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001750",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz",
- "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==",
+ "version": "1.0.30001751",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
+ "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==",
"dev": true,
"funding": [
{
@@ -4569,9 +4569,9 @@
}
},
"node_modules/chart.js": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
- "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@@ -5736,9 +5736,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.234",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
- "integrity": "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg==",
+ "version": "1.5.237",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
+ "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==",
"dev": true,
"license": "ISC"
},
@@ -5820,9 +5820,9 @@
}
},
"node_modules/envinfo": {
- "version": "7.18.0",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.18.0.tgz",
- "integrity": "sha512-02QGCLRW+Jb8PC270ic02lat+N57iBaWsvHjcJViqp6UVupRB+Vsg7brYPTqEFXvsdTql3KnSczv5ModZFpl8Q==",
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.19.0.tgz",
+ "integrity": "sha512-DoSM9VyG6O3vqBf+p3Gjgr/Q52HYBBtO3v+4koAxt1MnWr+zEnxE+nke/yXS4lt2P4SYCHQ4V3f1i88LQVOpAw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -5899,9 +5899,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.10",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
- "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+ "version": "0.25.11",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
+ "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -5912,32 +5912,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.10",
- "@esbuild/android-arm": "0.25.10",
- "@esbuild/android-arm64": "0.25.10",
- "@esbuild/android-x64": "0.25.10",
- "@esbuild/darwin-arm64": "0.25.10",
- "@esbuild/darwin-x64": "0.25.10",
- "@esbuild/freebsd-arm64": "0.25.10",
- "@esbuild/freebsd-x64": "0.25.10",
- "@esbuild/linux-arm": "0.25.10",
- "@esbuild/linux-arm64": "0.25.10",
- "@esbuild/linux-ia32": "0.25.10",
- "@esbuild/linux-loong64": "0.25.10",
- "@esbuild/linux-mips64el": "0.25.10",
- "@esbuild/linux-ppc64": "0.25.10",
- "@esbuild/linux-riscv64": "0.25.10",
- "@esbuild/linux-s390x": "0.25.10",
- "@esbuild/linux-x64": "0.25.10",
- "@esbuild/netbsd-arm64": "0.25.10",
- "@esbuild/netbsd-x64": "0.25.10",
- "@esbuild/openbsd-arm64": "0.25.10",
- "@esbuild/openbsd-x64": "0.25.10",
- "@esbuild/openharmony-arm64": "0.25.10",
- "@esbuild/sunos-x64": "0.25.10",
- "@esbuild/win32-arm64": "0.25.10",
- "@esbuild/win32-ia32": "0.25.10",
- "@esbuild/win32-x64": "0.25.10"
+ "@esbuild/aix-ppc64": "0.25.11",
+ "@esbuild/android-arm": "0.25.11",
+ "@esbuild/android-arm64": "0.25.11",
+ "@esbuild/android-x64": "0.25.11",
+ "@esbuild/darwin-arm64": "0.25.11",
+ "@esbuild/darwin-x64": "0.25.11",
+ "@esbuild/freebsd-arm64": "0.25.11",
+ "@esbuild/freebsd-x64": "0.25.11",
+ "@esbuild/linux-arm": "0.25.11",
+ "@esbuild/linux-arm64": "0.25.11",
+ "@esbuild/linux-ia32": "0.25.11",
+ "@esbuild/linux-loong64": "0.25.11",
+ "@esbuild/linux-mips64el": "0.25.11",
+ "@esbuild/linux-ppc64": "0.25.11",
+ "@esbuild/linux-riscv64": "0.25.11",
+ "@esbuild/linux-s390x": "0.25.11",
+ "@esbuild/linux-x64": "0.25.11",
+ "@esbuild/netbsd-arm64": "0.25.11",
+ "@esbuild/netbsd-x64": "0.25.11",
+ "@esbuild/openbsd-arm64": "0.25.11",
+ "@esbuild/openbsd-x64": "0.25.11",
+ "@esbuild/openharmony-arm64": "0.25.11",
+ "@esbuild/sunos-x64": "0.25.11",
+ "@esbuild/win32-arm64": "0.25.11",
+ "@esbuild/win32-ia32": "0.25.11",
+ "@esbuild/win32-x64": "0.25.11"
}
},
"node_modules/escalade": {
@@ -7266,9 +7266,9 @@
}
},
"node_modules/immutable": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
- "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
+ "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"dev": true,
"license": "MIT"
},
@@ -8468,9 +8468,9 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.23",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
- "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
+ "version": "2.0.25",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz",
+ "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==",
"dev": true,
"license": "MIT"
},
@@ -10110,9 +10110,9 @@
}
},
"node_modules/rollup": {
- "version": "4.52.4",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
- "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
+ "version": "4.52.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
+ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10126,28 +10126,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.52.4",
- "@rollup/rollup-android-arm64": "4.52.4",
- "@rollup/rollup-darwin-arm64": "4.52.4",
- "@rollup/rollup-darwin-x64": "4.52.4",
- "@rollup/rollup-freebsd-arm64": "4.52.4",
- "@rollup/rollup-freebsd-x64": "4.52.4",
- "@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
- "@rollup/rollup-linux-arm-musleabihf": "4.52.4",
- "@rollup/rollup-linux-arm64-gnu": "4.52.4",
- "@rollup/rollup-linux-arm64-musl": "4.52.4",
- "@rollup/rollup-linux-loong64-gnu": "4.52.4",
- "@rollup/rollup-linux-ppc64-gnu": "4.52.4",
- "@rollup/rollup-linux-riscv64-gnu": "4.52.4",
- "@rollup/rollup-linux-riscv64-musl": "4.52.4",
- "@rollup/rollup-linux-s390x-gnu": "4.52.4",
- "@rollup/rollup-linux-x64-gnu": "4.52.4",
- "@rollup/rollup-linux-x64-musl": "4.52.4",
- "@rollup/rollup-openharmony-arm64": "4.52.4",
- "@rollup/rollup-win32-arm64-msvc": "4.52.4",
- "@rollup/rollup-win32-ia32-msvc": "4.52.4",
- "@rollup/rollup-win32-x64-gnu": "4.52.4",
- "@rollup/rollup-win32-x64-msvc": "4.52.4",
+ "@rollup/rollup-android-arm-eabi": "4.52.5",
+ "@rollup/rollup-android-arm64": "4.52.5",
+ "@rollup/rollup-darwin-arm64": "4.52.5",
+ "@rollup/rollup-darwin-x64": "4.52.5",
+ "@rollup/rollup-freebsd-arm64": "4.52.5",
+ "@rollup/rollup-freebsd-x64": "4.52.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.5",
+ "@rollup/rollup-linux-arm64-musl": "4.52.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-gnu": "4.52.5",
+ "@rollup/rollup-linux-x64-musl": "4.52.5",
+ "@rollup/rollup-openharmony-arm64": "4.52.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.5",
+ "@rollup/rollup-win32-x64-gnu": "4.52.5",
+ "@rollup/rollup-win32-x64-msvc": "4.52.5",
"fsevents": "~2.3.2"
}
},
@@ -10788,9 +10788,9 @@
}
},
"node_modules/std-env": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
- "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"dev": true,
"license": "MIT"
},
@@ -11517,9 +11517,9 @@
}
},
"node_modules/vite": {
- "version": "7.1.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
- "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
+ "version": "7.1.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
+ "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
"dev": true,
"license": "MIT",
"dependencies": {
From a3ff26e3e45539c8632d4e41adb7e7dd76ab0d89 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Thu, 23 Oct 2025 16:54:13 +0200
Subject: [PATCH 48/82] Fix
https://github.com/firefly-iii/firefly-iii/issues/11096
---
app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php | 1 -
app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php | 3 +++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php b/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php
index 038c24a46c..501d680599 100644
--- a/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php
+++ b/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php
@@ -172,7 +172,6 @@ class ShowController extends Controller
/** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class);
- $transformer->setParameters($this->parameters);
$resource = new Item($budgetLimit, $transformer, 'budget_limits');
diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
index e1a6be6a94..8306274b4d 100644
--- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
@@ -154,6 +154,9 @@ class BudgetLimitEnrichment implements EnrichmentInterface
$this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth();
$this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth();
+ // #11096 make sure that the max end date is also at the end of the day,
+ $this->end->endOfDay();
+
/** @var BudgetLimit $limit */
foreach ($this->collection as $limit) {
$id = (int)$limit->id;
From 00ba2a46d2bbbf19bd71c3cf0e739a14693d4ef8 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 25 Oct 2025 09:20:21 +0200
Subject: [PATCH 49/82] Fix #11063
---
app/Support/Http/Controllers/PeriodOverview.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php
index caa114007b..6fec7eadda 100644
--- a/app/Support/Http/Controllers/PeriodOverview.php
+++ b/app/Support/Http/Controllers/PeriodOverview.php
@@ -634,10 +634,10 @@ trait PeriodOverview
$currencySymbol = $journal['currency_symbol'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$foreignCurrencyId = $journal['foreign_currency_id'];
- $amount = $journal['amount'] ?? '0';
+ $amount = (string) ($journal['amount'] ?? '0');
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) {
- $amount = $journal['pc_amount'] ?? '0';
+ $amount = (string) ($journal['pc_amount'] ?? '0');
$currencyId = $this->primaryCurrency->id;
$currencyCode = $this->primaryCurrency->code;
$currencyName = $this->primaryCurrency->name;
@@ -650,7 +650,7 @@ trait PeriodOverview
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
- $amount = $journal['foreign_amount'] ?? '0';
+ $amount = (string) ($journal['foreign_amount'] ?? '0');
}
$return[$currencyId] ??= [
'amount' => '0',
From 535e5e4f50497f8c3c8201274f44c3b9f12f8a9f Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 25 Oct 2025 11:08:07 +0200
Subject: [PATCH 50/82] Catch null pointer.
---
app/Handlers/Events/StoredGroupEventHandler.php | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php
index b960ff88df..960c6b30d7 100644
--- a/app/Handlers/Events/StoredGroupEventHandler.php
+++ b/app/Handlers/Events/StoredGroupEventHandler.php
@@ -27,6 +27,7 @@ use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
+use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
@@ -105,10 +106,17 @@ class StoredGroupEventHandler
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
+ /** @var Transaction|null $source */
$source = $journal->transactions()->where('amount', '<', '0')->first();
+ /** @var Transaction|null $dest */
$dest = $journal->transactions()->where('amount', '>', '0')->first();
- $repository->deleteStatisticsForModel($source->account, $journal->date);
- $repository->deleteStatisticsForModel($dest->account, $journal->date);
+
+ if(null !== $source) {
+ $repository->deleteStatisticsForModel($source->account, $journal->date);
+ }
+ if(null !== $dest) {
+ $repository->deleteStatisticsForModel($dest->account, $journal->date);
+ }
$categories = $journal->categories;
$tags = $journal->tags;
$budgets = $journal->budgets;
From 394df469615aa0e20bc09100f575d468c1dcbee3 Mon Sep 17 00:00:00 2001
From: JC5
Date: Sat, 25 Oct 2025 11:12:26 +0200
Subject: [PATCH 51/82] =?UTF-8?q?=F0=9F=A4=96=20Auto=20commit=20for=20rele?=
=?UTF-8?q?ase=20'develop'=20on=202025-10-25?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.ci/php-cs-fixer/composer.lock | 12 +-
.../Events/StoredGroupEventHandler.php | 9 +-
composer.lock | 92 +++---
config/firefly.php | 4 +-
package-lock.json | 284 +++++++++---------
5 files changed, 201 insertions(+), 200 deletions(-)
diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock
index 71a3131cac..2a5ff6a870 100644
--- a/.ci/php-cs-fixer/composer.lock
+++ b/.ci/php-cs-fixer/composer.lock
@@ -402,16 +402,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.89.0",
+ "version": "v3.89.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "4dd6768cb7558440d27d18f54909eee417317ce9"
+ "reference": "f34967da2866ace090a2b447de1f357356474573"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4dd6768cb7558440d27d18f54909eee417317ce9",
- "reference": "4dd6768cb7558440d27d18f54909eee417317ce9",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/f34967da2866ace090a2b447de1f357356474573",
+ "reference": "f34967da2866ace090a2b447de1f357356474573",
"shasum": ""
},
"require": {
@@ -493,7 +493,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.0"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.1"
},
"funding": [
{
@@ -501,7 +501,7 @@
"type": "github"
}
],
- "time": "2025-10-18T19:30:16+00:00"
+ "time": "2025-10-24T12:05:10+00:00"
},
{
"name": "psr/container",
diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php
index 960c6b30d7..fd33908b32 100644
--- a/app/Handlers/Events/StoredGroupEventHandler.php
+++ b/app/Handlers/Events/StoredGroupEventHandler.php
@@ -106,15 +106,16 @@ class StoredGroupEventHandler
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
- /** @var Transaction|null $source */
+ /** @var null|Transaction $source */
$source = $journal->transactions()->where('amount', '<', '0')->first();
- /** @var Transaction|null $dest */
+
+ /** @var null|Transaction $dest */
$dest = $journal->transactions()->where('amount', '>', '0')->first();
- if(null !== $source) {
+ if (null !== $source) {
$repository->deleteStatisticsForModel($source->account, $journal->date);
}
- if(null !== $dest) {
+ if (null !== $dest) {
$repository->deleteStatisticsForModel($dest->account, $journal->date);
}
$categories = $journal->categories;
diff --git a/composer.lock b/composer.lock
index 5782a664d6..0585ea3f7a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
- "version": "v12.34.0",
+ "version": "v12.35.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687"
+ "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687",
- "reference": "f9ec5a5d88bc8c468f17b59f88e05c8ac3c8d687",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/d6d6e3cb68238e2fb25b440f222442adef5a8a15",
+ "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15",
"shasum": ""
},
"require": {
@@ -2093,7 +2093,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2025-10-14T13:58:31+00:00"
+ "time": "2025-10-23T15:25:03+00:00"
},
{
"name": "laravel/passport",
@@ -2811,16 +2811,16 @@
},
{
"name": "league/csv",
- "version": "9.27.0",
+ "version": "9.27.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
- "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845"
+ "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/csv/zipball/cb491b1ba3c42ff2bcd0113814f4256b42bae845",
- "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845",
+ "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797",
+ "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797",
"shasum": ""
},
"require": {
@@ -2898,7 +2898,7 @@
"type": "github"
}
],
- "time": "2025-10-16T08:22:09+00:00"
+ "time": "2025-10-25T08:35:20+00:00"
},
{
"name": "league/event",
@@ -2956,16 +2956,16 @@
},
{
"name": "league/flysystem",
- "version": "3.30.0",
+ "version": "3.30.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "2203e3151755d874bb2943649dae1eb8533ac93e"
+ "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e",
- "reference": "2203e3151755d874bb2943649dae1eb8533ac93e",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c139fd65c1f796b926f4aec0df37f6caa959a8da",
+ "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da",
"shasum": ""
},
"require": {
@@ -3033,9 +3033,9 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
- "source": "https://github.com/thephpleague/flysystem/tree/3.30.0"
+ "source": "https://github.com/thephpleague/flysystem/tree/3.30.1"
},
- "time": "2025-06-25T13:29:59+00:00"
+ "time": "2025-10-20T15:35:26+00:00"
},
{
"name": "league/flysystem-local",
@@ -10052,28 +10052,28 @@
},
{
"name": "webmozart/assert",
- "version": "1.11.0",
+ "version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
+ "reference": "541057574806f942c94662b817a50f63f7345360"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
- "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/541057574806f942c94662b817a50f63f7345360",
+ "reference": "541057574806f942c94662b817a50f63f7345360",
"shasum": ""
},
"require": {
"ext-ctype": "*",
+ "ext-date": "*",
+ "ext-filter": "*",
"php": "^7.2 || ^8.0"
},
- "conflict": {
- "phpstan/phpstan": "<0.12.20",
- "vimeo/psalm": "<4.6.1 || 4.6.2"
- },
- "require-dev": {
- "phpunit/phpunit": "^8.5.13"
+ "suggest": {
+ "ext-intl": "",
+ "ext-simplexml": "",
+ "ext-spl": ""
},
"type": "library",
"extra": {
@@ -10104,9 +10104,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
- "source": "https://github.com/webmozarts/assert/tree/1.11.0"
+ "source": "https://github.com/webmozarts/assert/tree/1.12.0"
},
- "time": "2022-06-03T18:03:27+00:00"
+ "time": "2025-10-20T12:43:39+00:00"
}
],
"packages-dev": [
@@ -10549,16 +10549,16 @@
},
{
"name": "driftingly/rector-laravel",
- "version": "2.1.0",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/driftingly/rector-laravel.git",
- "reference": "efb636a08dfddfa2a3f4527b1dd970a898a075a4"
+ "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/efb636a08dfddfa2a3f4527b1dd970a898a075a4",
- "reference": "efb636a08dfddfa2a3f4527b1dd970a898a075a4",
+ "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/abc336cbf06f53d90ab74cecfd319379fc55d408",
+ "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408",
"shasum": ""
},
"require": {
@@ -10578,9 +10578,9 @@
"description": "Rector upgrades rules for Laravel Framework",
"support": {
"issues": "https://github.com/driftingly/rector-laravel/issues",
- "source": "https://github.com/driftingly/rector-laravel/tree/2.1.0"
+ "source": "https://github.com/driftingly/rector-laravel/tree/2.1.1"
},
- "time": "2025-10-12T21:51:39+00:00"
+ "time": "2025-10-23T13:53:44+00:00"
},
{
"name": "fakerphp/faker",
@@ -11036,16 +11036,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.6.1",
+ "version": "v5.6.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
- "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
+ "reference": "3a454ca033b9e06b63282ce19562e892747449bb",
"shasum": ""
},
"require": {
@@ -11088,9 +11088,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
},
- "time": "2025-08-13T20:13:15+00:00"
+ "time": "2025-10-21T19:32:17+00:00"
},
{
"name": "phar-io/manifest",
@@ -11920,16 +11920,16 @@
},
{
"name": "rector/rector",
- "version": "2.2.3",
+ "version": "2.2.5",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f"
+ "reference": "fb9418af7777dfb1c87a536dc58398b5b07c74b9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f",
- "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/fb9418af7777dfb1c87a536dc58398b5b07c74b9",
+ "reference": "fb9418af7777dfb1c87a536dc58398b5b07c74b9",
"shasum": ""
},
"require": {
@@ -11968,7 +11968,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.2.3"
+ "source": "https://github.com/rectorphp/rector/tree/2.2.5"
},
"funding": [
{
@@ -11976,7 +11976,7 @@
"type": "github"
}
],
- "time": "2025-10-11T21:50:23+00:00"
+ "time": "2025-10-23T11:22:37+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/config/firefly.php b/config/firefly.php
index 94dbbd6545..b5e0be9d48 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
- 'version' => 'develop/2025-10-20',
- 'build_time' => 1760930795,
+ 'version' => 'develop/2025-10-25',
+ 'build_time' => 1761383422,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
diff --git a/package-lock.json b/package-lock.json
index f71ec96e8f..9572091c7f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,9 +29,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
- "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -39,21 +39,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
- "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
+ "@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
- "@babel/parser": "^7.28.4",
+ "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.4",
- "@babel/types": "^7.28.4",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -80,14 +80,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
- "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@babel/types": "^7.28.2",
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -137,18 +137,18 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
- "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/traverse": "^7.28.3",
+ "@babel/traverse": "^7.28.5",
"semver": "^6.3.1"
},
"engines": {
@@ -169,14 +169,14 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
- "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "regexpu-core": "^6.2.0",
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "regexpu-core": "^6.3.1",
"semver": "^6.3.1"
},
"engines": {
@@ -224,14 +224,14 @@
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
- "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
+ "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -353,9 +353,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -402,13 +402,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
- "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.4"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -418,14 +418,14 @@
}
},
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
- "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
+ "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
- "@babel/traverse": "^7.27.1"
+ "@babel/traverse": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -679,9 +679,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz",
- "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
+ "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -767,14 +767,14 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
- "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
+ "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
- "@babel/traverse": "^7.28.0"
+ "@babel/traverse": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -867,9 +867,9 @@
}
},
"node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
- "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
+ "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -966,9 +966,9 @@
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
- "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
+ "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1032,16 +1032,16 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
- "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz",
+ "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-module-transforms": "^7.28.3",
"@babel/helper-plugin-utils": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1186,9 +1186,9 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
- "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
+ "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1319,9 +1319,9 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz",
- "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz",
+ "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1498,17 +1498,17 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
- "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz",
+ "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.28.0",
+ "@babel/compat-data": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
- "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
@@ -1521,42 +1521,42 @@
"@babel/plugin-transform-async-generator-functions": "^7.28.0",
"@babel/plugin-transform-async-to-generator": "^7.27.1",
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
- "@babel/plugin-transform-block-scoping": "^7.28.0",
+ "@babel/plugin-transform-block-scoping": "^7.28.5",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.28.3",
- "@babel/plugin-transform-classes": "^7.28.3",
+ "@babel/plugin-transform-classes": "^7.28.4",
"@babel/plugin-transform-computed-properties": "^7.27.1",
- "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-dotall-regex": "^7.27.1",
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-dynamic-import": "^7.27.1",
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
- "@babel/plugin-transform-exponentiation-operator": "^7.27.1",
+ "@babel/plugin-transform-exponentiation-operator": "^7.28.5",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-for-of": "^7.27.1",
"@babel/plugin-transform-function-name": "^7.27.1",
"@babel/plugin-transform-json-strings": "^7.27.1",
"@babel/plugin-transform-literals": "^7.27.1",
- "@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
"@babel/plugin-transform-modules-amd": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
- "@babel/plugin-transform-modules-systemjs": "^7.27.1",
+ "@babel/plugin-transform-modules-systemjs": "^7.28.5",
"@babel/plugin-transform-modules-umd": "^7.27.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-new-target": "^7.27.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-numeric-separator": "^7.27.1",
- "@babel/plugin-transform-object-rest-spread": "^7.28.0",
+ "@babel/plugin-transform-object-rest-spread": "^7.28.4",
"@babel/plugin-transform-object-super": "^7.27.1",
"@babel/plugin-transform-optional-catch-binding": "^7.27.1",
- "@babel/plugin-transform-optional-chaining": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.28.5",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-property-literals": "^7.27.1",
- "@babel/plugin-transform-regenerator": "^7.28.3",
+ "@babel/plugin-transform-regenerator": "^7.28.4",
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
@@ -1632,18 +1632,18 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
- "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.3",
+ "@babel/generator": "^7.28.5",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.4",
+ "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4",
+ "@babel/types": "^7.28.5",
"debug": "^4.3.1"
},
"engines": {
@@ -1651,14 +1651,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
- "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -3034,9 +3034,9 @@
"license": "MIT"
},
"node_modules/@types/express": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
- "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
+ "version": "4.17.24",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.24.tgz",
+ "integrity": "sha512-Mbrt4SRlXSTWryOnHAh2d4UQ/E7n9lZyGSi6KgX+4hkuL9soYbLOVXVhnk/ODp12YsGc95f4pOvqywJ6kngUwg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3091,9 +3091,9 @@
"license": "MIT"
},
"node_modules/@types/http-proxy": {
- "version": "1.17.16",
- "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
- "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==",
+ "version": "1.17.17",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz",
+ "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3173,13 +3173,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.8.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz",
- "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
+ "version": "24.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
+ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.14.0"
+ "undici-types": "~7.16.0"
}
},
"node_modules/@types/node-forge": {
@@ -3221,9 +3221,9 @@
"license": "MIT"
},
"node_modules/@types/send": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz",
- "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3241,9 +3241,9 @@
}
},
"node_modules/@types/serve-static": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz",
- "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==",
+ "version": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3253,9 +3253,9 @@
}
},
"node_modules/@types/serve-static/node_modules/@types/send": {
- "version": "0.17.5",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
- "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4075,9 +4075,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.18",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz",
- "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==",
+ "version": "2.8.20",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz",
+ "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4360,9 +4360,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.26.3",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
- "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "version": "4.27.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
+ "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
"dev": true,
"funding": [
{
@@ -4380,11 +4380,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.8.9",
- "caniuse-lite": "^1.0.30001746",
- "electron-to-chromium": "^1.5.227",
- "node-releases": "^2.0.21",
- "update-browserslist-db": "^1.1.3"
+ "baseline-browser-mapping": "^2.8.19",
+ "caniuse-lite": "^1.0.30001751",
+ "electron-to-chromium": "^1.5.238",
+ "node-releases": "^2.0.26",
+ "update-browserslist-db": "^1.1.4"
},
"bin": {
"browserslist": "cli.js"
@@ -5736,9 +5736,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.237",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz",
- "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==",
+ "version": "1.5.240",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz",
+ "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==",
"dev": true,
"license": "ISC"
},
@@ -7998,9 +7998,9 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.19",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
- "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8468,9 +8468,9 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.25",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.25.tgz",
- "integrity": "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==",
+ "version": "2.0.26",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
+ "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==",
"dev": true,
"license": "MIT"
},
@@ -9988,13 +9988,13 @@
"license": "MIT"
},
"node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-core-module": "^2.16.0",
+ "is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -11320,9 +11320,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.14.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
- "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -11391,9 +11391,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
"dev": true,
"funding": [
{
@@ -11517,9 +11517,9 @@
}
},
"node_modules/vite": {
- "version": "7.1.10",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz",
- "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==",
+ "version": "7.1.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
+ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"dev": true,
"license": "MIT",
"dependencies": {
From 0bdb4f2e6968eee681431a5e70a94121f9ed3cf3 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 25 Oct 2025 11:50:34 +0200
Subject: [PATCH 52/82] Fix
https://github.com/orgs/firefly-iii/discussions/11104
---
..._09_25_175248_create_period_statistics.php | 46 ++++++++++---------
1 file changed, 24 insertions(+), 22 deletions(-)
diff --git a/database/migrations/2025_09_25_175248_create_period_statistics.php b/database/migrations/2025_09_25_175248_create_period_statistics.php
index 0cda62b0ef..1e118fc58d 100644
--- a/database/migrations/2025_09_25_175248_create_period_statistics.php
+++ b/database/migrations/2025_09_25_175248_create_period_statistics.php
@@ -11,34 +11,36 @@ return new class extends Migration
*/
public function up(): void
{
- Schema::create('period_statistics', function (Blueprint $table) {
- $table->id();
- $table->timestamps();
+ if (!Schema::hasTable('period_statistics')) {
+ Schema::create('period_statistics', function (Blueprint $table) {
+ $table->id();
+ $table->timestamps();
- // reference to user group id.
- $table->bigInteger('user_group_id', false, true);
+ // reference to user group id.
+ $table->bigInteger('user_group_id', false, true);
- $table->integer('primary_statable_id', false, true)->nullable();
- $table->string('primary_statable_type', 255)->nullable();
+ $table->integer('primary_statable_id', false, true)->nullable();
+ $table->string('primary_statable_type', 255)->nullable();
- $table->integer('secondary_statable_id', false, true)->nullable();
- $table->string('secondary_statable_type', 255)->nullable();
+ $table->integer('secondary_statable_id', false, true)->nullable();
+ $table->string('secondary_statable_type', 255)->nullable();
- $table->integer('tertiary_statable_id', false, true)->nullable();
- $table->string('tertiary_statable_type', 255)->nullable();
+ $table->integer('tertiary_statable_id', false, true)->nullable();
+ $table->string('tertiary_statable_type', 255)->nullable();
- $table->integer('transaction_currency_id', false, true);
- $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
+ $table->integer('transaction_currency_id', false, true);
+ $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
- $table->dateTime('start')->nullable();
- $table->string('start_tz', 50)->nullable();
- $table->dateTime('end')->nullable();
- $table->string('end_tz', 50)->nullable();
- $table->string('type',255);
- $table->integer('count', false, true)->default(0);
- $table->decimal('amount', 32, 12);
- $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade');
- });
+ $table->dateTime('start')->nullable();
+ $table->string('start_tz', 50)->nullable();
+ $table->dateTime('end')->nullable();
+ $table->string('end_tz', 50)->nullable();
+ $table->string('type', 255);
+ $table->integer('count', false, true)->default(0);
+ $table->decimal('amount', 32, 12);
+ $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade');
+ });
+ }
}
/**
From f5806ea6deb13886e811110023be99f9561e349e Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 25 Oct 2025 14:09:44 +0200
Subject: [PATCH 53/82] Fix #11102
---
app/Http/Controllers/Controller.php | 1 +
app/Http/Controllers/JavascriptController.php | 2 +
app/Support/Amount.php | 1 +
app/Support/Steam.php | 13 ++++
config/firefly.php | 63 ++++++++++---------
public/v1/js/ff/charts.defaults.js | 38 +++++++----
public/v1/js/ff/firefly.js | 1 -
public/v1/js/ff/help.js | 31 ++++++++-
resources/lang/en_US/firefly.php | 2 +
resources/views/budgets/index.twig | 10 +++
resources/views/javascript/variables.twig | 4 ++
resources/views/layout/default.twig | 11 +++-
12 files changed, 131 insertions(+), 46 deletions(-)
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 534acf8488..1f39963a53 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -131,6 +131,7 @@ abstract class Controller extends BaseController
$this->primaryCurrency = null;
// get shown-intro-preference:
if (auth()->check()) {
+ View::share('anonymous', Steam::anonymous());
$this->primaryCurrency = Amount::getPrimaryCurrency();
$language = Steam::getLanguage();
$locale = Steam::getLocale();
diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php
index c5beecfeb4..217a400006 100644
--- a/app/Http/Controllers/JavascriptController.php
+++ b/app/Http/Controllers/JavascriptController.php
@@ -30,6 +30,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
+use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@@ -114,6 +115,7 @@ class JavascriptController extends Controller
'currencyCode' => $currency->code,
'currencySymbol' => $currency->symbol,
'accountingLocaleInfo' => $accounting,
+ 'anonymous' => var_export(Steam::anonymous(), true),
'language' => $lang,
'dateRangeTitle' => $dateRange['title'],
'locale' => $locale,
diff --git a/app/Support/Amount.php b/app/Support/Amount.php
index 98e8aa284c..22e44a1316 100644
--- a/app/Support/Amount.php
+++ b/app/Support/Amount.php
@@ -163,6 +163,7 @@ class Amount
*/
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
{
+ $amount = Steam::anonymous() ? '0' : $amount;
$locale = Steam::getLocale();
$rounded = Steam::bcround($amount, $decimalPlaces);
$coloured ??= true;
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 586ecd4a7b..2dfc48ee3f 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -627,6 +627,19 @@ class Steam
return $locale;
}
+ public function anonymous(): bool // get preference
+ {
+ $singleton = PreferencesSingleton::getInstance();
+ $cached = $singleton->getPreference('anonymous');
+ if (null !== $cached) {
+ return $cached;
+ }
+ $anonymous = app('preferences')->get('anonymous', config('firefly.default_preferences.anonymous', false))->data;
+ $singleton->setPreference('anonymous', $anonymous);
+
+ return $anonymous;
+ }
+
public function getLocaleArray(string $locale): array
{
return [
diff --git a/config/firefly.php b/config/firefly.php
index b5e0be9d48..4801282895 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -182,6 +182,7 @@ return [
'darkMode' => 'browser',
'list_length' => 10, // to be removed if v1 is cancelled.
'default_preferences' => [
+ 'anonymous' => false,
'frontpageAccounts' => [],
'listPageSize' => 50,
'currencyPreference' => 'EUR',
@@ -409,7 +410,7 @@ return [
],
- 'rule-actions' => [
+ 'rule-actions' => [
'set_category' => SetCategory::class,
'clear_category' => ClearCategory::class,
'set_budget' => SetBudget::class,
@@ -443,7 +444,7 @@ return [
// 'set_foreign_amount' => SetForeignAmount::class,
// 'set_foreign_currency' => SetForeignCurrency::class,
],
- 'context-rule-actions' => [
+ 'context-rule-actions' => [
'set_category',
'set_budget',
'add_tag',
@@ -462,13 +463,13 @@ return [
'convert_transfer',
],
- 'test-triggers' => [
+ 'test-triggers' => [
'limit' => 10,
'range' => 200,
],
// expected source types for each transaction type, in order of preference.
- 'expected_source_types' => [
+ 'expected_source_types' => [
'source' => [
TransactionTypeEnum::WITHDRAWAL->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::CASH->value],
@@ -513,7 +514,7 @@ return [
TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
],
],
- 'allowed_opposing_types' => [
+ 'allowed_opposing_types' => [
'source' => [
AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value,
@@ -603,7 +604,7 @@ return [
],
],
// depending on the account type, return the allowed transaction types:
- 'allowed_transaction_types' => [
+ 'allowed_transaction_types' => [
'source' => [
AccountTypeEnum::ASSET->value => [
TransactionTypeEnum::WITHDRAWAL->value,
@@ -672,7 +673,7 @@ return [
],
// having the source + dest will tell you the transaction type.
- 'account_to_transaction' => [
+ 'account_to_transaction' => [
AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value,
AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value,
@@ -737,7 +738,7 @@ return [
],
// allowed source -> destination accounts.
- 'source_dests' => [
+ 'source_dests' => [
TransactionTypeEnum::WITHDRAWAL->value => [
AccountTypeEnum::ASSET->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CASH->value],
AccountTypeEnum::LOAN->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value],
@@ -776,7 +777,7 @@ return [
],
],
// if you add fields to this array, don't forget to update the export routine (ExportDataGenerator).
- 'journal_meta_fields' => [
+ 'journal_meta_fields' => [
// sepa
'sepa_cc',
'sepa_ct_op',
@@ -810,47 +811,47 @@ return [
'recurrence_count',
'recurrence_date',
],
- 'webhooks' => [
+ 'webhooks' => [
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
],
- 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value],
- 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
- 'dynamic_creation_allowed' => [
+ 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value],
+ 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
+ 'dynamic_creation_allowed' => [
AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::REVENUE->value,
AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::RECONCILIATION->value,
AccountTypeEnum::LIABILITY_CREDIT->value,
],
- 'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
- 'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
- 'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
+ 'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
+ 'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
+ 'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
// dynamic date ranges are as follows:
- 'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'],
+ 'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'],
- 'allowed_sort_parameters' => [
+ 'allowed_sort_parameters' => [
'Account' => ['id', 'order', 'name', 'iban', 'active', 'account_type_id',
- 'current_balance',
- 'pc_current_balance',
- 'opening_balance',
- 'pc_opening_balance',
- 'virtual_balance',
- 'pc_virtual_balance',
- 'debt_amount',
- 'pc_debt_amount',
- 'balance_difference',
- 'pc_balance_difference',
+ 'current_balance',
+ 'pc_current_balance',
+ 'opening_balance',
+ 'pc_opening_balance',
+ 'virtual_balance',
+ 'pc_virtual_balance',
+ 'debt_amount',
+ 'pc_debt_amount',
+ 'balance_difference',
+ 'pc_balance_difference',
],
],
- 'allowed_db_sort_parameters' => [
+ 'allowed_db_sort_parameters' => [
'Account' => ['id', 'order', 'name', 'iban', 'active', 'account_type_id'],
],
// preselected account lists possibilities:
- 'preselected_accounts' => ['all', 'assets', 'liabilities'],
+ 'preselected_accounts' => ['all', 'assets', 'liabilities'],
// allowed to store a piggy bank in:
- 'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
+ 'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
];
diff --git a/public/v1/js/ff/charts.defaults.js b/public/v1/js/ff/charts.defaults.js
index 4bb308ee26..4bd57b5445 100644
--- a/public/v1/js/ff/charts.defaults.js
+++ b/public/v1/js/ff/charts.defaults.js
@@ -42,13 +42,11 @@ function formatLabel(str, maxwidth) {
if (concat.length > maxwidth) {
sections.push(temp);
temp = "";
- }
- else {
+ } else {
if (index === (words.length - 1)) {
sections.push(concat);
return;
- }
- else {
+ } else {
temp = concat;
return;
}
@@ -62,8 +60,7 @@ function formatLabel(str, maxwidth) {
if (item.length < maxwidth) {
temp = item;
- }
- else {
+ } else {
sections.push(item);
}
@@ -98,9 +95,11 @@ var defaultChartOptions = {
ticks: {
callback: function (tickValue) {
"use strict";
+ if (anonymous) {
+ return accounting.formatMoney(0);
+ }
// use first symbol or null:
return accounting.formatMoney(tickValue);
-
},
beginAtZero: true
}
@@ -112,8 +111,11 @@ var defaultChartOptions = {
callbacks: {
label: function (tooltipItem, data) {
"use strict";
- return data.datasets[tooltipItem.datasetIndex].label + ': ' +
- accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol);
+ var string = accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol);
+ if (anonymous) {
+ string = accounting.formatMoney(0);
+ }
+ return data.datasets[tooltipItem.datasetIndex].label + ': ' + string;
}
}
}
@@ -125,7 +127,11 @@ var pieOptionsWithCurrency = {
label: function (tooltipItem, data) {
"use strict";
var value = data.datasets[0].data[tooltipItem.index];
- return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]);
+ var string = accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]);
+ if (anonymous) {
+ string = accounting.formatMoney(0);
+ }
+ return data.labels[tooltipItem.index] + ': ' + string;
}
}
},
@@ -139,7 +145,11 @@ var defaultPieOptions = {
label: function (tooltipItem, data) {
"use strict";
var value = data.datasets[0].data[tooltipItem.index];
- return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value);
+ var string = accounting.formatMoney(value);
+ if (anonymous) {
+ string = accounting.formatMoney(0);
+ }
+ return data.labels[tooltipItem.index] + ': ' + string;
}
}
},
@@ -153,7 +163,11 @@ var neutralDefaultPieOptions = {
label: function (tooltipItem, data) {
"use strict";
var value = data.datasets[0].data[tooltipItem.index];
- return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, '¤');
+ var string = accounting.formatMoney(value, '¤');
+ if(anonymous) {
+ string = accounting.formatMoney(0);
+ }
+ return data.labels[tooltipItem.index] + ': ' + string;
}
}
},
diff --git a/public/v1/js/ff/firefly.js b/public/v1/js/ff/firefly.js
index 442fd820d1..846971fb5b 100644
--- a/public/v1/js/ff/firefly.js
+++ b/public/v1/js/ff/firefly.js
@@ -47,7 +47,6 @@ function parseToLocalDates() {
$(function () {
"use strict";
-
configAccounting(currencySymbol);
// on submit of logout button:
diff --git a/public/v1/js/ff/help.js b/public/v1/js/ff/help.js
index 36998bbfea..30c0f76c0f 100644
--- a/public/v1/js/ff/help.js
+++ b/public/v1/js/ff/help.js
@@ -17,13 +17,42 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-/** global: token, helpPageTitle */
+/** global: token, helpPageTitle, anonymous */
$(function () {
"use strict";
$('#help').click(showHelp);
+ $('#anonymous').click(changeAnonymity)
});
+function submitAnonymity(value) {
+ $.ajax({
+ url: 'api/v1/preferences/anonymous',
+ data: JSON.stringify({data: value}),
+ type: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
+ },
+ });
+}
+
+function changeAnonymity(e) {
+ if (anonymous) {
+ console.log('Will DISABLE.');
+ submitAnonymity(false);
+ alert(anonymous_warning_off_txt);
+ window.location.reload(true);
+ }
+ if (!anonymous) {
+ console.log('Will ENABLE.');
+ submitAnonymity(true);
+ alert(anonymous_warning_on_txt);
+ window.location.reload(true);
+ }
+ return false;
+}
+
function showHelp(e) {
"use strict";
var target = $(e.target);
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index f530ea76e0..d06840a2d2 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -235,6 +235,8 @@ return [
'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!',
'here_be_dragons' => 'Hic sunt dracones',
'bad_date_transaction' => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.',
+ 'anonymous_warning_on' => 'For your privacy, all amounts are now displayed as "zero". Warning: text input boxes may still show the original amounts!',
+ 'anonymous_warning_off' => 'Amounts will be visible again. Please be mindful of your surroundings.',
// Webhooks
'webhooks' => 'Webhooks',
diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig
index 73b8814968..4ee60e68a4 100644
--- a/resources/views/budgets/index.twig
+++ b/resources/views/budgets/index.twig
@@ -267,9 +267,14 @@
@@ -283,10 +288,15 @@
{% endif %}
{{ budgetLimit.currency_symbol }}
+ {% if not anonymous %}
+ {% endif %}
+ {% if anonymous %}
+ ---
+ {% endif %}
diff --git a/resources/views/javascript/variables.twig b/resources/views/javascript/variables.twig
index 37c6ab8c13..68714d7537 100644
--- a/resources/views/javascript/variables.twig
+++ b/resources/views/javascript/variables.twig
@@ -26,6 +26,7 @@ ranges: ranges
};
var uid = "{{ uid }}";
+var anonymous = {{ anonymous }};
var language = "{{ language|escape }}";
var locale = "{{ locale|escape }}";
var currencyCode = '{{ currencyCode|escape('js') }}';
@@ -51,6 +52,9 @@ var acc_config_new = {format: accountingConfig};
var helpPageTitle = "{{ trans('firefly.help_for_this_page')|escape('js') }}";
var helpPageBody = "{{ trans('firefly.help_for_this_page_body')|escape('js') }}";
+var anonymous_warning_on_txt = "{{ trans('firefly.anonymous_warning_on')|escape('js') }}";
+var anonymous_warning_off_txt= "{{ trans('firefly.anonymous_warning_off')|escape('js') }}";
+
var edit_selected_txt = "{{ trans('firefly.mass_edit')|escape('js') }}";
var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}";
var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}";
diff --git a/resources/views/layout/default.twig b/resources/views/layout/default.twig
index a20ed1b060..db253c4ec8 100644
--- a/resources/views/layout/default.twig
+++ b/resources/views/layout/default.twig
@@ -111,7 +111,16 @@
{% endblock %}
{% block scripts %}
-
+
{% endblock %}
{% block styles %}
-
+
{% endblock %}
diff --git a/resources/views/bills/create.twig b/resources/views/bills/create.twig
index cdab3ccef1..46342c9120 100644
--- a/resources/views/bills/create.twig
+++ b/resources/views/bills/create.twig
@@ -68,17 +68,17 @@
{% endblock %}
{% block styles %}
-
-
-
+
+
+
{% endblock %}
{% block scripts %}
-
-
-
-
+
+
+
+
{# auto complete for object groups #}
-
-
+
+
{% endblock %}
diff --git a/resources/views/bills/edit.twig b/resources/views/bills/edit.twig
index 4620647039..9e2c3e33a5 100644
--- a/resources/views/bills/edit.twig
+++ b/resources/views/bills/edit.twig
@@ -74,24 +74,24 @@
{% endblock %}
{% block styles %}
-
-
-
{% endblock %}
{% block scripts %}
-
-
-
-
+
+
{# auto complete for object groups #}
-
-
{% endblock %}
diff --git a/resources/views/bills/index.twig b/resources/views/bills/index.twig
index 49a2905d5d..abb3a8f454 100644
--- a/resources/views/bills/index.twig
+++ b/resources/views/bills/index.twig
@@ -41,6 +41,6 @@
{% endblock %}
{% block scripts %}
-
-
+
+
{% endblock %}
diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig
index ec55e97220..84b4c347bb 100644
--- a/resources/views/bills/show.twig
+++ b/resources/views/bills/show.twig
@@ -188,10 +188,10 @@
var billCurrencySymbol = "{{ convertToPrimary ? primaryCurrency.symbol : object.data.currency.symbol }}";
var billUrl = '{{ route('chart.bill.single', [object.data.id]) }}';
-
-
-
-
+
+
+
+
{# required for groups.twig #}
-
+
{% endblock %}
diff --git a/resources/views/budgets/create.twig b/resources/views/budgets/create.twig
index fd1e014b48..576eca9665 100644
--- a/resources/views/budgets/create.twig
+++ b/resources/views/budgets/create.twig
@@ -60,5 +60,5 @@
{% endblock %}
{% block scripts %}
-
+
{% endblock %}
diff --git a/resources/views/budgets/edit.twig b/resources/views/budgets/edit.twig
index ebe8d604b7..5f78e91ebc 100644
--- a/resources/views/budgets/edit.twig
+++ b/resources/views/budgets/edit.twig
@@ -61,5 +61,5 @@
{% endblock %}
{% block scripts %}
-
+
{% endblock %}
diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig
index 4ee60e68a4..014742cc74 100644
--- a/resources/views/budgets/index.twig
+++ b/resources/views/budgets/index.twig
@@ -465,11 +465,11 @@
{% endblock %}
{% block styles %}
-
+
{% endblock %}
{% block scripts %}
-
+
-
-
+
+
{% endblock %}
diff --git a/resources/views/budgets/no-budget.twig b/resources/views/budgets/no-budget.twig
index ac5fcf44cc..3b48d8ed79 100644
--- a/resources/views/budgets/no-budget.twig
+++ b/resources/views/budgets/no-budget.twig
@@ -60,5 +60,5 @@
{% endblock %}
{% block scripts %}
{# required for groups.twig #}
-
+
{% endblock %}
diff --git a/resources/views/budgets/show.twig b/resources/views/budgets/show.twig
index 8f3c53aefa..5b2915d575 100644
--- a/resources/views/budgets/show.twig
+++ b/resources/views/budgets/show.twig
@@ -230,10 +230,10 @@
{% endif %}
-
-
-
-
+
+
+
+
{# required for groups.twig #}
-
+
{% endblock %}
diff --git a/resources/views/categories/create.twig b/resources/views/categories/create.twig
index 0556ac39ba..1301ef0f45 100644
--- a/resources/views/categories/create.twig
+++ b/resources/views/categories/create.twig
@@ -53,5 +53,5 @@
{% endblock %}
{% block scripts %}
-
+
{% endblock %}
diff --git a/resources/views/categories/edit.twig b/resources/views/categories/edit.twig
index aa9b6263d6..8d9e7b863a 100644
--- a/resources/views/categories/edit.twig
+++ b/resources/views/categories/edit.twig
@@ -59,5 +59,5 @@
{% endblock %}
{% block scripts %}
-
+
{% endblock %}
diff --git a/resources/views/categories/index.twig b/resources/views/categories/index.twig
index cefb5c0df2..e4e19944ac 100644
--- a/resources/views/categories/index.twig
+++ b/resources/views/categories/index.twig
@@ -40,10 +40,10 @@
{% endblock %}
{% block styles %}
-
+
{% endblock %}
{% block scripts %}
-
-
+
+
{% endblock %}
diff --git a/resources/views/categories/no-category.twig b/resources/views/categories/no-category.twig
index 7dde51d679..e616ae72cb 100644
--- a/resources/views/categories/no-category.twig
+++ b/resources/views/categories/no-category.twig
@@ -60,5 +60,5 @@
{% endblock %}
{% block scripts %}
{# required for groups.twig #}
-
+
{% endblock %}
diff --git a/resources/views/categories/show.twig b/resources/views/categories/show.twig
index 5a8563356e..b0b0788130 100644
--- a/resources/views/categories/show.twig
+++ b/resources/views/categories/show.twig
@@ -113,10 +113,10 @@
var everything = '{{ route('chart.category.all', [category.id]) }}';
var specific = '{{ route('chart.category.specific', [category.id, start.format('Ymd')]) }}';
-
-
-
-
+
+
+
+
{# required for groups.twig #}
-
+
{% endblock %}
diff --git a/resources/views/currencies/index.twig b/resources/views/currencies/index.twig
index c05c82ea20..abe1ea20e5 100644
--- a/resources/views/currencies/index.twig
+++ b/resources/views/currencies/index.twig
@@ -98,5 +98,5 @@
var redirectUrl = "{{ route('currencies.index') }}";
var updateCurrencyUrl = "{{ route('api.v1.currencies.update', ['']) }}";
-
+
{% endblock %}
diff --git a/resources/views/index.twig b/resources/views/index.twig
index ec0655f809..58ad2587cb 100644
--- a/resources/views/index.twig
+++ b/resources/views/index.twig
@@ -211,14 +211,14 @@
{% endif %}
-
-
-
-
-
+
+
{% endblock %}
{% block styles %}
{% endblock %}
diff --git a/resources/views/layout/default.twig b/resources/views/layout/default.twig
index db253c4ec8..11112c6e64 100644
--- a/resources/views/layout/default.twig
+++ b/resources/views/layout/default.twig
@@ -29,14 +29,14 @@
{# CSS things #}
-
-
{# the theme #}
-
{% if 'browser' == darkMode %}
-
-
-
{% endif %}
{% if 'dark' == darkMode %}
-
-
{% endif %}
{% if 'light' == darkMode %}
-
-
{% endif %}
{# Firefly III customisations #}
-
+
{# Extra CSS for the demo: #}
{% if not shownDemo %}
-
{% endif %}
{# Any local custom CSS. #}
{% block styles %}{% endblock %}
{# this entry is in the header so it's loaded early #}
@@ -228,8 +228,8 @@
{% block definitions %}{% endblock %}
{# Moment JS #}
-
-
+
{# All kinds of variables. #}
@@ -238,32 +238,32 @@
type="text/javascript" nonce="{{ JS_NONCE }}">
{# Base script: jquery and bootstrap #}
-
+
{# Vue scripts and components. Only loaded when needed #}
{% if VUE_SCRIPT_NAME %}
-
-
+
{% endif %}
{# date range picker, current template, etc. #}
-
-
+
-
+
{# Firefly III code #}
-
-
+
+
{% if not shownDemo %}
-
-
+
+
{% endif %}
{% block scripts %}{% endblock %}
diff --git a/resources/views/layout/empty.twig b/resources/views/layout/empty.twig
index 444a336971..4b28679032 100644
--- a/resources/views/layout/empty.twig
+++ b/resources/views/layout/empty.twig
@@ -14,11 +14,11 @@
{# CSS things #}
{# libraries #}
-
-
+
+
{# the theme #}
-
+
{% if 'browser' == darkMode %}
-
-
+
+
{% endif %}
{% if 'dark' == darkMode %}
-
+
{% endif %}
{% if 'light' == darkMode %}
-
+
{% endif %}
{# Firefly III customisations #}
-
+
{# favicons #}
@@ -61,7 +61,7 @@
{% block content %}{% endblock %}
-
+
{% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %}
diff --git a/resources/views/layout/guest.twig b/resources/views/layout/guest.twig
index 889a241aa5..87717a72ba 100644
--- a/resources/views/layout/guest.twig
+++ b/resources/views/layout/guest.twig
@@ -24,11 +24,11 @@
{# CSS things #}
{# libraries #}
-
-
+
+
{# the theme #}
-
+
{% if 'browser' == darkMode %}
-
-
+
+
{% endif %}
{% if 'dark' == darkMode %}
-
+
{% endif %}
{% if 'light' == darkMode %}
-
+
{% endif %}
{# Firefly III customisations #}
-
+
{# favicons #}
@@ -75,8 +75,8 @@
Developed by James Cole, the source code is licensed under the
AGPL-3.0-or-later.
-
-
+
+
{% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %}
diff --git a/resources/views/layout/install.twig b/resources/views/layout/install.twig
index 29e7bc88a8..7693e2265d 100644
--- a/resources/views/layout/install.twig
+++ b/resources/views/layout/install.twig
@@ -35,8 +35,8 @@
Developed by James Cole, the source code is licensed under the AGPL-3.0-or-later.
-
-
+
+
{% block scripts %}{% endblock %}
{% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %}
diff --git a/resources/views/layout/v3/session.twig b/resources/views/layout/v3/session.twig
index 33d893678e..87f52d5937 100644
--- a/resources/views/layout/v3/session.twig
+++ b/resources/views/layout/v3/session.twig
@@ -7,10 +7,10 @@
{{ 'login_page_title'|_ }}
-
-
-
-
+
+
+
+
{# favicons #}
{% include 'partials.favicons' %}
@@ -19,11 +19,11 @@
{% block content %}{% endblock %}
-
+
-
+
-
+
{% block scripts %}
{% endblock %}