Compare commits

..

13 Commits

Author SHA1 Message Date
github-actions[bot]
667cbb1332 Merge pull request #10991 from firefly-iii/release-1759381485
🤖 Automatically merge the PR into the develop branch.
2025-10-02 07:04:53 +02:00
JC5
d1bae875f7 🤖 Auto commit for release 'develop' on 2025-10-02 2025-10-02 07:04:45 +02:00
James Cole
c5cf529413 Update changelog. 2025-10-02 06:59:56 +02:00
James Cole
62b9f2785f Ignore db version in scripts. 2025-10-02 06:55:09 +02:00
James Cole
3b85f87502 Stop using "db_version", check versions instead. 2025-10-02 06:53:23 +02:00
James Cole
87d3d14504 Fix #10990 2025-10-01 20:28:24 +02:00
github-actions[bot]
5637573fd0 Merge pull request #10982 from firefly-iii/release-1759116145
🤖 Automatically merge the PR into the develop branch.
2025-09-29 05:22:35 +02:00
JC5
48255ae6ee 🤖 Auto commit for release 'develop' on 2025-09-29 2025-09-29 05:22:25 +02:00
James Cole
ea02986170 Merge pull request #10979 from maksimkurb/patch-1
Add Kazakhstani Tenge (KZT) currency
2025-09-28 15:28:15 +02:00
mergify[bot]
f4fbc15ac6 Merge branch 'develop' into patch-1 2025-09-28 07:17:01 +00:00
Maxim Kurbatov
a02c4b42a4 Add Kazakhstani Tenge (KZT) currency
Signed-off-by: Maxim Kurbatov <maxim@kurb.me>
2025-09-28 11:49:37 +05:00
James Cole
1ff47441ce Also add no budget and no category overview. 2025-09-27 16:07:03 +02:00
James Cole
2cc8568077 Clean up code. 2025-09-27 06:40:56 +02:00
29 changed files with 719 additions and 947 deletions

View File

@@ -1252,16 +1252,16 @@
},
{
"name": "symfony/console",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"shasum": ""
},
"require": {
@@ -1326,7 +1326,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.3"
"source": "https://github.com/symfony/console/tree/v7.3.4"
},
"funding": [
{
@@ -1346,7 +1346,7 @@
"type": "tidelift"
}
],
"time": "2025-08-25T06:35:40+00:00"
"time": "2025-09-22T15:31:00+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2365,16 +2365,16 @@
},
{
"name": "symfony/process",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"shasum": ""
},
"require": {
@@ -2406,7 +2406,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.3.3"
"source": "https://github.com/symfony/process/tree/v7.3.4"
},
"funding": [
{
@@ -2426,7 +2426,7 @@
"type": "tidelift"
}
],
"time": "2025-08-18T09:42:54+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2575,16 +2575,16 @@
},
{
"name": "symfony/string",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
"reference": "f96476035142921000338bad71e5247fbc138872"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
"reference": "f96476035142921000338bad71e5247fbc138872",
"shasum": ""
},
"require": {
@@ -2599,7 +2599,6 @@
},
"require-dev": {
"symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
@@ -2642,7 +2641,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.3.3"
"source": "https://github.com/symfony/string/tree/v7.3.4"
},
"funding": [
{
@@ -2662,7 +2661,7 @@
"type": "tidelift"
}
],
"time": "2025-08-25T06:35:40+00:00"
"time": "2025-09-11T14:36:48+00:00"
}
],
"packages-dev": [],

View File

@@ -68,10 +68,6 @@ class UpdateController extends Controller
$data = $request->getAll();
$piggyBank = $this->repository->update($piggyBank, $data);
if (array_key_exists('current_amount', $data) && '' !== $data['current_amount']) {
$this->repository->setCurrentAmount($piggyBank, $data['current_amount']);
}
// enrich
/** @var User $admin */
$admin = auth()->user();

View File

