diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php index b3354b203d..48bad03ae5 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php @@ -59,8 +59,9 @@ final class DestroyController extends Controller public function destroy(DestroyRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse { + $first = Carbon::create(1970,1,1); $this->repository->deleteRates($from, $to); - event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request))); + event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $first)); return response()->json([], 204); } @@ -74,7 +75,7 @@ final class DestroyController extends Controller if (!$exchangeRate instanceof CurrencyExchangeRate) { throw new FireflyException('Bla'); } - event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request))); + event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $date)); return response()->json([], 204); } @@ -85,7 +86,7 @@ final class DestroyController extends Controller $to = $exchangeRate->toCurrency; $this->repository->deleteRate($exchangeRate); - event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request))); + event(new DestroyedCurrencyExchangeRate($from, $to, $this->validateUserGroup($request), $exchangeRate->date)); return response()->json([], 204); } diff --git a/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php b/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php index 8b20ac67d6..ba3562243f 100644 --- a/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php +++ b/app/Events/Model/CurrencyExchangeRate/DestroyedCurrencyExchangeRate.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Events\Model\CurrencyExchangeRate; +use Carbon\Carbon; use FireflyIII\Events\Event; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\UserGroup; @@ -37,7 +38,8 @@ class DestroyedCurrencyExchangeRate extends Event public function __construct( public TransactionCurrency $from, public TransactionCurrency $to, - public UserGroup $userGroup + public UserGroup $userGroup, + public Carbon $date ) { Log::debug(sprintf('DestroyedCurrencyExchangeRate(%s, %s) Event', $from->code, $to->code)); } diff --git a/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php b/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php index 91956bba9c..4fad6786e0 100644 --- a/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php +++ b/app/Listeners/Model/CurrencyExchangeRate/ProcessesExchangeRates.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Listeners\Model\CurrencyExchangeRate; +use Carbon\Carbon; use FireflyIII\Events\Model\CurrencyExchangeRate\CreatedCurrencyExchangeRate; use FireflyIII\Events\Model\CurrencyExchangeRate\DestroyedCurrencyExchangeRate; use FireflyIII\Events\Model\CurrencyExchangeRate\UpdatedCurrencyExchangeRate; @@ -42,20 +43,22 @@ class ProcessesExchangeRates Preferences::mark(); Cache::clear(); if ($event instanceof DestroyedCurrencyExchangeRate) { - $this->handleCurrency($event->userGroup, $event->from); - $this->handleCurrency($event->userGroup, $event->to); + $this->handleCurrency($event->userGroup, $event->from, $event->date); + $this->handleCurrency($event->userGroup, $event->to, $event->date); return; } - $this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency); - $this->handleCurrency($event->rate->userGroup, $event->rate->toCurrency); + $this->handleCurrency($event->rate->userGroup, $event->rate->fromCurrency, $event->rate->date); + $this->handleCurrency($event->rate->userGroup, $event->rate->toCurrency, $event->rate->date); } - private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency): void + private function handleCurrency(UserGroup $userGroup, TransactionCurrency $currency, Carbon $date): void { $calculator = new PrimaryAmountRecalculationService(); + $calculator->setDate($date); if (Amount::convertToPrimary()) { - Log::debug(sprintf('Will now convert amounts to primary currency for currency %s.', $currency->code)); + $date->startOfDay(); + Log::debug(sprintf('Will now convert amounts to primary currency for currency %s after %s.', $currency->code, $date->format('Y-m-d'))); $calculator->recalculateForGroupAndCurrency($userGroup, $currency); // $calculator->recalculateForGroup($userGroup); diff --git a/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php b/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php index 0a0e0d48a5..2bb45bd5b2 100644 --- a/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php +++ b/app/Services/Internal/Recalculate/PrimaryAmountRecalculationService.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Recalculate; +use Carbon\Carbon; use FireflyIII\Events\Model\Account\UpdatedExistingAccount; use FireflyIII\Handlers\Observer\TransactionObserver; use FireflyIII\Models\Account; @@ -52,6 +53,13 @@ use Illuminate\Support\Facades\Log; class PrimaryAmountRecalculationService { + private Carbon $date; + + public function __construct() + { + $this->date = Carbon::createFromDate(1970, 1, 1); + } + public function recalculate(): void { if (false === FireflyConfig::get('enable_exchange_rates', config('cer.enabled'))->data) { @@ -110,22 +118,22 @@ class PrimaryAmountRecalculationService { // 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('transaction_journals.date', '>=', $this->date) + ->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) { @@ -145,20 +153,20 @@ 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('transaction_journals.date', '>=', $this->date) + ->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']) - ; + ->where('transactions.transaction_currency_id', $limitCurrency->id) + ->orWhere('transactions.foreign_currency_id', $limitCurrency->id) + ->get(['transactions.id']); TransactionObserver::$recalculate = false; Log::debug(sprintf('Count of set is %d', $set->count())); foreach ($set as $item) { @@ -188,8 +196,7 @@ class PrimaryAmountRecalculationService $q->orWhere('virtual_balance', '!=', ''); } }) - ->get() - ; + ->get(); } /** @@ -202,7 +209,7 @@ 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)); @@ -225,7 +232,7 @@ 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)); @@ -279,7 +286,12 @@ class PrimaryAmountRecalculationService private function recalculateBudgetLimits(Budget $budget, TransactionCurrency $currency): void { - $set = $budget->budgetlimits()->where('transaction_currency_id', '!=', $currency->id)->get(); + $set = $budget->budgetlimits() + ->where(function (EloquentBuilder $q) { + $q->where('budget_limits.start_date', '>=', $this->date); + $q->orWhere('budget_limits.end_date', '<=', $this->date); + }) + ->where('transaction_currency_id', '!=', $currency->id)->get(); /** @var BudgetLimit $limit */ foreach ($set as $limit) { @@ -316,25 +328,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(); @@ -347,7 +359,7 @@ class PrimaryAmountRecalculationService private function resetBudget(Budget $budget): void { foreach ($budget->autoBudgets as $autoBudget) { - if ('' === (string) $autoBudget->native_amount) { + if ('' === (string)$autoBudget->native_amount) { continue; } Log::debug(sprintf('Resetting native_amount for budget #%d and auto budget #%d.', $budget->id, $autoBudget->id)); @@ -355,7 +367,7 @@ class PrimaryAmountRecalculationService $autoBudget->saveQuietly(); } foreach ($budget->budgetlimits as $limit) { - if ('' !== (string) $limit->native_amount) { + 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(); @@ -367,7 +379,7 @@ class PrimaryAmountRecalculationService { $repository = app(BudgetRepositoryInterface::class); $repository->setUserGroup($userGroup); - $set = $repository->getBudgets(); + $set = $repository->getBudgets(); Log::debug(sprintf('Reset primary currency of %d budget(s).', $set->count())); @@ -396,20 +408,20 @@ class PrimaryAmountRecalculationService private function resetPiggyBank(PiggyBank $piggyBank): void { - if ('' !== (string) $piggyBank->native_target_amount) { + 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) { + 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) { + 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(); @@ -434,13 +446,19 @@ class PrimaryAmountRecalculationService { // 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]) - ; + ->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.user_group_id', $userGroup->id) + ->where('transaction_journals.date', '>=', $this->date) + ->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)); } + + public function setDate(?Carbon $date): void + { + $this->date = $date; + } + }