Merge pull request #11992 from firefly-iii/release-1774047683

🤖 Automatically merge the PR into the develop branch.
This commit is contained in:
github-actions[bot] authored and GitHub committed 2026-03-21 00:01:32 +01:00
commit 7c09278c8e
17 files changed
+254 -226

No files matched your search

@@ -43,7 +43,7 @@ final class DestroyController extends Controller
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()
@@ -102,6 +102,7 @@ final class StoreController extends Controller
$existing = $this->repository->updateExchangeRate($existing, $rate);
$collection->push($existing);
event(new UpdatedCurrencyExchangeRate($existing));
continue;
}
$new = $this->repository->storeExchangeRate($from, $to, $rate, $date);
@@ -44,7 +44,7 @@ final class UpdateController extends Controller
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()
@@ -68,7 +68,7 @@ final class UpdateController extends Controller
$rate = $request->getRate();
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
event(new UpdatedCurrencyExchangeRate($exchangeRate));
$transformer = new ExchangeRateTransformer();
$transformer = new ExchangeRateTransformer();
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
}
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* CreatedCurrencyExchangeRate.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -22,7 +25,6 @@
namespace FireflyIII\Events\Model\CurrencyExchangeRate;
use FireflyIII\Events\Event;
use FireflyIII\Models\CurrencyExchangeRate;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
@@ -31,7 +33,9 @@ class CreatedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(public CurrencyExchangeRate $rate,) {
public function __construct(
public CurrencyExchangeRate $rate
) {
Log::debug(sprintf('CreatedCurrencyExchangeRate(#%d) Event', $rate->id));
}
}
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* DestroyedCurrencyExchangeRate.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -31,8 +34,11 @@ class DestroyedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(public TransactionCurrency $from, public TransactionCurrency $to, public UserGroup $userGroup)
{
public function __construct(
public TransactionCurrency $from,
public TransactionCurrency $to,
public UserGroup $userGroup
) {
Log::debug(sprintf('DestroyedCurrencyExchangeRate(%s, %s) Event', $from->code, $to->code));
}
}
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* UpdatedCurrencyExchangeRate.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -30,7 +33,9 @@ class UpdatedCurrencyExchangeRate extends Event
{
use SerializesModels;
public function __construct(public CurrencyExchangeRate $rate) {
public function __construct(
public CurrencyExchangeRate $rate
) {
Log::debug(sprintf('UpdatedCurrencyExchangeRate(#%d) Event', $rate->id));
}
}
@@ -119,7 +119,11 @@ class UpdatesAccountInformation implements ShouldQueue
++$fixed;
}
// fix account number: // account_number
if (array_key_exists('account_number', $oldData) && $oldData['account_number'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
if (
array_key_exists('account_number', $oldData)
&& $oldData['account_number'] === $trigger->trigger_value
&& in_array($trigger->trigger_type, $numberFields, true)
) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account account_number, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->iban;
$trigger->save();
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* ProcessesExchangeRates.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -34,13 +37,14 @@ use Illuminate\Support\Facades\Log;
class ProcessesExchangeRates
{
public function handle(CreatedCurrencyExchangeRate | UpdatedCurrencyExchangeRate | DestroyedCurrencyExchangeRate $event): void
public function handle(CreatedCurrencyExchangeRate|DestroyedCurrencyExchangeRate|UpdatedCurrencyExchangeRate $event): void
{
Preferences::mark();
Cache::clear();
if ($event instanceof DestroyedCurrencyExchangeRate) {
$this->handleCurrency($event->userGroup, $event->from);
$this->handleCurrency($event->userGroup, $event->to);
return;
}
$this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency);
@@ -49,18 +53,15 @@ class ProcessesExchangeRates
private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency): void
{
$calculator = new PrimaryAmountRecalculationService();
if (Amount::convertToPrimary()) {
Log::debug(sprintf('Will now convert amounts to primary currency for currency %s.', $currency->code));
$calculator->recalculateForGroupAndCurrency($userGroup, $currency);
// $calculator->recalculateForGroup($userGroup);
// $calculator->recalculateForGroup($userGroup);
return;
}
Log::debug('Will NOT convert to primary currency.');
}
}
@@ -25,15 +25,8 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\System;
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
use FireflyIII\Services\Internal\Recalculate\PrimaryAmountRecalculationService;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Facades\Log;
class RecalculatesPrimaryCurrencyAmounts
@@ -50,8 +43,4 @@ class RecalculatesPrimaryCurrencyAmounts
}
Log::debug('Will NOT convert to primary currency.');
}
}
+1
View File
@@ -58,6 +58,7 @@ class CurrencyExchangeRate extends Model
{
return $this->belongsTo(User::class);
}
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
+14 -3
View File
@@ -368,10 +368,21 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getBudgets(): Collection
{
if(null === $this->user) {
return $this->userGroup->budgets()->orderBy('order', 'ASC')->orderBy('name', 'ASC')->get();
if (null === $this->user) {
return $this->userGroup
->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
return $this->user->budgets()->orderBy('order', 'ASC')->orderBy('name', 'ASC')->get();
return $this->user
->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
/**
@@ -73,130 +73,64 @@ class PrimaryAmountRecalculationService
}
}
private function resetGenericTables(UserGroup $userGroup): void
public function recalculateForGroup(UserGroup $userGroup): void
{
$tables = [
// !!! this array is also in the migration
'accounts' => ['native_virtual_balance'],
'available_budgets' => ['native_amount'],
'bills' => ['native_amount_min', 'native_amount_max'],
];
foreach ($tables as $table => $columns) {
Log::debug(sprintf('Now processing table "%s"', $table));
foreach ($columns as $column) {
Log::debug(sprintf('Resetting column "%s" in table "%s".', $column, $table));
DB::table($table)->where('user_group_id', $userGroup->id)->update([$column => null]);
}
}
Log::debug(sprintf('Now recalculating primary amounts for user group #%d', $userGroup->id));
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculateAccounts($userGroup, $currency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
private function resetPiggyBank(PiggyBank $piggyBank): void
public function recalculateForGroupAndCurrency(UserGroup $userGroup, TransactionCurrency $limitCurrency): void
{
if ('' !== (string)$piggyBank->native_target_amount) {
Log::debug(sprintf('Resetting native_target_amount for piggy bank #%d.', $piggyBank->id));
$piggyBank->native_target_amount = null;
$piggyBank->saveQuietly();
}
foreach ($piggyBank->accounts as $account) {
if ('' !== (string)$account->pivot->native_current_amount) {
Log::debug(sprintf('Resetting native_current_amount for piggy bank #%d and account #%d.', $piggyBank->id, $account->id));
$account->pivot->native_current_amount = null;
$account->pivot->save();
}
}
foreach ($piggyBank->piggyBankEvents as $event) {
if ('' !== (string)$event->native_amount) {
Log::debug(sprintf('Resetting native_amount for piggy bank #%d and event #%d.', $piggyBank->id, $event->id));
$event->native_amount = null;
$event->saveQuietly();
}
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
if ($limitCurrency->id === $currency->id) {
Log::debug(sprintf('Can skip recalculation because user requested the same currencies (%s).', $limitCurrency->code));
return;
}
$this->recalculateAccountsForCurrency($userGroup, $currency, $limitCurrency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactionsForCurrency($userGroup, $currency, $limitCurrency);
}
private function resetTransactions(UserGroup $userGroup): void
{
// custom query because of the potential size of this update.
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')->orWhereNotNull('native_foreign_amount');
})
->update(['native_amount' => null, 'native_foreign_amount' => null]);
Log::debug(sprintf('Reset %d transactions.', $success));
}
private function resetPiggyBanks(UserGroup $userGroup): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$piggyBanks = $repository->getPiggyBanks();
Log::debug(sprintf('Reset primary currency of %d piggy bank(s).', $piggyBanks->count()));
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
$this->resetPiggyBank($piggyBank);
}
}
private function resetBudgets(UserGroup $userGroup): void
{
$repository = app(BudgetRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
Log::debug(sprintf('Reset primary currency of %d budget(s).', $set->count()));
/** @var Budget $budget */
foreach ($set as $budget) {
$this->resetBudget($budget);
}
}
private function resetBudget(Budget $budget): void
{
foreach ($budget->autoBudgets as $autoBudget) {
if ('' === (string)$autoBudget->native_amount) {
continue;
}
Log::debug(sprintf('Resetting native_amount for budget #%d and auto budget #%d.', $budget->id, $autoBudget->id));
$autoBudget->native_amount = null;
$autoBudget->saveQuietly();
}
foreach ($budget->budgetlimits as $limit) {
if ('' !== (string)$limit->native_amount) {
Log::debug(sprintf('Resetting native_amount for budget #%d and budget limit #%d.', $budget->id, $limit->id));
$limit->native_amount = null;
$limit->saveQuietly();
}
}
}
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
{
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
// ->where(static function (DatabaseBuilder $q) use ($currency): void {
// $q->whereNot('transactions.transaction_currency_id', $currency->id)
// ->whereNot('transactions.foreign_currency_id', $currency->id)
// ;
// })
->get(['transactions.id']);
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
Log::debug(sprintf('Count of set is %d', $set->count()));
foreach ($set as $item) {
Log::debug(sprintf('Touch transaction #%d', $item->id));
// here we are.
/** @var null|Transaction $transaction */
$transaction = Transaction::find($item->id);
@@ -211,25 +145,25 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Now in calculateTransactionsForCurrency(#%d, %s, %s)', $userGroup->id, $currency->code, $limitCurrency->code));
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)
->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)
->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
// must be in the limit currency.
->where('transactions.transaction_currency_id', $limitCurrency->id)
->orWhere('transactions.foreign_currency_id', $limitCurrency->id)
->get(['transactions.id']);
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
Log::debug(sprintf('Count of set is %d', $set->count()));
foreach ($set as $item) {
Log::debug(sprintf('Touch transaction #%d', $item->id));
// here we are.
/** @var null|Transaction $transaction */
$transaction = Transaction::find($item->id);
@@ -239,27 +173,6 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
}
/**
* Only recalculate accounts that have a virtual balance.
*/
private function recalculateAccounts(UserGroup $userGroup, TransactionCurrency $groupCurrency): void
{
Log::debug(sprintf('recalculateAccounts(#%d, %s)', $userGroup->id, $groupCurrency->code));
$set = $this->collectAccounts($userGroup);
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int)$account->accountMeta()->where('name', 'currency_id')->first()->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));
continue;
}
Log::debug(sprintf('Account "%s" is NOT in group currency %s, so do it.', $account->name, $groupCurrency->code));
$account->touch();
}
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
}
private function collectAccounts(UserGroup $userGroup): Collection
{
return $userGroup
@@ -275,7 +188,30 @@ class PrimaryAmountRecalculationService
$q->orWhere('virtual_balance', '!=', '');
}
})
->get();
->get()
;
}
/**
* Only recalculate accounts that have a virtual balance.
*/
private function recalculateAccounts(UserGroup $userGroup, TransactionCurrency $groupCurrency): void
{
Log::debug(sprintf('recalculateAccounts(#%d, %s)', $userGroup->id, $groupCurrency->code));
$set = $this->collectAccounts($userGroup);
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));
continue;
}
Log::debug(sprintf('Account "%s" is NOT in group currency %s, so do it.', $account->name, $groupCurrency->code));
$account->touch();
}
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
}
/**
@@ -289,13 +225,15 @@ class PrimaryAmountRecalculationService
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int)$account->accountMeta()->where('name', 'currency_id')->first()->data;
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));
continue;
}
if ($limitCurrency->id !== $currencyId) {
Log::debug(sprintf('Account "%s" is NOT in limit currency %s, skip.', $account->name, $limitCurrency->code));
continue;
}
Log::debug(sprintf('Account "%s" is NOT in group currency %s, so do it.', $account->name, $groupCurrency->code));
@@ -364,38 +302,6 @@ class PrimaryAmountRecalculationService
Log::debug(sprintf('Recalculated %d budgets.', $set->count()));
}
public function recalculateForGroupAndCurrency(UserGroup $userGroup, TransactionCurrency $limitCurrency): void
{
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
if ($limitCurrency->id === $currency->id) {
Log::debug(sprintf('Can skip recalculation because user requested the same currencies (%s).', $limitCurrency->code));
return;
}
$this->recalculateAccountsForCurrency($userGroup, $currency, $limitCurrency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactionsForCurrency($userGroup, $currency, $limitCurrency);
}
public function recalculateForGroup(UserGroup $userGroup): void
{
Log::debug(sprintf('Now recalculating primary amounts for user group #%d', $userGroup->id));
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculateAccounts($userGroup, $currency);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
private function recalculatePiggyBankEvents(PiggyBank $piggyBank): void
{
$set = $piggyBank->piggyBankEvents()->get();
@@ -410,25 +316,25 @@ class PrimaryAmountRecalculationService
*/
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
{
$converter = new ExchangeRateConverter();
$converter = new ExchangeRateConverter();
$converter->setUserGroup($userGroup);
$converter->setIgnoreSettings(true);
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getPiggyBanks();
$set = $set->filter(static fn(PiggyBank $piggyBank): bool => $currency->id !== $piggyBank->transaction_currency_id);
$set = $repository->getPiggyBanks();
$set = $set->filter(static fn (PiggyBank $piggyBank): bool => $currency->id !== $piggyBank->transaction_currency_id);
foreach ($set as $piggyBank) {
$piggyBank->encrypted = false;
$piggyBank->save();
foreach ($piggyBank->accounts as $account) {
$account->pivot->native_current_amount = null;
if (0 !== bccomp((string)$account->pivot->current_amount, '0')) {
if (0 !== bccomp((string) $account->pivot->current_amount, '0')) {
$account->pivot->native_current_amount = $converter->convert(
$piggyBank->transactionCurrency,
$currency,
today(),
(string)$account->pivot->current_amount
(string) $account->pivot->current_amount
);
}
$account->pivot->save();
@@ -437,4 +343,104 @@ class PrimaryAmountRecalculationService
}
Log::debug(sprintf('Recalculated %d piggy banks for user group #%d.', $set->count(), $userGroup->id));
}
private function resetBudget(Budget $budget): void
{
foreach ($budget->autoBudgets as $autoBudget) {
if ('' === (string) $autoBudget->native_amount) {
continue;
}
Log::debug(sprintf('Resetting native_amount for budget #%d and auto budget #%d.', $budget->id, $autoBudget->id));
$autoBudget->native_amount = null;
$autoBudget->saveQuietly();
}
foreach ($budget->budgetlimits as $limit) {
if ('' !== (string) $limit->native_amount) {
Log::debug(sprintf('Resetting native_amount for budget #%d and budget limit #%d.', $budget->id, $limit->id));
$limit->native_amount = null;
$limit->saveQuietly();
}
}
}
private function resetBudgets(UserGroup $userGroup): void
{
$repository = app(BudgetRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
Log::debug(sprintf('Reset primary currency of %d budget(s).', $set->count()));
/** @var Budget $budget */
foreach ($set as $budget) {
$this->resetBudget($budget);
}
}
private function resetGenericTables(UserGroup $userGroup): void
{
$tables = [
// !!! this array is also in the migration
'accounts' => ['native_virtual_balance'],
'available_budgets' => ['native_amount'],
'bills' => ['native_amount_min', 'native_amount_max'],
];
foreach ($tables as $table => $columns) {
Log::debug(sprintf('Now processing table "%s"', $table));
foreach ($columns as $column) {
Log::debug(sprintf('Resetting column "%s" in table "%s".', $column, $table));
DB::table($table)->where('user_group_id', $userGroup->id)->update([$column => null]);
}
}
}
private function resetPiggyBank(PiggyBank $piggyBank): void
{
if ('' !== (string) $piggyBank->native_target_amount) {
Log::debug(sprintf('Resetting native_target_amount for piggy bank #%d.', $piggyBank->id));
$piggyBank->native_target_amount = null;
$piggyBank->saveQuietly();
}
foreach ($piggyBank->accounts as $account) {
if ('' !== (string) $account->pivot->native_current_amount) {
Log::debug(sprintf('Resetting native_current_amount for piggy bank #%d and account #%d.', $piggyBank->id, $account->id));
$account->pivot->native_current_amount = null;
$account->pivot->save();
}
}
foreach ($piggyBank->piggyBankEvents as $event) {
if ('' !== (string) $event->native_amount) {
Log::debug(sprintf('Resetting native_amount for piggy bank #%d and event #%d.', $piggyBank->id, $event->id));
$event->native_amount = null;
$event->saveQuietly();
}
}
}
private function resetPiggyBanks(UserGroup $userGroup): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$piggyBanks = $repository->getPiggyBanks();
Log::debug(sprintf('Reset primary currency of %d piggy bank(s).', $piggyBanks->count()));
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
$this->resetPiggyBank($piggyBank);
}
}
private function resetTransactions(UserGroup $userGroup): void
{
// custom query because of the potential size of this update.
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')->orWhereNotNull('native_foreign_amount');
})
->update(['native_amount' => null, 'native_foreign_amount' => null])
;
Log::debug(sprintf('Reset %d transactions.', $success));
}
}
+6 -6
View File
@@ -11,16 +11,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [Issue 11964](https://github.com/firefly-iii/firefly-iii/issues/11964) ("Left to spend" is not taking into account non-main currency withdrawals (when displaying in primary currency)) reported by @absdjfh
- [Issue 11966](https://github.com/firefly-iii/firefly-iii/issues/11966) (Error when trying to export data in CSV (Export data via front end)) reported by @jgmm81
- #11969
- #11974
- #11977
- #11978
- #11982
- [Issue 11969](https://github.com/firefly-iii/firefly-iii/issues/11969) (Problem found when editing a multi-currency record, as well as details in the "Audit log entries") reported by @jgmm81
- [PR 11974](https://github.com/firefly-iii/firefly-iii/pull/11974) (Fix typo in SMTP server comment in .env.example) reported by @NorskNoobing
- [Discussion 11977](https://github.com/orgs/firefly-iii/discussions/11977) (CSP header `form-action 'self'` prevents form submission because it's a redirect) started by @superrio0187
- [Issue 11978](https://github.com/firefly-iii/firefly-iii/issues/11978) (Tags not associated with any record display incorrect information) reported by @jgmm81
- [Issue 11982](https://github.com/firefly-iii/firefly-iii/issues/11982) (Foreign currency account value in primary currency does not update after changing exchange rates) reported by @gattacus
- Remove old `zoomLevel` / `zoom_level` database references for tags, since they are no longer queries anyway.
### API
- #11976
- [Issue 11976](https://github.com/firefly-iii/firefly-iii/issues/11976) (New lines are removed from rule description when created using API POST) reported by @AlexRNL
## v6.5.6 - 2026-03-16
Generated
+9 -9
View File
@@ -5781,27 +5781,27 @@
},
{
"name": "rcrowe/twigbridge",
"version": "v0.14.6",
"version": "v0.14.7",
"source": {
"type": "git",
"url": "https://github.com/rcrowe/TwigBridge.git",
"reference": "0798ee4b5e5b943d0200850acaa87ccd82e2fe45"
"reference": "03a767c8d5c1d74d5f14e9fc754619a271822663"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/0798ee4b5e5b943d0200850acaa87ccd82e2fe45",
"reference": "0798ee4b5e5b943d0200850acaa87ccd82e2fe45",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/03a767c8d5c1d74d5f14e9fc754619a271822663",
"reference": "03a767c8d5c1d74d5f14e9fc754619a271822663",
"shasum": ""
},
"require": {
"illuminate/support": "^9|^10|^11|^12",
"illuminate/view": "^9|^10|^11|^12",
"illuminate/support": "^9|^10|^11|^12|^13",
"illuminate/view": "^9|^10|^11|^12|^13",
"php": "^8.1",
"twig/twig": "~3.21"
},
"require-dev": {
"ext-json": "*",
"laravel/framework": "^9|^10|^11|^12",
"laravel/framework": "^9|^10|^11|^12|^13",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^8.5.8 || ^9.3.7 || ^10.0 || ^11.0 || ^12.0",
"squizlabs/php_codesniffer": "^3.6"
@@ -5847,9 +5847,9 @@
],
"support": {
"issues": "https://github.com/rcrowe/TwigBridge/issues",
"source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.6"
"source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.7"
},
"time": "2025-08-20T11:25:49+00:00"
"time": "2026-03-20T16:59:18+00:00"
},
{
"name": "spatie/backtrace",
+1 -1
View File
@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-03-20',
'build_time' => 1773991310,
'build_time' => 1774047487,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
+3 -3
View File
@@ -7143,9 +7143,9 @@
}
},
"node_modules/i18next": {
"version": "25.8.20",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.20.tgz",
"integrity": "sha512-xjo9+lbX/P1tQt3xpO2rfJiBppNfUnNIPKgCvNsTKsvTOCro1Qr/geXVg1N47j5ScOSaXAPq8ET93raK3Rr06A==",
"version": "25.9.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.9.0.tgz",
"integrity": "sha512-mJ4rVRNWOTkqh5xnaGR6iMFT5vEw3Y2MTJhcjinR/7u8cRv6dAfC0ofuePh5fVPxoh395p6JdrJTStCcNW66gg==",
"funding": [
{
"type": "individual",
+3 -3
View File
@@ -658,9 +658,9 @@ Route::group(
// transaction currency API routes that require admin rights:
Route::group(
[
'namespace' => 'FireflyIII\Api\V1\Controllers\Models\TransactionCurrency',
'prefix' => 'v1/currencies',
'as' => 'api.v1.currencies.',
'namespace' => 'FireflyIII\Api\V1\Controllers\Models\TransactionCurrency',
'prefix' => 'v1/currencies',
'as' => 'api.v1.currencies.',
'middleware' => ['api-admin'],
],
static function (): void {