diff --git a/app/Events/Model/Account/Updated.php b/app/Events/Model/Account/Updated.php new file mode 100644 index 0000000000..c73b939954 --- /dev/null +++ b/app/Events/Model/Account/Updated.php @@ -0,0 +1,39 @@ +account = $account; + } +} diff --git a/app/Handlers/Observer/AccountObserver.php b/app/Handlers/Observer/AccountObserver.php index 3e48547c8a..36f27f56d5 100644 --- a/app/Handlers/Observer/AccountObserver.php +++ b/app/Handlers/Observer/AccountObserver.php @@ -26,6 +26,9 @@ namespace FireflyIII\Handlers\Observer; use FireflyIII\Models\Account; use FireflyIII\Models\PiggyBank; +use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; +use Illuminate\Support\Facades\Log; /** * Class AccountObserver @@ -53,4 +56,37 @@ class AccountObserver $account->notes()->delete(); $account->locations()->delete(); } + + public function created(Account $account): void + { + Log::debug('Observe "created" of an account.'); + $this->updateNativeAmount($account); + } + + public function updated(Account $account): void + { + Log::debug('Observe "updated" of an account.'); + $this->updateNativeAmount($account); + } + + private function updateNativeAmount(Account $account): void + { + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); + $repository = app(AccountRepositoryInterface::class); + $currency = $repository->getAccountCurrency($account); + if (null !== $currency && $currency->id !== $userCurrency->id && '' !== (string) $account->virtual_balance && 0 !== bccomp($account->virtual_balance, '0')) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $account->native_virtual_balance = $converter->convert($currency, $userCurrency, today(), $account->virtual_balance); + + } + if ('' === (string) $account->virtual_balance || ('' !== (string) $account->virtual_balance && 0 === bccomp($account->virtual_balance, '0'))) { + $account->virtual_balance = null; + $account->native_virtual_balance = null; + } + $account->saveQuietly(); + Log::debug('Account native virtual balance is updated.'); + } + + } diff --git a/app/Handlers/Observer/AutoBudgetObserver.php b/app/Handlers/Observer/AutoBudgetObserver.php new file mode 100644 index 0000000000..02b6972e89 --- /dev/null +++ b/app/Handlers/Observer/AutoBudgetObserver.php @@ -0,0 +1,58 @@ +updateNativeAmount($autoBudget); + } + + public function created(AutoBudget $autoBudget): void + { + Log::debug('Observe "created" of an auto budget.'); + $this->updateNativeAmount($autoBudget); + } + + private function updateNativeAmount(AutoBudget $autoBudget): void + { + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($autoBudget->budget->user->userGroup); + $autoBudget->native_amount =null; + if ($autoBudget->transactionCurrency->id !== $userCurrency->id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $autoBudget->native_amount = $converter->convert($autoBudget->transactionCurrency, $userCurrency, today(), $autoBudget->amount); + } + $autoBudget->saveQuietly(); + Log::debug('Auto budget native amount is updated.'); + } + + +} diff --git a/app/Handlers/Observer/AvailableBudgetObserver.php b/app/Handlers/Observer/AvailableBudgetObserver.php new file mode 100644 index 0000000000..1fe0fa1e1b --- /dev/null +++ b/app/Handlers/Observer/AvailableBudgetObserver.php @@ -0,0 +1,59 @@ +updateNativeAmount($availableBudget); + } + + public function created(AvailableBudget $availableBudget): void + { + Log::debug('Observe "created" of an available budget.'); + $this->updateNativeAmount($availableBudget); + } + + private function updateNativeAmount(AvailableBudget $availableBudget): void + { + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($availableBudget->user->userGroup); + $availableBudget->native_amount = null; + if ($availableBudget->transactionCurrency->id !== $userCurrency->id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $availableBudget->native_amount = $converter->convert($availableBudget->transactionCurrency, $userCurrency, today(), $availableBudget->amount); + } + $availableBudget->saveQuietly(); + Log::debug('Available budget native amount is updated.'); + } + + +} diff --git a/app/Handlers/Observer/BillObserver.php b/app/Handlers/Observer/BillObserver.php index 5d93e8609f..00045a1dbc 100644 --- a/app/Handlers/Observer/BillObserver.php +++ b/app/Handlers/Observer/BillObserver.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Handlers\Observer; use FireflyIII\Models\Bill; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; +use Illuminate\Support\Facades\Log; /** * Class BillObserver @@ -38,4 +40,31 @@ class BillObserver } $bill->notes()->delete(); } + + public function updated(Bill $bill): void + { + Log::debug('Observe "updated" of a bill.'); + $this->updateNativeAmount($bill); + } + + public function created(Bill $bill): void + { + Log::debug('Observe "created" of a bill.'); + $this->updateNativeAmount($bill); + } + + private function updateNativeAmount(Bill $bill): void + { + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($bill->user->userGroup); + $bill->native_amount_min = null; + $bill->native_amount_max = null; + if ($bill->transactionCurrency->id !== $userCurrency->id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $bill->native_amount_min = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_min); + $bill->native_amount_max = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_max); + } + $bill->saveQuietly(); + Log::debug('Bill native amounts are updated.'); + } } diff --git a/app/Handlers/Observer/BudgetLimitObserver.php b/app/Handlers/Observer/BudgetLimitObserver.php new file mode 100644 index 0000000000..4e0b6a2606 --- /dev/null +++ b/app/Handlers/Observer/BudgetLimitObserver.php @@ -0,0 +1,56 @@ +updateNativeAmount($budgetLimit); + } + + public function created(BudgetLimit $budgetLimit): void + { + Log::debug('Observe "created" of a budget limit.'); + $this->updateNativeAmount($budgetLimit); + } + + private function updateNativeAmount(BudgetLimit $budgetLimit): void + { + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($budgetLimit->budget->user->userGroup); + $budgetLimit->native_amount = null; + if ($budgetLimit->transactionCurrency->id !== $userCurrency->id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $budgetLimit->native_amount = $converter->convert($budgetLimit->transactionCurrency, $userCurrency, today(), $budgetLimit->amount); + } + $budgetLimit->saveQuietly(); + Log::debug('Bill native amounts are updated.'); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 74eb677e5d..afcc11eacb 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -59,6 +59,8 @@ use FireflyIII\Events\UserChangedEmail; use FireflyIII\Events\WarnUserAboutBill; use FireflyIII\Handlers\Observer\AccountObserver; use FireflyIII\Handlers\Observer\AttachmentObserver; +use FireflyIII\Handlers\Observer\AutoBudgetObserver; +use FireflyIII\Handlers\Observer\AvailableBudgetObserver; use FireflyIII\Handlers\Observer\BillObserver; use FireflyIII\Handlers\Observer\BudgetObserver; use FireflyIII\Handlers\Observer\CategoryObserver; @@ -75,6 +77,8 @@ use FireflyIII\Handlers\Observer\WebhookMessageObserver; use FireflyIII\Handlers\Observer\WebhookObserver; use FireflyIII\Models\Account; use FireflyIII\Models\Attachment; +use FireflyIII\Models\AutoBudget; +use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Bill; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; @@ -262,6 +266,8 @@ class EventServiceProvider extends ServiceProvider Attachment::observe(new AttachmentObserver()); PiggyBank::observe(new PiggyBankObserver()); Account::observe(new AccountObserver()); + AvailableBudget::observe(new AvailableBudgetObserver()); + AutoBudget::observe(new AutoBudgetObserver()); Bill::observe(new BillObserver()); Budget::observe(new BudgetObserver()); Category::observe(new CategoryObserver()); diff --git a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php index 7194993495..c5b9c2af58 100644 --- a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php +++ b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php @@ -33,7 +33,7 @@ use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; -use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; use Illuminate\Support\Facades\Log; /** @@ -59,9 +59,19 @@ trait ModifiesPiggyBanks public function removeAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool { - $currentAmount = $this->getCurrentAmount($piggyBank, $account); - $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot; - $pivot->current_amount = bcsub($currentAmount, $amount); + $currentAmount = $this->getCurrentAmount($piggyBank, $account); + $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot; + $pivot->current_amount = bcsub($currentAmount, $amount); + $pivot->native_current_amount = null; + + // also update native_current_amount. + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); + if ($userCurrency->id !== $piggyBank->transaction_currency_id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); + } + $pivot->save(); Log::debug('ChangedAmount: removeAmount [a]: Trigger change for negative amount.'); @@ -93,6 +103,16 @@ trait ModifiesPiggyBanks $currentAmount = $this->getCurrentAmount($piggyBank, $account); $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot; $pivot->current_amount = bcadd($currentAmount, $amount); + $pivot->native_current_amount = null; + + // also update native_current_amount. + $userCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); + if ($userCurrency->id !== $piggyBank->transaction_currency_id) { + $converter = new ExchangeRateConverter(); + $converter->setIgnoreSettings(true); + $pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); + } + $pivot->save(); Log::debug('ChangedAmount: addAmount [b]: Trigger change for positive amount.'); @@ -120,8 +140,8 @@ trait ModifiesPiggyBanks Log::debug(sprintf('Maximum amount: %s', $maxAmount)); } - $compare = bccomp($amount, $maxAmount); - $result = $compare <= 0; + $compare = bccomp($amount, $maxAmount); + $result = $compare <= 0; Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true))); @@ -155,11 +175,11 @@ trait ModifiesPiggyBanks public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank { - $repetition = $this->getRepetition($piggyBank); + $repetition = $this->getRepetition($piggyBank); if (null === $repetition) { return $piggyBank; } - $max = $piggyBank->target_amount; + $max = $piggyBank->target_amount; if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) { $amount = $max; } @@ -202,16 +222,15 @@ trait ModifiesPiggyBanks public function setOrder(PiggyBank $piggyBank, int $newOrder): bool { - $oldOrder = $piggyBank->order; + $oldOrder = $piggyBank->order; // Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); if ($newOrder > $oldOrder) { PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') - ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') - ->where('accounts.user_id', $this->user->id) - ->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) - ->where('piggy_banks.id', '!=', $piggyBank->id) - ->distinct()->decrement('piggy_banks.order') - ; + ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') + ->where('accounts.user_id', $this->user->id) + ->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) + ->where('piggy_banks.id', '!=', $piggyBank->id) + ->distinct()->decrement('piggy_banks.order'); $piggyBank->order = $newOrder; Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); @@ -220,12 +239,11 @@ trait ModifiesPiggyBanks return true; } PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') - ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') - ->where('accounts.user_id', $this->user->id) - ->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) - ->where('piggy_banks.id', '!=', $piggyBank->id) - ->distinct()->increment('piggy_banks.order') - ; + ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') + ->where('accounts.user_id', $this->user->id) + ->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) + ->where('piggy_banks.id', '!=', $piggyBank->id) + ->distinct()->increment('piggy_banks.order'); $piggyBank->order = $newOrder; Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); @@ -242,7 +260,7 @@ trait ModifiesPiggyBanks return; } - $dbNote = $piggyBank->notes()->first(); + $dbNote = $piggyBank->notes()->first(); if (null === $dbNote) { $dbNote = new Note(); $dbNote->noteable()->associate($piggyBank); @@ -253,14 +271,14 @@ trait ModifiesPiggyBanks public function update(PiggyBank $piggyBank, array $data): PiggyBank { - $piggyBank = $this->updateProperties($piggyBank, $data); + $piggyBank = $this->updateProperties($piggyBank, $data); if (array_key_exists('notes', $data)) { - $this->updateNote($piggyBank, (string)$data['notes']); + $this->updateNote($piggyBank, (string) $data['notes']); } // update the order of the piggy bank: - $oldOrder = $piggyBank->order; - $newOrder = (int)($data['order'] ?? $oldOrder); + $oldOrder = $piggyBank->order; + $newOrder = (int) ($data['order'] ?? $oldOrder); if ($oldOrder !== $newOrder) { $this->setOrder($piggyBank, $newOrder); } @@ -289,7 +307,7 @@ trait ModifiesPiggyBanks // update using name: if (array_key_exists('object_group_title', $data)) { - $objectGroupTitle = (string)$data['object_group_title']; + $objectGroupTitle = (string) $data['object_group_title']; if ('' !== $objectGroupTitle) { $objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle); if (null !== $objectGroup) { @@ -305,7 +323,7 @@ trait ModifiesPiggyBanks // try also with ID: if (array_key_exists('object_group_id', $data)) { - $objectGroupId = (int)($data['object_group_id'] ?? 0); + $objectGroupId = (int) ($data['object_group_id'] ?? 0); if (0 !== $objectGroupId) { $objectGroup = $this->findObjectGroupById($objectGroupId); if (null !== $objectGroup) { diff --git a/app/Support/Http/Api/ExchangeRateConverter.php b/app/Support/Http/Api/ExchangeRateConverter.php index 1b1ddc4465..de095dd0ce 100644 --- a/app/Support/Http/Api/ExchangeRateConverter.php +++ b/app/Support/Http/Api/ExchangeRateConverter.php @@ -43,10 +43,16 @@ class ExchangeRateConverter private bool $noPreparedRates = false; private array $prepared = []; private int $queryCount = 0; + private bool $ignoreSettings = false; + + public function setIgnoreSettings(bool $ignoreSettings): void + { + $this->ignoreSettings = $ignoreSettings; + } public function enabled(): bool { - return false !== config('cer.enabled'); + return false !== config('cer.enabled') || true === $this->ignoreSettings; } /** diff --git a/database/migrations/2024_12_19_061003_add_native_amount_column.php b/database/migrations/2024_12_19_061003_add_native_amount_column.php index 33973d07a6..35c6928742 100644 --- a/database/migrations/2024_12_19_061003_add_native_amount_column.php +++ b/database/migrations/2024_12_19_061003_add_native_amount_column.php @@ -6,15 +6,18 @@ use Illuminate\Support\Facades\Schema; return new class extends Migration { private array $tables = [ - 'accounts' => ['native_virtual_balance'], - 'account_piggy_bank' => ['native_current_amount'], - 'auto_budgets' => ['native_amount'], - 'available_budgets' => ['native_amount'], - 'bills' => ['native_amount_min', 'native_amount_max'], - 'budget_limits' => ['native_amount'], + 'accounts' => ['native_virtual_balance'], // works. + 'account_piggy_bank' => ['native_current_amount'], // works + 'auto_budgets' => ['native_amount'], // works + 'available_budgets' => ['native_amount'], // works + 'bills' => ['native_amount_min', 'native_amount_max'], // works + 'budget_limits' => ['native_amount'], // works 'piggy_bank_events' => ['native_amount'], 'piggy_banks' => ['native_target_amount'], - 'transactions' => ['native_amount'], + 'transactions' => ['native_amount', 'native_foreign_amount'], + + // TODO native currency changes, reset everything. + // button to recalculate all native amounts on selected pages? ];