@@ -64,7 +64,7 @@ class UpdateRequest extends FormRequest
*/
public function getAll(): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$this->integerFields = ['order', 'currency_id', 'foreign_currency_id', 'transaction_journal_id', 'source_id', 'destination_id', 'budget_id', 'category_id', 'bill_id', 'recurrence_id'];
$this->dateFields = ['date', 'interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
$this->textareaFields = ['notes'];
@@ -97,7 +97,7 @@ class UpdateRequest extends FormRequest
*/
private function getTransactionData(): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$return = [];
/** @var null|array $transactions */
@@ -181,7 +181,7 @@ class UpdateRequest extends FormRequest
private function getDateData(array $current, array $transaction): array
{
foreach ($this->dateFields as $fieldName) {
app('log')->debug(sprintf('Now at date field %s', $fieldName));
Log::debug(sprintf('Now at date field %s', $fieldName));
if (array_key_exists($fieldName, $transaction)) {
Log::debug(sprintf('New value: "%s"', $transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
@@ -247,7 +247,7 @@ class UpdateRequest extends FormRequest
*/
public function rules(): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$validProtocols = config('firefly.valid_url_protocols');
return [
@@ -330,7 +330,7 @@ class UpdateRequest extends FormRequest
*/
public function withValidator(Validator $validator): void
{
app('log')->debug('Now in withValidator');
Log::debug('Now in withValidator');
/** @var TransactionGroup $transactionGroup */
$transactionGroup = $this->route()->parameter('transactionGroup');

View File

@@ -26,12 +26,14 @@ namespace FireflyIII\Console\Commands\System;
use Carbon\Carbon;
use FireflyIII\Support\System\GeneratesInstallationId;
use FireflyIII\Support\System\IsOldVersion;
use Illuminate\Console\Command;
use Random\RandomException;
class OutputsInstructions extends Command
{
use GeneratesInstallationId;
use IsOldVersion;
protected $description = 'Instructions in case of upgrade trouble.';
@@ -58,7 +60,7 @@ class OutputsInstructions extends Command
*/
private function updateInstructions(): void
{
$version = (string) config('firefly.version');
$version = (string)config('firefly.version');
/** @var array $config */
$config = config('upgrade.text.upgrade');
@@ -68,12 +70,12 @@ class OutputsInstructions extends Command
foreach (array_keys($config) as $compare) {
// if string starts with:
if (str_starts_with($version, $compare)) {
$text = (string) $config[$compare];
$text = (string)$config[$compare];
}
}
// validate some settings.
if ('' === $text && 'local' === (string) config('app.env')) {
if ('' === $text && 'local' === (string)config('app.env')) {
$text = 'Please set APP_ENV=production for a safer environment.';
}
@@ -191,7 +193,7 @@ class OutputsInstructions extends Command
*/
private function installInstructions(): void
{
$version = (string) config('firefly.version');
$version = (string)config('firefly.version');
/** @var array $config */
$config = config('upgrade.text.install');
@@ -201,12 +203,12 @@ class OutputsInstructions extends Command
foreach (array_keys($config) as $compare) {
// if string starts with:
if (str_starts_with($version, $compare)) {
$text = (string) $config[$compare];
$text = (string)$config[$compare];
}
}
// validate some settings.
if ('' === $text && 'local' === (string) config('app.env')) {
if ('' === $text && 'local' === (string)config('app.env')) {
$text = 'Please set APP_ENV=production for a safer environment.';
}
@@ -242,14 +244,15 @@ class OutputsInstructions extends Command
private function someQuote(): void
{
$lines = [
'Forgive yourself for not being at peace.',
'Doesn\'t look like anything to me.',
'Be proud of what you make.',
'Be there or forever wonder.',
'A year from now you will wish you had started today.',
$lines = [
'"Forgive yourself for not being at peace."',
'"Doesn\'t look like anything to me."',
'"Be proud of what you make."',
'"Be there or forever wonder."',
'"A year from now you will wish you had started today."',
'🇺🇦 Слава Україні!',
'🇺🇦 Slava Ukraini!',
];
$addQuotes = true;
// fuck the Russian aggression in Ukraine.
@@ -260,8 +263,7 @@ class OutputsInstructions extends Command
// going on, to allow that to happen.
if ('ru_RU' === config('firefly.default_language')) {
$addQuotes = false;
$lines = [
$lines = [
'🇺🇦 Слава Україні!',
'🇺🇦 Slava Ukraini!',
];
@@ -272,11 +274,6 @@ class OutputsInstructions extends Command
} catch (RandomException) {
$random = 0;
}
if ($addQuotes) {
$this->line(sprintf(' "%s"', $lines[$random]));
return;
}
$this->line(sprintf(' %s', $lines[$random]));
}

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\System;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Console\Command;
class SetsLatestVersion extends Command
@@ -45,8 +46,7 @@ class SetsLatestVersion extends Command
return 0;
}
app('fireflyconfig')->set('db_version', config('firefly.db_version'));
app('fireflyconfig')->set('ff3_version', config('firefly.version'));
FireflyConfig::set('ff3_version', config('firefly.version'));
$this->friendlyInfo('Updated version.');
return 0;

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Support\Facades\Log;
use Safe\Exceptions\InfoException;
@@ -86,10 +87,8 @@ class UpgradesDatabase extends Command
$this->friendlyLine(sprintf('Now executing %s', $command));
$this->call($command, $args);
}
// set new DB version.
app('fireflyconfig')->set('db_version', (int) config('firefly.db_version'));
// index will set FF3 version.
app('fireflyconfig')->set('ff3_version', (string) config('firefly.version'));
FireflyConfig::set('ff3_version', (string) config('firefly.version'));
return 0;
}

View File

@@ -242,6 +242,7 @@ class PiggyBankFactory
}
}
}
Log::debug('Looping all accounts.');
/** @var array $info */
foreach ($accounts as $info) {
@@ -251,6 +252,7 @@ class PiggyBankFactory
continue;
}
Log::debug(sprintf('Working on account #%d', $account->id));
if (array_key_exists('current_amount', $info) && null !== $info['current_amount']) {
// an amount is set, first check out if there is a difference with the previous amount.
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
@@ -258,22 +260,24 @@ class PiggyBankFactory
// create event for difference.
if (0 !== bccomp($diff, '0')) {
// 2025-10-01 for issue #10990 disable this event.
Log::debug(sprintf('[a] Will save event for difference %s (previous value was %s)', $diff, $previous));
event(new ChangedAmount($piggyBank, $diff, null, null));
// event(new ChangedAmount($piggyBank, $diff, null, null));
}
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
Log::debug(sprintf('[a] Will link account #%d with amount %s', $account->id, $info['current_amount']));
}
if (array_key_exists('current_amount', $info) && null === $info['current_amount']) {
// an amount is set, first check out if there is a difference with the previous amount.
// no amount is set, first check out if there is a difference with the previous amount.
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
$diff = bcsub('0', $previous);
// create event for difference.
if (0 !== bccomp($diff, '0')) {
// 2025-10-01 for issue #10990 disable this event.
Log::debug(sprintf('[b] Will save event for difference %s (previous value was %s)', $diff, $previous));
event(new ChangedAmount($piggyBank, $diff, null, null));
// event(new ChangedAmount($piggyBank, $diff, null, null));
}
// no amount set, use previous amount or go to ZERO.
@@ -282,7 +286,8 @@ class PiggyBankFactory
// create event:
Log::debug('linkToAccountIds: Trigger change for positive amount [b].');
event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'] ?? '0', null, null));
// 2025-10-01 for issue #10990 disable this event.
// event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'] ?? '0', null, null));
}
if (!array_key_exists('current_amount', $info)) {
$toBeLinked[$account->id] ??= [];

View File

@@ -92,7 +92,7 @@ class ShowController extends Controller
// get first journal ever to set off the budget period overview.
$first = $this->journalRepos->firstNull();
$firstDate = $first instanceof TransactionJournal ? $first->date : $start;
$periods = $this->getNoBudgetPeriodOverview($firstDate, $end);
$periods = $this->getNoModelPeriodOverview('budget', $firstDate, $end);
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;

View File

@@ -35,6 +35,7 @@ use FireflyIII\Support\Http\Controllers\PeriodOverview;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
/**
@@ -74,7 +75,7 @@ class NoCategoryController extends Controller
*/
public function show(Request $request, ?Carbon $start = null, ?Carbon $end = null)
{
app('log')->debug('Start of noCategory()');
Log::debug('Start of noCategory()');
$start ??= session('start');
$end ??= session('end');
@@ -82,14 +83,12 @@ class NoCategoryController extends Controller
/** @var Carbon $end */
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview($start);
$subTitle = trans('firefly.without_category_between', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]);
$first = $this->journalRepos->firstNull()->date ?? clone $start;
$periods = $this->getNoModelPeriodOverview('category', $first, $end);
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
@@ -117,13 +116,13 @@ class NoCategoryController extends Controller
$periods = new Collection();
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
app('log')->debug('Start of noCategory()');
Log::debug('Start of noCategory()');
$subTitle = (string) trans('firefly.all_journals_without_category');
$first = $this->journalRepos->firstNull();
$start = $first instanceof TransactionJournal ? $first->date : new Carbon();
$end = today(config('app.timezone'));
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);

View File

@@ -184,7 +184,6 @@ class DebugController extends Controller
$currentDriver = DB::getDriverName();
return [
'db_version' => app('fireflyconfig')->get('db_version', 1)->data,
'php_version' => PHP_VERSION,
'php_os' => PHP_OS,
'uname' => php_uname('m'),

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\System;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Exception;
@@ -81,10 +82,7 @@ class InstallController extends Controller
{
app('view')->share('FF_VERSION', config('firefly.version'));
// index will set FF3 version.
app('fireflyconfig')->set('ff3_version', (string) config('firefly.version'));
// set new DB version.
app('fireflyconfig')->set('db_version', (int) config('firefly.db_version'));
FireflyConfig::set('ff3_version', (string) config('firefly.version'));
return view('install.index');
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Middleware;
use Closure;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\System\IsOldVersion;
use FireflyIII\Support\System\OAuthKeys;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
@@ -37,6 +38,8 @@ use Illuminate\Support\Facades\Log;
*/
class Installer
{
use IsOldVersion;
/**
* Handle an incoming request.
*
@@ -65,7 +68,7 @@ class Installer
// run installer when no tables are present,
// or when old scheme version
// or when old firefly version
if ($this->hasNoTables() || $this->oldDBVersion() || $this->oldVersion()) {
if ($this->hasNoTables() || $this->isOldVersionInstalled()) {
return response()->redirectTo(route('installer.index'));
}
OAuthKeys::verifyKeysRoutine();
@@ -126,59 +129,4 @@ class Installer
{
return false !== stripos($message, 'Base table or view not found');
}
/**
* Check if the "db_version" variable is correct.
*/
private function oldDBVersion(): bool
{
// older version in config than database?
$configVersion = (int) config('firefly.db_version');
$dbVersion = (int) app('fireflyconfig')->getFresh('db_version', 1)->data;
if ($configVersion > $dbVersion) {
Log::warning(
sprintf(
'The current configured version (%d) is older than the required version (%d). Redirect to migrate routine.',
$dbVersion,
$configVersion
)
);
return true;
}
// Log::info(sprintf('Configured DB version (%d) equals expected DB version (%d)', $dbVersion, $configVersion));
return false;
}
/**
* Check if the "firefly_version" variable is correct.
*/
private function oldVersion(): bool
{
// version compare thing.
$configVersion = (string) config('firefly.version');
$dbVersion = (string) app('fireflyconfig')->getFresh('ff3_version', '1.0')->data;
if (str_starts_with($configVersion, 'develop')) {
Log::debug('Skipping version check for develop version.');
return false;
}
if (1 === version_compare($configVersion, $dbVersion)) {
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::info(sprintf('Installed Firefly III version (%s) equals expected Firefly III version (%s)', $dbVersion, $configVersion));
return false;
}
}

View File

@@ -8,6 +8,7 @@ use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class PeriodStatistic extends Model
@@ -24,6 +25,11 @@ class PeriodStatistic extends Model
];
}
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
protected function count(): Attribute
{
return Attribute::make(

View File

@@ -76,6 +76,14 @@ class UserGroup extends Model
return $this->hasMany(Account::class);
}
/**
* Link to accounts.
*/
public function periodStatistics(): HasMany
{
return $this->hasMany(PeriodStatistic::class);
}
/**
* Link to attachments.
*/

View File

@@ -163,7 +163,6 @@ class FireflyServiceProvider extends ServiceProvider
$this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class);
$this->app->bind(ALERepositoryInterface::class, ALERepository::class);
$this->app->bind(PeriodStatisticRepositoryInterface::class, PeriodStatisticRepository::class);
$this->app->bind(
static function (Application $app): ObjectGroupRepositoryInterface {
@@ -177,6 +176,18 @@ class FireflyServiceProvider extends ServiceProvider
}
);
$this->app->bind(
static function (Application $app): PeriodStatisticRepositoryInterface {
/** @var PeriodStatisticRepository $repository */
$repository = app(PeriodStatisticRepository::class);
if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth)
$repository->setUser(auth()->user());
}
return $repository;
}
);
$this->app->bind(
static function (Application $app): WebhookRepositoryInterface {
/** @var WebhookRepository $repository */

View File

@@ -25,12 +25,17 @@ namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface
class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, UserGroupInterface
{
use UserGroupTrait;
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection
{
return $model->primaryPeriodStatistics()
@@ -56,6 +61,7 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface
$stat = new PeriodStatistic();
$stat->primaryStatable()->associate($model);
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
$stat->start = $start;
$stat->start_tz = $start->format('e');
$stat->end = $end;
@@ -89,4 +95,42 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface
{
$model->primaryPeriodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->delete();
}
#[Override]
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection
{
return $this->userGroup->periodStatistics()
->where('type', 'LIKE', sprintf('%s%%', $prefix))
->where('start', '>=', $start)->where('end', '<=', $end)->get()
;
}
#[Override]
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
{
$stat = new PeriodStatistic();
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
$stat->start = $start;
$stat->start_tz = $start->format('e');
$stat->end = $end;
$stat->end_tz = $end->format('e');
$stat->amount = $amount;
$stat->count = $count;
$stat->type = sprintf('%s_%s', $prefix, $type);
$stat->save();
Log::debug(sprintf(
'Saved #%d [currency #%d, type "%s", %s to %s, %d, %s] as new statistic.',
$stat->id,
$stat->transaction_currency_id,
$stat->type,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
return $stat;
}
}

View File

@@ -36,7 +36,11 @@ interface PeriodStatisticRepositoryInterface
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection;
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection;
public function deleteStatisticsForModel(Model $model, Carbon $date): void;
}

View File

@@ -36,6 +36,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
@@ -227,7 +228,6 @@ trait ModifiesPiggyBanks
$factory->user = $this->user;
// the piggy bank currency is set or updated FIRST, if it exists.
$factory->linkToAccountIds($piggyBank, $data['accounts'] ?? []);
@@ -244,7 +244,7 @@ trait ModifiesPiggyBanks
// question is, from which account(s) to remove the difference?
// solution: just start from the top until there is no more money left to remove.
$this->removeAmountFromAll($piggyBank, app('steam')->positive($difference));
$this->removeAmountFromAll($piggyBank, Steam::positive($difference));
}
// update using name:

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Services\FireflyIIIOrg\Update;
use Carbon\Carbon;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Support\System\IsOldVersion;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
@@ -38,6 +39,8 @@ use function Safe\json_decode;
*/
class UpdateRequest implements UpdateRequestInterface
{
use IsOldVersion;
public function getUpdateInformation(string $channel): array
{
Log::debug(sprintf('Now in getUpdateInformation(%s)', $channel));
@@ -183,20 +186,15 @@ class UpdateRequest implements UpdateRequestInterface
private function parseResultDevelop(string $current, string $latest, array $information): array
{
Log::debug(sprintf('User is running develop version "%s"', $current));
$parts = explode('/', $current);
$compare = $this->compareDevelopVersions($current, $latest);
$return = [];
/** @var Carbon $devDate */
$devDate = Carbon::createFromFormat('Y-m-d', $parts[1]);
if ($devDate->lte($information['date'])) {
Log::debug(sprintf('This development release is older, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
if (-1 === $compare) {
$return['level'] = 'info';
$return['message'] = (string) trans('firefly.update_current_dev_older', ['version' => $current, 'new_version' => $latest]);
return $return;
}
Log::debug(sprintf('This development release is newer, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
$return['level'] = 'info';
$return['message'] = (string) trans('firefly.update_current_dev_newer', ['version' => $current, 'new_version' => $latest]);

View File

@@ -44,6 +44,7 @@ use FireflyIII\Support\Facades\Steam;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
/**
* Trait PeriodOverview.
@@ -76,7 +77,7 @@ trait PeriodOverview
{
protected AccountRepositoryInterface $accountRepository;
protected CategoryRepositoryInterface $categoryRepository;
protected TagRepositoryInterface $tagRepository;
protected TagRepositoryInterface $tagRepository;
protected JournalRepositoryInterface $journalRepos;
protected PeriodStatisticRepositoryInterface $periodStatisticRepo;
private Collection $statistics; // temp data holder
@@ -103,16 +104,10 @@ trait PeriodOverview
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
$this->statistics = $this->periodStatisticRepo->allInRangeForModel($account, $start, $end);
// TODO needs to be re-arranged:
// get all period stats for entire range.
// loop blocks, an loop the types, and select the missing ones.
// create new ones, or use collected.
$entries = [];
Log::debug(sprintf('Count of loops: %d', count($dates)));
foreach ($dates as $currentDate) {
$entries[] = $this->getSingleAccountPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']);
$entries[] = $this->getSingleModelPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']);
}
Log::debug('End of getAccountPeriodOverview()');
@@ -161,7 +156,7 @@ trait PeriodOverview
Log::debug(sprintf('Count of loops: %d', count($dates)));
foreach ($dates as $currentDate) {
$entries[] = $this->getSingleCategoryPeriod($category, $currentDate['period'], $currentDate['start'], $currentDate['end']);
$entries[] = $this->getSingleModelPeriod($category, $currentDate['period'], $currentDate['start'], $currentDate['end']);
}
return $entries;
@@ -174,253 +169,116 @@ trait PeriodOverview
*
* @throws FireflyException
*/
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
protected function getNoModelPeriodOverview(string $model, Carbon $start, Carbon $end): array
{
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToPrimary);
$cache->addProperty('no-budget-period-entries');
if ($cache->has()) {
return $cache->get();
}
Log::debug(sprintf('Now in getNoModelPeriodOverview(%s, %s %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d')));
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
/** @var array $dates */
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// get all expenses without a budget.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$journals = $collector->getExtractedJournals();
$dates = Navigation::blockPeriods($start, $end, $range);
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
$entries = [];
$this->statistics = $this->periodStatisticRepo->allInRangeForPrefix(sprintf('no_%s', $model), $start, $end);
Log::debug(sprintf('Collected %d stats', $this->statistics->count()));
foreach ($dates as $currentDate) {
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($set),
'spent' => $this->groupByCurrency($set),
'earned' => [],
'transferred_away' => [],
'transferred_in' => [],
];
$entries[] = $this->getSingleNoModelPeriodOverview($model, $currentDate['start'], $currentDate['end'], $currentDate['period']);
}
$cache->store($entries);
return $entries;
}
/**
* TODO fix the date.
*
* Show period overview for no category view.
*
* @throws FireflyException
*/
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
private function getSingleNoModelPeriodOverview(string $model, Carbon $start, Carbon $end, string $period): array
{
Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
$range = Navigation::getViewRange(true);
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon() : $first->date;
$end = clone $theDate;
$end = Navigation::endOfPeriod($end, $range);
Log::debug(sprintf('getSingleNoModelPeriodOverview(%s, %s, %s, %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d'), $period));
$statistics = $this->filterPrefixedStatistics($start, $end, sprintf('no_%s', $model));
$title = Navigation::periodShow($end, $period);
Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
// properties for cache
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earnedSet = $collector->getExtractedJournals();
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spentSet = $collector->getExtractedJournals();
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
$transferSet = $collector->getExtractedJournals();
/** @var array $currentDate */
foreach ($dates as $currentDate) {
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
}
Log::debug('End of loops');
return $entries;
}
protected function getSingleAccountPeriod(Account $account, string $period, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in getSingleAccountPeriod(#%d, %s %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
$return = [
'title' => Navigation::periodShow($start, $period),
'route' => route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => 0,
];
$this->transactions = [];
foreach ($types as $type) {
$set = $this->getSingleAccountPeriodByType($account, $start, $end, $type);
$return['total_transactions'] += $set['count'];
unset($set['count']);
$return[$type] = $set;
}
return $return;
}
protected function getSingleCategoryPeriod(Category $category, string $period, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in getSingleCategoryPeriod(#%d, %s %s)', $category->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
$return = [
'title' => Navigation::periodShow($start, $period),
'route' => route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => 0,
];
$this->transactions = [];
foreach ($types as $type) {
$set = $this->getSingleCategoryPeriodByType($category, $start, $end, $type);
$return['total_transactions'] += $set['count'];
unset($set['count']);
$return[$type] = $set;
}
return $return;
}
protected function getSingleTagPeriod(Tag $tag, string $period, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in getSingleTagPeriod(#%d, %s %s)', $tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
$return = [
'title' => Navigation::periodShow($start, $period),
'route' => route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => 0,
];
$this->transactions = [];
foreach ($types as $type) {
$set = $this->getSingleTagPeriodByType($tag, $start, $end, $type);
$return['total_transactions'] += $set['count'];
unset($set['count']);
$return[$type] = $set;
}
return $return;
}
protected function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
{
return $this->statistics->filter(
function (PeriodStatistic $statistic) use ($start, $end, $type) {
if (
!$statistic->end->equalTo($end)
&& $statistic->end->format('Y-m-d H:i:s') === $end->format('Y-m-d H:i:s')
) {
echo sprintf('End: "%s" vs "%s": %s', $statistic->end->toW3cString(), $end->toW3cString(), var_export($statistic->end->eq($end), true));
var_dump($statistic->end);
var_dump($end);
exit;
}
return $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type;
}
);
}
protected function getSingleAccountPeriodByType(Account $account, Carbon $start, Carbon $end, string $type): array
{
Log::debug(sprintf('Now in getSingleAccountPeriodByType(#%d, %s %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
$statistics = $this->filterStatistics($start, $end, $type);
// nothing found, regenerate them.
if (0 === $statistics->count()) {
Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
if (0 === count($this->transactions)) {
$this->transactions = $this->accountRepository->periodCollection($account, $start, $end);
}
Log::debug(sprintf('Found no statistics in period %s - %s, regenerating them.', $start->format('Y-m-d'), $end->format('Y-m-d')));
switch ($type) {
switch ($model) {
default:
throw new FireflyException(sprintf('Cannot deal with account period type %s', $type));
throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model));
case 'spent':
$result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $start, $end);
case 'budget':
// get all expenses without a budget.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spent = $collector->getExtractedJournals();
$earned = [];
$transferred = [];
break;
case 'earned':
$result = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $start, $end);
case 'category':
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earned = $collector->getExtractedJournals();
break;
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spent = $collector->getExtractedJournals();
case 'transferred_in':
$result = $this->filterTransfers('in', $start, $end);
break;
case 'transferred_away':
$result = $this->filterTransfers('away', $start, $end);
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
$transferred = $collector->getExtractedJournals();
break;
}
// each result must be grouped by currency, then saved as period statistic.
Log::debug(sprintf('Going to group %d found journal(s)', count($result)));
$grouped = $this->groupByCurrency($result);
$groupedSpent = $this->groupByCurrency($spent);
$groupedEarned = $this->groupByCurrency($earned);
$groupedTransferred = $this->groupByCurrency($transferred);
$entry
= [
'title' => $title,
'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [$start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => count($spent),
'spent' => $groupedSpent,
'earned' => $groupedEarned,
'transferred' => $groupedTransferred,
];
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'spent', $groupedSpent);
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'earned', $groupedEarned);
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'transferred', $groupedTransferred);
$this->saveGroupedAsStatistics($account, $start, $end, $type, $grouped);
return $grouped;
return $entry;
}
$grouped = [
'count' => 0,
];
Log::debug(sprintf('Found %d statistics in period %s - %s.', count($statistics), $start->format('Y-m-d'), $end->format('Y-m-d')));
$entry
= [
'title' => $title,
'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [$start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => 0,
'spent' => [],
'earned' => [],
'transferred' => [],
];
$grouped = [];
/** @var PeriodStatistic $statistic */
foreach ($statistics as $statistic) {
$id = (int)$statistic->transaction_currency_id;
$currency = Amount::getTransactionCurrencyById($id);
$grouped[$id] = [
$type = str_replace(sprintf('no_%s_', $model), '', $statistic->type);
$id = (int)$statistic->transaction_currency_id;
$currency = Amount::getTransactionCurrencyById($id);
$grouped[$type]['count'] ??= 0;
$grouped[$type][$id] = [
'amount' => (string)$statistic->amount,
'count' => (int)$statistic->count,
'currency_id' => $currency->id,
@@ -429,22 +287,99 @@ trait PeriodOverview
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$grouped['count'] += (int)$statistic->count;
$grouped[$type]['count'] += (int)$statistic->count;
}
$types = ['spent', 'earned', 'transferred'];
foreach ($types as $type) {
if (array_key_exists($type, $grouped)) {
$entry['total_transactions'] += $grouped[$type]['count'];
unset($grouped[$type]['count']);
$entry[$type] = $grouped[$type];
}
}
return $grouped;
return $entry;
}
protected function getSingleCategoryPeriodByType(Category $category, Carbon $start, Carbon $end, string $type): array
protected function getSingleModelPeriod(Model $model, string $period, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in getSingleCategoryPeriodByType(#%d, %s %s, %s)', $category->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
Log::debug(sprintf('Now in getSingleModelPeriod(%s #%d, %s %s)', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
$return = [
'title' => Navigation::periodShow($start, $period),
'route' => route(sprintf('%s.show', strtolower(Str::plural(class_basename($model)))), [$model->id, $start->format('Y-m-d'), $end->format('Y-m-d')]),
'total_transactions' => 0,
];
$this->transactions = [];
foreach ($types as $type) {
$set = $this->getSingleModelPeriodByType($model, $start, $end, $type);
$return['total_transactions'] += $set['count'];
unset($set['count']);
$return[$type] = $set;
}
return $return;
}
private function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
{
if (0 === $this->statistics->count()) {
Log::warning('Have no statistic to filter!');
return new Collection();
}
return $this->statistics->filter(
function (PeriodStatistic $statistic) use ($start, $end, $type) {
return $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type;
}
);
}
private function filterPrefixedStatistics(Carbon $start, Carbon $end, string $prefix): Collection
{
if (0 === $this->statistics->count()) {
Log::warning('Have no statistic to filter!');
return new Collection();
}
return $this->statistics->filter(
function (PeriodStatistic $statistic) use ($start, $end, $prefix) {
return $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix);
}
);
}
private function getSingleModelPeriodByType(Model $model, Carbon $start, Carbon $end, string $type): array
{
Log::debug(sprintf('Now in getSingleModelPeriodByType(%s #%d, %s %s, %s)', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
$statistics = $this->filterStatistics($start, $end, $type);
// nothing found, regenerate them.
if (0 === $statistics->count()) {
Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
if (0 === count($this->transactions)) {
$this->transactions = $this->categoryRepository->periodCollection($category, $start, $end);
switch ($model::class) {
default:
throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model::class));
case Category::class:
$this->transactions = $this->categoryRepository->periodCollection($model, $start, $end);
break;
case Account::class:
$this->transactions = $this->accountRepository->periodCollection($model, $start, $end);
break;
case Tag::class:
$this->transactions = $this->tagRepository->periodCollection($model, $start, $end);
break;
}
}
switch ($type) {
@@ -476,75 +411,7 @@ trait PeriodOverview
Log::debug(sprintf('Going to group %d found journal(s)', count($result)));
$grouped = $this->groupByCurrency($result);
$this->saveGroupedAsStatistics($category, $start, $end, $type, $grouped);
return $grouped;
}
$grouped = [
'count' => 0,
];
/** @var PeriodStatistic $statistic */
foreach ($statistics as $statistic) {
$id = (int)$statistic->transaction_currency_id;
$currency = Amount::getTransactionCurrencyById($id);
$grouped[$id] = [
'amount' => (string)$statistic->amount,
'count' => (int)$statistic->count,
'currency_id' => $currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$grouped['count'] += (int)$statistic->count;
}
return $grouped;
}
protected function getSingleTagPeriodByType(Tag $tag, Carbon $start, Carbon $end, string $type): array
{
Log::debug(sprintf('Now in getSingleTagPeriodByType(#%d, %s %s, %s)', $tag->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
$statistics = $this->filterStatistics($start, $end, $type);
// nothing found, regenerate them.
if (0 === $statistics->count()) {
Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
if (0 === count($this->transactions)) {
$this->transactions = $this->tagRepository->periodCollection($tag, $start, $end);
}
switch ($type) {
default:
throw new FireflyException(sprintf('Cannot deal with tag period type %s', $type));
case 'spent':
$result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $start, $end);
break;
case 'earned':
$result = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $start, $end);
break;
case 'transferred_in':
$result = $this->filterTransfers('in', $start, $end);
break;
case 'transferred_away':
$result = $this->filterTransfers('away', $start, $end);
break;
}
// each result must be grouped by currency, then saved as period statistic.
Log::debug(sprintf('Going to group %d found journal(s)', count($result)));
$grouped = $this->groupByCurrency($result);
$this->saveGroupedAsStatistics($tag, $start, $end, $type, $grouped);
$this->saveGroupedAsStatistics($model, $start, $end, $type, $grouped);
return $grouped;
}
@@ -594,66 +461,7 @@ trait PeriodOverview
Log::debug(sprintf('Count of loops: %d', count($dates)));
foreach ($dates as $currentDate) {
$entries[] = $this->getSingleTagPeriod($tag, $currentDate['period'], $currentDate['start'], $currentDate['end']);
}
return $entries;
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
/** @var array $dates */
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earnedSet = $collector->getExtractedJournals();
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spentSet = $collector->getExtractedJournals();
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
$transferSet = $collector->getExtractedJournals();
// filer all of them:
$earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
$spentSet = $this->filterJournalsByTag($spentSet, $tag);
$transferSet = $this->filterJournalsByTag($transferSet, $tag);
foreach ($dates as $currentDate) {
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'transactions' => 0,
'title' => $title,
'route' => route(
'tags.show',
[$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
$entries[] = $this->getSingleModelPeriod($tag, $currentDate['period'], $currentDate['start'], $currentDate['end']);
}
return $entries;
@@ -707,20 +515,20 @@ trait PeriodOverview
}
$entries[]
= [
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
++$loops;
}
return $entries;
}
protected function saveGroupedAsStatistics(Model $model, Carbon $start, Carbon $end, string $type, array $array): void
private function saveGroupedAsStatistics(Model $model, Carbon $start, Carbon $end, string $type, array $array): void
{
unset($array['count']);
Log::debug(sprintf('saveGroupedAsStatistics(%s #%d, %s, %s, "%s", array(%d))', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type, count($array)));
@@ -733,6 +541,19 @@ trait PeriodOverview
}
}
private function saveGroupedForPrefix(string $prefix, Carbon $start, Carbon $end, string $type, array $array): void
{
unset($array['count']);
Log::debug(sprintf('saveGroupedForPrefix("%s", %s, %s, "%s", array(%d))', $prefix, $start->format('Y-m-d'), $end->format('Y-m-d'), $type, count($array)));
foreach ($array as $entry) {
$this->periodStatisticRepo->savePrefixedStatistic($prefix, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
}
if (0 === count($array)) {
Log::debug('Save empty statistic.');
$this->periodStatisticRepo->savePrefixedStatistic($prefix, $this->primaryCurrency->id, $start, $end, $type, 0, '0');
}
}
/**
* Filter a list of journals by a set of dates, and then group them by currency.
*/
@@ -750,27 +571,6 @@ trait PeriodOverview
return $result;
}
private function filterJournalsByTag(array $set, Tag $tag): array
{
$return = [];
foreach ($set as $entry) {
$found = false;
/** @var array $localTag */
foreach ($entry['tags'] as $localTag) {
if ($localTag['id'] === $tag->id) {
$found = true;
}
}
if (false === $found) {
continue;
}
$return[] = $entry;
}
return $return;
}
private function filterTransactionsByType(TransactionTypeEnum $type, Carbon $start, Carbon $end): array
{
$result = [];
@@ -795,40 +595,6 @@ trait PeriodOverview
return $result;
}
/**
* Return only transactions where $account is the source.
*/
private function filterTransferredAway(Account $account, array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
if ($account->id === (int)$journal['source_account_id']) {
$return[] = $journal;
}
}
return $return;
}
/**
* Return only transactions where $account is the source.
*/
private function filterTransferredIn(Account $account, array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
if ($account->id === (int)$journal['destination_account_id']) {
$return[] = $journal;
}
}
return $return;
}
private function filterTransfers(string $direction, Carbon $start, Carbon $end): array
{
$result = [];
@@ -860,6 +626,9 @@ trait PeriodOverview
$return = [
'count' => 0,
];
if (0 === count($journals)) {
return $return;
}
/** @var array $journal */
foreach ($journals as $journal) {
@@ -898,7 +667,7 @@ trait PeriodOverview
];
$return[$currencyId]['amount'] = bcadd((string) $return[$currencyId]['amount'], $amount);
$return[$currencyId]['amount'] = bcadd((string)$return[$currencyId]['amount'], $amount);
++$return[$currencyId]['count'];
++$return['count'];
}

View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/*
* IsOldVersion.php
* Copyright (c) 2025 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Support\System;
use Carbon\Carbon;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Support\Facades\Log;
trait IsOldVersion
{
/**
* By default, version_compare() returns -1 if the first version is lower than the second, 0 if they are equal, and
* 1 if the second is lower.
*/
protected function compareDevelopVersions(string $current, string $latest): int
{
$currentParts = explode('/', $current);
$latestParts = explode('/', $latest);
if (2 !== count($currentParts) || 2 !== count($latestParts)) {
Log::error(sprintf('Version "%s" or "%s" is not a valid develop-version.', $current, $latest));
return 0;
}
$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));
return -1;
}
if ($currentDate->gt($latestDate)) {
Log::debug(sprintf('This current version is newer, current = %s, latest version %s.', $current, $latest));
return 1;
}
Log::debug(sprintf('This current version is of the same age, current = %s, latest version %s.', $current, $latest));
return 0;
}
/**
* Check if the "firefly_version" variable is correct.
*/
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')) {
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;
}
return false;
}
}

View File

@@ -5,16 +5,31 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## 6.4.1 - 2025-09-15
### Added
- #10979
### Fixed
- Fixed a missing filter from [issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803).
- #10833
- #10854
- #10891
- #10916
- #10920
- #10921
- #10833
- #10924
- #10938
- #10940
- #10954
- #10956
- #10960
- #10974
- #10990
### API
- #10803
- #10908

275
composer.lock generated
View File

@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
"version": "v12.31.1",
"version": "v12.32.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "281b711710c245dd8275d73132e92635be3094df"
"reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/281b711710c245dd8275d73132e92635be3094df",
"reference": "281b711710c245dd8275d73132e92635be3094df",
"url": "https://api.github.com/repos/laravel/framework/zipball/77b2740391cd2a825ba59d6fada45e9b8b9bcc5a",
"reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a",
"shasum": ""
},
"require": {
@@ -1915,7 +1915,6 @@
"monolog/monolog": "^3.0",
"nesbot/carbon": "^3.8.4",
"nunomaduro/termwind": "^2.0",
"phiki/phiki": "^2.0.0",
"php": "^8.2",
"psr/container": "^1.1.1|^2.0.1",
"psr/log": "^1.0|^2.0|^3.0",
@@ -2094,7 +2093,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-09-23T15:33:04+00:00"
"time": "2025-09-30T17:39:22+00:00"
},
{
"name": "laravel/passport",
@@ -2812,16 +2811,16 @@
},
{
"name": "league/csv",
"version": "9.25.0",
"version": "9.26.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "f856f532866369fb1debe4e7c5a1db185f40ef86"
"reference": "7fce732754d043f3938899e5183e2d0f3d31b571"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/f856f532866369fb1debe4e7c5a1db185f40ef86",
"reference": "f856f532866369fb1debe4e7c5a1db185f40ef86",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571",
"reference": "7fce732754d043f3938899e5183e2d0f3d31b571",
"shasum": ""
},
"require": {
@@ -2899,7 +2898,7 @@
"type": "github"
}
],
"time": "2025-09-11T08:29:08+00:00"
"time": "2025-10-01T11:24:54+00:00"
},
{
"name": "league/event",
@@ -4352,77 +4351,6 @@
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phiki/phiki",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phikiphp/phiki.git",
"reference": "160785c50c01077780ab217e5808f00ab8f05a13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phikiphp/phiki/zipball/160785c50c01077780ab217e5808f00ab8f05a13",
"reference": "160785c50c01077780ab217e5808f00ab8f05a13",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"league/commonmark": "^2.5.3",
"php": "^8.2",
"psr/simple-cache": "^3.0"
},
"require-dev": {
"illuminate/support": "^11.45",
"laravel/pint": "^1.18.1",
"orchestra/testbench": "^9.15",
"pestphp/pest": "^3.5.1",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.0",
"symfony/var-dumper": "^7.1.6"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Phiki\\Adapters\\Laravel\\PhikiServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Phiki\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ryan Chandler",
"email": "support@ryangjchandler.co.uk",
"homepage": "https://ryangjchandler.co.uk",
"role": "Developer"
}
],
"description": "Syntax highlighting using TextMate grammars in PHP.",
"support": {
"issues": "https://github.com/phikiphp/phiki/issues",
"source": "https://github.com/phikiphp/phiki/tree/v2.0.4"
},
"funding": [
{
"url": "https://github.com/sponsors/ryangjchandler",
"type": "github"
},
{
"url": "https://buymeacoffee.com/ryangjchandler",
"type": "other"
}
],
"time": "2025-09-20T17:21:02+00:00"
},
{
"name": "php-http/client-common",
"version": "2.7.2",
@@ -6488,16 +6416,16 @@
},
{
"name": "symfony/cache",
"version": "v7.3.2",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6"
"reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6",
"reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6",
"url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f",
"reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f",
"shasum": ""
},
"require": {
@@ -6566,7 +6494,7 @@
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v7.3.2"
"source": "https://github.com/symfony/cache/tree/v7.3.4"
},
"funding": [
{
@@ -6586,7 +6514,7 @@
"type": "tidelift"
}
],
"time": "2025-07-30T17:13:41+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/cache-contracts",
@@ -6740,16 +6668,16 @@
},
{
"name": "symfony/console",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"shasum": ""
},
"require": {
@@ -6814,7 +6742,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.3"
"source": "https://github.com/symfony/console/tree/v7.3.4"
},
"funding": [
{
@@ -6834,7 +6762,7 @@
"type": "tidelift"
}
],
"time": "2025-08-25T06:35:40+00:00"
"time": "2025-09-22T15:31:00+00:00"
},
{
"name": "symfony/css-selector",
@@ -6970,16 +6898,16 @@
},
{
"name": "symfony/error-handler",
"version": "v7.3.2",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
"reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3"
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
"reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
"shasum": ""
},
"require": {
@@ -7027,7 +6955,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/error-handler/tree/v7.3.2"
"source": "https://github.com/symfony/error-handler/tree/v7.3.4"
},
"funding": [
{
@@ -7047,7 +6975,7 @@
"type": "tidelift"
}
],
"time": "2025-07-07T08:17:57+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -7347,16 +7275,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019"
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62",
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62",
"shasum": ""
},
"require": {
@@ -7423,7 +7351,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.3.3"
"source": "https://github.com/symfony/http-client/tree/v7.3.4"
},
"funding": [
{
@@ -7443,7 +7371,7 @@
"type": "tidelift"
}
],
"time": "2025-08-27T07:45:05+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -7525,16 +7453,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00"
"reference": "c061c7c18918b1b64268771aad04b40be41dd2e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00",
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6",
"reference": "c061c7c18918b1b64268771aad04b40be41dd2e6",
"shasum": ""
},
"require": {
@@ -7584,7 +7512,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.3.3"
"source": "https://github.com/symfony/http-foundation/tree/v7.3.4"
},
"funding": [
{
@@ -7604,20 +7532,20 @@
"type": "tidelift"
}
],
"time": "2025-08-20T08:04:18+00:00"
"time": "2025-09-16T08:38:17+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b"
"reference": "b796dffea7821f035047235e076b60ca2446e3cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b",
"reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf",
"reference": "b796dffea7821f035047235e076b60ca2446e3cf",
"shasum": ""
},
"require": {
@@ -7702,7 +7630,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.3.3"
"source": "https://github.com/symfony/http-kernel/tree/v7.3.4"
},
"funding": [
{
@@ -7722,20 +7650,20 @@
"type": "tidelift"
}
],
"time": "2025-08-29T08:23:45+00:00"
"time": "2025-09-27T12:32:17+00:00"
},
{
"name": "symfony/mailer",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
"reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575"
"reference": "ab97ef2f7acf0216955f5845484235113047a31d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575",
"reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575",
"url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d",
"reference": "ab97ef2f7acf0216955f5845484235113047a31d",
"shasum": ""
},
"require": {
@@ -7786,7 +7714,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/mailer/tree/v7.3.3"
"source": "https://github.com/symfony/mailer/tree/v7.3.4"
},
"funding": [
{
@@ -7806,7 +7734,7 @@
"type": "tidelift"
}
],
"time": "2025-08-13T11:49:31+00:00"
"time": "2025-09-17T05:51:54+00:00"
},
{
"name": "symfony/mailgun-mailer",
@@ -7879,16 +7807,16 @@
},
{
"name": "symfony/mime",
"version": "v7.3.2",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1"
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1",
"reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1",
"url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35",
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35",
"shasum": ""
},
"require": {
@@ -7943,7 +7871,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v7.3.2"
"source": "https://github.com/symfony/mime/tree/v7.3.4"
},
"funding": [
{
@@ -7963,7 +7891,7 @@
"type": "tidelift"
}
],
"time": "2025-07-15T13:41:35+00:00"
"time": "2025-09-16T08:38:17+00:00"
},
{
"name": "symfony/options-resolver",
@@ -8867,16 +8795,16 @@
},
{
"name": "symfony/process",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"shasum": ""
},
"require": {
@@ -8908,7 +8836,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.3.3"
"source": "https://github.com/symfony/process/tree/v7.3.4"
},
"funding": [
{
@@ -8928,7 +8856,7 @@
"type": "tidelift"
}
],
"time": "2025-08-18T09:42:54+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
@@ -9015,16 +8943,16 @@
},
{
"name": "symfony/routing",
"version": "v7.3.2",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4"
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
"reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
"url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c",
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c",
"shasum": ""
},
"require": {
@@ -9076,7 +9004,7 @@
"url"
],
"support": {
"source": "https://github.com/symfony/routing/tree/v7.3.2"
"source": "https://github.com/symfony/routing/tree/v7.3.4"
},
"funding": [
{
@@ -9096,7 +9024,7 @@
"type": "tidelift"
}
],
"time": "2025-07-15T11:36:08+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/service-contracts",
@@ -9183,16 +9111,16 @@
},
{
"name": "symfony/string",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
"reference": "f96476035142921000338bad71e5247fbc138872"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
"reference": "f96476035142921000338bad71e5247fbc138872",
"shasum": ""
},
"require": {
@@ -9207,7 +9135,6 @@
},
"require-dev": {
"symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
@@ -9250,7 +9177,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.3.3"
"source": "https://github.com/symfony/string/tree/v7.3.4"
},
"funding": [
{
@@ -9270,20 +9197,20 @@
"type": "tidelift"
}
],
"time": "2025-08-25T06:35:40+00:00"
"time": "2025-09-11T14:36:48+00:00"
},
{
"name": "symfony/translation",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "e0837b4cbcef63c754d89a4806575cada743a38d"
"reference": "ec25870502d0c7072d086e8ffba1420c85965174"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d",
"reference": "e0837b4cbcef63c754d89a4806575cada743a38d",
"url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174",
"reference": "ec25870502d0c7072d086e8ffba1420c85965174",
"shasum": ""
},
"require": {
@@ -9350,7 +9277,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v7.3.3"
"source": "https://github.com/symfony/translation/tree/v7.3.4"
},
"funding": [
{
@@ -9370,7 +9297,7 @@
"type": "tidelift"
}
],
"time": "2025-08-01T21:02:37+00:00"
"time": "2025-09-07T11:39:36+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -9526,16 +9453,16 @@
},
{
"name": "symfony/var-dumper",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f"
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
"shasum": ""
},
"require": {
@@ -9589,7 +9516,7 @@
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v7.3.3"
"source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
},
"funding": [
{
@@ -9609,20 +9536,20 @@
"type": "tidelift"
}
],
"time": "2025-08-13T11:49:31+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/var-exporter",
"version": "v7.3.3",
"version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
"reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137"
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137",
"reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
"shasum": ""
},
"require": {
@@ -9670,7 +9597,7 @@
"serialize"
],
"support": {
"source": "https://github.com/symfony/var-exporter/tree/v7.3.3"
"source": "https://github.com/symfony/var-exporter/tree/v7.3.4"
},
"funding": [
{
@@ -9690,7 +9617,7 @@
"type": "tidelift"
}
],
"time": "2025-08-18T13:10:53+00:00"
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "thecodingmachine/safe",
@@ -11893,16 +11820,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.3.14",
"version": "12.3.15",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "13e9b2bea9327b094176147250d2c10319a10f5b"
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/13e9b2bea9327b094176147250d2c10319a10f5b",
"reference": "13e9b2bea9327b094176147250d2c10319a10f5b",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"shasum": ""
},
"require": {
@@ -11916,7 +11843,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
"phpunit/php-code-coverage": "^12.3.8",
"phpunit/php-code-coverage": "^12.4.0",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@@ -11970,7 +11897,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.3.14"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.15"
},
"funding": [
{
@@ -11994,7 +11921,7 @@
"type": "tidelift"
}
],
"time": "2025-09-24T06:34:27+00:00"
"time": "2025-09-28T12:10:54+00:00"
},
{
"name": "rector/rector",

View File

@@ -78,10 +78,10 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-09-27',
'build_time' => 1758945787,
'version' => 'develop/2025-10-02',
'build_time' => 1759381368,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 27,
'db_version' => 28, // field is no longer used.
// generic settings
'maxUploadSize' => 1073741824, // 1 GB

View File

@@ -14,6 +14,10 @@ return new class extends Migration
Schema::create('period_statistics', function (Blueprint $table) {
$table->id();
$table->timestamps();
// 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();
@@ -33,6 +37,7 @@ return new class extends Migration
$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');
});
}

