diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php index 631657a9ed..b3354b203d 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php @@ -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() diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/StoreController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/StoreController.php index eea84c3760..a26cc94722 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/StoreController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/StoreController.php @@ -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); diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php index f85876095e..5746e5ed3e 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php @@ -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); } diff --git a/app/Events/Model/CurrencyExchangeRate/CreatedCurrencyExchangeRate.php b/app/Events/Model/CurrencyExchangeRate/CreatedCurrencyExchangeRate.php index db8d364025..ba08d0e675 100644 --- a/app/Events/Model/CurrencyExchangeRate/CreatedCurrencyExchangeRate.php +++ b/app/Events/Model/CurrencyExchangeRate/CreatedCurrencyExchangeRate.php @@ -1,4 +1,7 @@ id)); } } diff --git a/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php b/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php index 60d406c1e8..8b20ac67d6 100644 --- a/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php +++ b/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php @@ -1,4 +1,7 @@ code, $to->code)); } } diff --git a/app/Events/Model/CurrencyExchangeRate/UpdatedCurrencyExchangeRate.php b/app/Events/Model/CurrencyExchangeRate/UpdatedCurrencyExchangeRate.php index a0abd436bb..563024c54d 100644 --- a/app/Events/Model/CurrencyExchangeRate/UpdatedCurrencyExchangeRate.php +++ b/app/Events/Model/CurrencyExchangeRate/UpdatedCurrencyExchangeRate.php @@ -1,4 +1,7 @@ id)); } } diff --git a/app/Listeners/Model/Account/UpdatesAccountInformation.php b/app/Listeners/Model/Account/UpdatesAccountInformation.php index ba613199e2..1f5f1160da 100644 --- a/app/Listeners/Model/Account/UpdatesAccountInformation.php +++ b/app/Listeners/Model/Account/UpdatesAccountInformation.php @@ -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(); diff --git a/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php b/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php index f090b0fc64..91956bba9c 100644 --- a/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php +++ b/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php @@ -1,4 +1,7 @@ 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.'); - } - } diff --git a/app/Listeners/System/RecalculatesPrimaryCurrencyAmounts.php b/app/Listeners/System/RecalculatesPrimaryCurrencyAmounts.php index 811cac0370..26db42d757 100644 --- a/app/Listeners/System/RecalculatesPrimaryCurrencyAmounts.php +++ b/app/Listeners/System/RecalculatesPrimaryCurrencyAmounts.php @@ -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.'); } - - - - } diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 1b78fced38..a081c244ac 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -58,6 +58,7 @@ class CurrencyExchangeRate extends Model { return $this->belongsTo(User::class); } + public function userGroup(): BelongsTo { return $this->belongsTo(UserGroup::class); diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 0c50162b78..a7dd131635 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -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() + ; } /** diff --git a/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php b/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php index 4d377b858a..1adc513a8e 100644 --- a/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php +++ b/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php @@ -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)); + } } diff --git a/changelog.md b/changelog.md index ed9708fe6a..04f383e025 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/composer.lock b/composer.lock index bc88a420ef..0209076051 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/config/firefly.php b/config/firefly.php index 3497d0f11d..fa3ba45cb6 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -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. diff --git a/package-lock.json b/package-lock.json index 686dfaf6aa..987a07caab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/routes/api.php b/routes/api.php index 3452ca11a6..6b9b2de127 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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 {