From 6ada5fa560491b81b897f2b0240cc842a516863f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 4 Feb 2025 19:43:53 +0100 Subject: [PATCH] Fix #9751 --- app/Support/Steam.php | 233 +++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 115 deletions(-) diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 6616f5aebf..cafabdc33f 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -28,10 +28,11 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; -use FireflyIII\Support\Facades\Amount; /** * Class Steam. @@ -40,8 +41,8 @@ class Steam { public function getAccountCurrency(Account $account): ?TransactionCurrency { - $type = $account->accountType->type; - $list = config('firefly.valid_currency_account_types'); + $type = $account->accountType->type; + $list = config('firefly.valid_currency_account_types'); // return null if not in this list. if (!in_array($type, $list, true)) { @@ -63,7 +64,7 @@ class Steam Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); // set up cache - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('final-balance-in-range'); $cache->addProperty($start); @@ -72,66 +73,65 @@ class Steam return $cache->get(); } - $balances = []; - $formatted = $start->format('Y-m-d'); - $startBalance = $this->finalAccountBalance($account, $start); - $defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup); - $accountCurrency = $this->getAccountCurrency($account); - $hasCurrency = null !== $accountCurrency; - $currency = $accountCurrency ?? $defaultCurrency; + $balances = []; + $formatted = $start->format('Y-m-d'); + $startBalance = $this->finalAccountBalance($account, $start); + $defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup); + $accountCurrency = $this->getAccountCurrency($account); + $hasCurrency = null !== $accountCurrency; + $currency = $accountCurrency ?? $defaultCurrency; Log::debug(sprintf('Currency is %s', $currency->code)); if (!$hasCurrency) { Log::debug(sprintf('Also set start balance in %s', $defaultCurrency->code)); $startBalance[$defaultCurrency->code] ??= '0'; } - $currencies = [ + $currencies = [ $currency->id => $currency, $defaultCurrency->id => $defaultCurrency, ]; $startBalance[$currency->code] ??= '0'; - $balances[$formatted] = $startBalance; + $balances[$formatted] = $startBalance; Log::debug('Final start balance: ', $startBalance); // sums up the balance changes per day, for foreign, native and normal amounts. - $set = $account->transactions() - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s')) - ->groupBy('transaction_journals.date') - ->groupBy('transactions.transaction_currency_id') - ->groupBy('transactions.foreign_currency_id') - ->orderBy('transaction_journals.date', 'ASC') - ->whereNull('transaction_journals.deleted_at') - ->get( - [ // @phpstan-ignore-line - 'transaction_journals.date', - 'transactions.transaction_currency_id', - DB::raw('SUM(transactions.amount) AS modified'), - 'transactions.foreign_currency_id', - DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), - DB::raw('SUM(transactions.native_amount) AS modified_native'), - ] - ) - ; + $set = $account->transactions() + ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s')) + ->groupBy('transaction_journals.date') + ->groupBy('transactions.transaction_currency_id') + ->groupBy('transactions.foreign_currency_id') + ->orderBy('transaction_journals.date', 'ASC') + ->whereNull('transaction_journals.deleted_at') + ->get( + [ // @phpstan-ignore-line + 'transaction_journals.date', + 'transactions.transaction_currency_id', + DB::raw('SUM(transactions.amount) AS modified'), + 'transactions.foreign_currency_id', + DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), + DB::raw('SUM(transactions.native_amount) AS modified_native'), + ] + ); - $currentBalance = $startBalance; + $currentBalance = $startBalance; /** @var Transaction $entry */ foreach ($set as $entry) { // normal, native and foreign amount - $carbon = new Carbon($entry->date, $entry->date_tz); - $modified = (string) (null === $entry->modified ? '0' : $entry->modified); - $foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign); - $nativeModified = (string) (null === $entry->modified_native ? '0' : $entry->modified_native); + $carbon = new Carbon($entry->date, $entry->date_tz); + $modified = (string) (null === $entry->modified ? '0' : $entry->modified); + $foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign); + $nativeModified = (string) (null === $entry->modified_native ? '0' : $entry->modified_native); // find currency of this entry. $currencies[$entry->transaction_currency_id] ??= TransactionCurrency::find($entry->transaction_currency_id); /** @var TransactionCurrency $entryCurrency */ - $entryCurrency = $currencies[$entry->transaction_currency_id]; + $entryCurrency = $currencies[$entry->transaction_currency_id]; Log::debug(sprintf('Processing transaction(s) on date %s', $carbon->format('Y-m-d H:i:s'))); @@ -163,7 +163,7 @@ class Steam if (!$convertToNative) { Log::debug(sprintf('Amount is %s %s, foreign amount is %s, native amount is %s', $entryCurrency->code, $modified, $foreignModified, $nativeModified)); // add to balance, as expected. - $currentBalance['balance'] = bcadd($currentBalance['balance'] ?? '0', $modified); + $currentBalance['balance'] = bcadd($currentBalance['balance'] ?? '0', $modified); // add to GBP, as expected. $currentBalance[$entryCurrency->code] = bcadd($currentBalance[$entryCurrency->code] ?? '0', $modified); } @@ -205,10 +205,10 @@ class Steam // Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision)); if (str_contains($number, '.')) { if ('-' !== $number[0]) { - return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision); + return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision); } - return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision); + return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision); } return $number; @@ -296,11 +296,11 @@ class Steam */ public function finalAccountBalance(Account $account, Carbon $date): array { - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + //return $cache->get(); } Log::debug(sprintf('Now in finalAccountBalance(#%d, "%s", "%s")', $account->id, $account->name, $date->format('Y-m-d H:i:s'))); @@ -310,61 +310,47 @@ class Steam $accountCurrency = $this->getAccountCurrency($account); $hasCurrency = null !== $accountCurrency; $currency = $hasCurrency ? $accountCurrency : $native; - $return = []; - - // first, the "balance", as described earlier. - if ($convertToNative) { - // normal balance - $return['balance'] = (string) $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) - ->where('transactions.transaction_currency_id', $native->id) - ->sum('transactions.amount') - ; - // plus virtual balance, if the account has a virtual_balance in the native currency - if ($native->id === $accountCurrency?->id) { - $return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']); - } - // Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $this->bcround($return['balance'], 2))); - - // native balance - $return['native_balance'] = (string) $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) - ->whereNot('transactions.transaction_currency_id', $native->id) - ->sum('transactions.native_amount') - ; - // plus native virtual balance. - $return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']); - // Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $this->bcround($return['native_balance']))); - - // plus foreign transactions in THIS currency. - $sum = (string) $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) - ->whereNot('transactions.transaction_currency_id', $native->id) - ->where('transactions.foreign_currency_id', $native->id) - ->sum('transactions.foreign_amount') - ; - $return['native_balance'] = bcadd($return['native_balance'], $sum); - - // Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $this->bcround($sum), $this->bcround($return['native_balance']))); - } - + $return = [ + 'balance' => '0', + 'native_balance' => '0', + ]; // balance(s) in other (all) currencies. - $array = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) - ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() - ; - $others = $this->groupAndSumTransactions($array, 'code', 'amount'); - // Log::debug('All balances are (joined)', $others); - // if the account has no own currency preference, drop balance in favor of native balance - if ($hasCurrency && !$convertToNative) { + $array = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) + ->get(['transaction_currencies.code', 'transactions.amount'])->toArray(); + $others = $this->groupAndSumTransactions($array, 'code', 'amount'); + Log::debug('All balances are (joined)', $others); + // if there is no request to convert, take this as "balance" and "native_balance". + if (!$convertToNative) { $return['balance'] = $others[$currency->code] ?? '0'; $return['native_balance'] = $others[$currency->code] ?? '0'; - // Log::debug(sprintf('Set balance + native_balance to %s', $return['balance'])); + Log::debug(sprintf('Set balance + native_balance to %s', $return['balance'])); + } + // if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency. + if ($convertToNative) { + $return['balance'] = $others[$native->code] ?? '0'; + $return['native_balance'] = $this->convertAllBalances($others, $native, $date);// todo sum all and convert. + Log::debug(sprintf('Set balance to %s and native_balance to %s', $return['balance'], $return['native_balance'])); + } + + // either way, the balance is always combined with the virtual balance: + $virtualBalance = (string) ('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance); + $return['balance'] = bcadd($return['balance'], $virtualBalance); + Log::debug(sprintf('Virtual balance makes the total %s', $return['balance'])); + + if ($convertToNative) { + // the native balance is combined with a converted virtual_balance: + $converter = new ExchangeRateConverter(); + $nativeVirtualBalance = $converter->convert($currency, $native, $date, $virtualBalance); + $return['native_balance'] = bcadd($nativeVirtualBalance, $return['native_balance']); + Log::debug(sprintf('Native virtual balance makes the native total %s', $return['native_balance'])); + } + if (!$convertToNative) { + // if not, also increase the native balance for consistency. + $return['native_balance'] = bcadd($return['native_balance'], $virtualBalance); + Log::debug(sprintf('Virtual balance makes the (native) total %s', $return['balance'])); } // if the currency is the same as the native currency, set the native_balance to the balance for consistency. @@ -372,18 +358,20 @@ class Steam // $return['native_balance'] = $return['balance']; // } - if (!$hasCurrency && array_key_exists('balance', $return)) { - // Log::debug('Account has no currency preference, dropping balance in favor of native balance.'); - $sum = bcadd($return['balance'], $return['native_balance']); - // Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum)); - $return['native_balance'] = $sum; - unset($return['balance']); - } +// if (!$hasCurrency && array_key_exists('balance', $return)) { +// // Log::debug('Account has no currency preference, dropping balance in favor of native balance.'); +// $sum = bcadd($return['balance'], $return['native_balance']); +// // Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum)); +// $return['native_balance'] = $sum; +// unset($return['balance']); +// } $final = array_merge($return, $others); - // Log::debug('Return is', $final); - $cache->store($final); +// // Log::debug('Return is', $final); - return array_merge($return, $others); + $cache->store($final); + return $final; + + //return array_merge($return, $others); // Log::debug('Return is', $final); } @@ -487,15 +475,15 @@ class Steam { $list = []; - $set = auth()->user()->transactions() - ->whereIn('transactions.account_id', $accounts) - ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) - ->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line + $set = auth()->user()->transactions() + ->whereIn('transactions.account_id', $accounts) + ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) + ->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line ; /** @var Transaction $entry */ foreach ($set as $entry) { - $date = new Carbon($entry->max_date, config('app.timezone')); + $date = new Carbon($entry->max_date, config('app.timezone')); $date->setTimezone(config('app.timezone')); $list[(int) $entry->account_id] = $date; } @@ -570,9 +558,9 @@ class Steam public function getSafeUrl(string $unknownUrl, string $safeUrl): string { // Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl)); - $returnUrl = $safeUrl; - $unknownHost = parse_url($unknownUrl, PHP_URL_HOST); - $safeHost = parse_url($safeUrl, PHP_URL_HOST); + $returnUrl = $safeUrl; + $unknownHost = parse_url($unknownUrl, PHP_URL_HOST); + $safeHost = parse_url($safeUrl, PHP_URL_HOST); if (null !== $unknownHost && $unknownHost === $safeHost) { $returnUrl = $unknownUrl; @@ -609,7 +597,7 @@ class Steam */ public function floatalize(string $value): string { - $value = strtoupper($value); + $value = strtoupper($value); if (!str_contains($value, 'E')) { return $value; } @@ -687,4 +675,19 @@ class Steam return $amount; } + + private function convertAllBalances(array $others, TransactionCurrency $native, Carbon $date): string { + $total = '0'; + $converter = new ExchangeRateConverter(); + foreach($others as $key => $amount) { + $currency = TransactionCurrency::where('code', $key)->first(); + if(null === $currency) { + continue; + } + $current = $converter->convert($currency, $native, $date, $amount); + Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $native->code, $current)); + $total = bcadd($current, $total); + } + return $total; + } }