View File

@@ -80,6 +80,7 @@ class TransactionCurrencySeeder extends Seeder
$currencies[] = ['code' => 'CHF', 'name' => 'Swiss franc', 'symbol' => 'CHF', 'decimal_places' => 2];
$currencies[] = ['code' => 'NOK', 'name' => 'Norwegian krone', 'symbol' => 'kr.', 'decimal_places' => 2];
$currencies[] = ['code' => 'CZK', 'name' => 'Czech koruna', 'symbol' => 'Kč', 'decimal_places' => 2];
$currencies[] = ['code' => 'KZT', 'name' => 'Kazakhstani tenge', 'symbol' => '₸', 'decimal_places' => 2];
foreach ($currencies as $currency) {
if (null === TransactionCurrency::where('code', $currency['code'])->first()) {

346
package-lock.json generated
View File

@@ -2135,9 +2135,9 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.1.tgz",
"integrity": "sha512-RLmb9U6H2rJDnGxEqXxzy7ANPrQz7WK2/eTjdZqyU9uRU5W+FkAec9uU5gTYzFBH7aoXIw2WTJSCJR4KPlReQw==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz",
"integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
@@ -2589,9 +2589,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"cpu": [
"arm"
],
@@ -2603,9 +2603,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"cpu": [
"arm64"
],
@@ -2617,9 +2617,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"cpu": [
"arm64"
],
@@ -2631,9 +2631,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"cpu": [
"x64"
],
@@ -2645,9 +2645,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"cpu": [
"arm64"
],
@@ -2659,9 +2659,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"cpu": [
"x64"
],
@@ -2673,9 +2673,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"cpu": [
"arm"
],
@@ -2687,9 +2687,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"cpu": [
"arm"
],
@@ -2701,9 +2701,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"cpu": [
"arm64"
],
@@ -2715,9 +2715,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"cpu": [
"arm64"
],
@@ -2729,9 +2729,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"cpu": [
"loong64"
],
@@ -2743,9 +2743,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"cpu": [
"ppc64"
],
@@ -2757,9 +2757,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"cpu": [
"riscv64"
],
@@ -2771,9 +2771,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"cpu": [
"riscv64"
],
@@ -2785,9 +2785,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"cpu": [
"s390x"
],
@@ -2799,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"cpu": [
"x64"
],
@@ -2813,9 +2813,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"cpu": [
"x64"
],
@@ -2827,9 +2827,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"cpu": [
"arm64"
],
@@ -2841,9 +2841,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"cpu": [
"arm64"
],
@@ -2855,9 +2855,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"cpu": [
"ia32"
],
@@ -2869,9 +2869,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
"cpu": [
"x64"
],
@@ -2883,9 +2883,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
"cpu": [
"x64"
],
@@ -3173,13 +3173,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"version": "24.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.12.0"
"undici-types": "~7.13.0"
}
},
"node_modules/@types/node-forge": {
@@ -3898,16 +3898,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
@@ -4075,9 +4065,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz",
"integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==",
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz",
"integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -4360,9 +4350,9 @@
}
},
"node_modules/browserslist": {
"version": "4.26.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
"integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
"version": "4.26.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
"dev": true,
"funding": [
{
@@ -4380,9 +4370,9 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
"electron-to-chromium": "^1.5.218",
"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"
},
@@ -4521,9 +4511,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001745",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz",
"integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==",
"version": "1.0.30001746",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz",
"integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==",
"dev": true,
"funding": [
{
@@ -5061,9 +5051,9 @@
}
},
"node_modules/cross-env": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz",
"integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5736,9 +5726,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.224",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz",
"integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==",
"version": "1.5.228",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
"integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
"dev": true,
"license": "ISC"
},
@@ -5820,9 +5810,9 @@
}
},
"node_modules/envinfo": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz",
"integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==",
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.15.0.tgz",
"integrity": "sha512-chR+t7exF6y59kelhXw5I3849nTy7KIRO+ePdLMhCD+JRP/JvmkenDWP7QSFGlsHX+kxGxdDutOPrmj5j1HR6g==",
"dev": true,
"license": "MIT",
"bin": {
@@ -7088,9 +7078,9 @@
}
},
"node_modules/i18next": {
"version": "25.5.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz",
"integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==",
"version": "25.5.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.3.tgz",
"integrity": "sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==",
"funding": [
{
"type": "individual",
@@ -8653,16 +8643,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -8818,9 +8798,9 @@
}
},
"node_modules/patch-package": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8829,15 +8809,14 @@
"ci-info": "^3.7.0",
"cross-spawn": "^7.0.3",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^9.0.0",
"fs-extra": "^10.0.0",
"json-stable-stringify": "^1.0.2",
"klaw-sync": "^6.0.0",
"minimist": "^1.2.6",
"open": "^7.4.2",
"rimraf": "^2.6.3",
"semver": "^7.5.3",
"slash": "^2.0.0",
"tmp": "^0.0.33",
"tmp": "^0.2.4",
"yaml": "^2.2.2"
},
"bin": {
@@ -8848,22 +8827,6 @@
"npm": ">5"
}
},
"node_modules/patch-package/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/patch-package/node_modules/slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
@@ -10086,9 +10049,9 @@
}
},
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
@@ -10097,6 +10060,9 @@
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ripemd160": {
@@ -10130,9 +10096,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10146,28 +10112,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.2",
"@rollup/rollup-android-arm64": "4.52.2",
"@rollup/rollup-darwin-arm64": "4.52.2",
"@rollup/rollup-darwin-x64": "4.52.2",
"@rollup/rollup-freebsd-arm64": "4.52.2",
"@rollup/rollup-freebsd-x64": "4.52.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
"@rollup/rollup-linux-arm64-musl": "4.52.2",
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
"@rollup/rollup-linux-x64-gnu": "4.52.2",
"@rollup/rollup-linux-x64-musl": "4.52.2",
"@rollup/rollup-openharmony-arm64": "4.52.2",
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
"@rollup/rollup-win32-x64-gnu": "4.52.2",
"@rollup/rollup-win32-x64-msvc": "4.52.2",
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"fsevents": "~2.3.2"
}
},
@@ -11214,16 +11180,13 @@
}
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"dev": true,
"license": "MIT",
"dependencies": {
"os-tmpdir": "~1.0.2"
},
"engines": {
"node": ">=0.6.0"
"node": ">=14.14"
}
},
"node_modules/to-arraybuffer": {
@@ -11343,9 +11306,9 @@
}
},
"node_modules/undici-types": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"dev": true,
"license": "MIT"
},
@@ -11863,9 +11826,9 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.101.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"version": "5.102.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.0.tgz",
"integrity": "sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11877,7 +11840,7 @@
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.15.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.24.0",
"browserslist": "^4.24.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.3",
"es-module-lexer": "^1.2.1",
@@ -11890,9 +11853,9 @@
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.2",
"tapable": "^2.1.1",
"tapable": "^2.2.3",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1",
"watchpack": "^2.4.4",
"webpack-sources": "^3.3.3"
},
"bin": {
@@ -12155,23 +12118,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/webpack-dev-server/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/webpack-dev-server/node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",

View File

@@ -164,7 +164,7 @@
"title": "Titel",
"date": "Datum",
"book_date": "Buchungsdatum",
"process_date": "Bearbeitungsdatum",
"process_date": "Wertstellungsdatum",
"due_date": "F\u00e4lligkeitstermin",
"foreign_amount": "Ausl\u00e4ndischer Betrag",
"payment_date": "Zahlungsdatum",

View File

@@ -12,8 +12,7 @@
{# Firefly III version #}
<tr>
<td>Firefly III</td>
<td>{% if FF_IS_DEVELOP %}<!-- .Z9JBCmw64Zkx1pQw -->{% endif %}{% if not FF_IS_DEVELOP %}v{% endif %}{{ FF_VERSION }} / <span>#</span>{{ system.db_version }} (expects <span>#</span>{{ config('firefly.db_version') }})
</td>
<td>{% if FF_IS_DEVELOP %}<!-- .Z9JBCmw64Zkx1pQw -->{% endif %}{% if not FF_IS_DEVELOP %}v{% endif %}{{ FF_VERSION }}</td>
</tr>
{# PHP version + settings #}
<tr>