diff --git a/.ci/phpstan.neon b/.ci/phpstan.neon index 272b8604b4..e454c7fc2a 100644 --- a/.ci/phpstan.neon +++ b/.ci/phpstan.neon @@ -1,4 +1,6 @@ parameters: + scanFiles: + - ../_ide_helper_models.php universalObjectCratesClasses: - Illuminate\Database\Eloquent\Model # TODO: slowly remove these parameters and fix the issues found. @@ -10,6 +12,7 @@ parameters: - '#with no value type specified in iterable type array#' # remove this rule when all other issues are solved. - '#has no value type specified in iterable type array#' # remove this rule when all other issues are solved. - '#is not allowed to extend#' + - '#does not specify its types#' - '#switch is forbidden to use#' - '#is neither abstract nor final#' - '#on left side of \?\?\= always exists and is not nullable#' diff --git a/app/Api/V1/Controllers/User/PreferencesController.php b/app/Api/V1/Controllers/User/PreferencesController.php index e947af858e..8ccfa9f27e 100644 --- a/app/Api/V1/Controllers/User/PreferencesController.php +++ b/app/Api/V1/Controllers/User/PreferencesController.php @@ -102,6 +102,7 @@ class PreferencesController extends Controller * TODO This endpoint is not documented. * * Return a single preference by name. + * @param Collection $collection */ public function showList(Collection $collection): JsonResponse { diff --git a/app/Api/V2/Controllers/Chart/BalanceController.php b/app/Api/V2/Controllers/Chart/BalanceController.php index cc6cb0dc47..582e226d97 100644 --- a/app/Api/V2/Controllers/Chart/BalanceController.php +++ b/app/Api/V2/Controllers/Chart/BalanceController.php @@ -48,7 +48,7 @@ class BalanceController extends Controller private AccountRepositoryInterface $repository; private GroupCollectorInterface $collector; private ChartData $chartData; - private TransactionCurrency $default; + // private TransactionCurrency $default; public function __construct() { @@ -61,7 +61,7 @@ class BalanceController extends Controller $this->repository->setUserGroup($userGroup); $this->collector->setUserGroup($userGroup); $this->chartData = new ChartData(); - $this->default = app('amount')->getDefaultCurrency(); + // $this->default = app('amount')->getDefaultCurrency(); return $next($request); } diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php index e62e824bed..2124d72e83 100644 --- a/app/Console/Commands/Correction/FixUnevenAmount.php +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -51,7 +51,7 @@ class FixUnevenAmount extends Command $this->convertOldStyleTransfers(); $this->fixUnevenAmounts(); $this->matchCurrencies(); - AccountBalanceCalculator::recalculateAll(); + AccountBalanceCalculator::forceRecalculateAll(); return 0; } diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php index a6dba4b748..475b7e7f0b 100644 --- a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -43,8 +43,8 @@ class OtherCurrenciesCorrections extends Command use ShowsFriendlyMessages; public const string CONFIG_NAME = '480_other_currencies'; - protected $description = 'Update all journal currency information.'; - protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; + protected $description = 'Update all journal currency information.'; + protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; private array $accountCurrencies; private AccountRepositoryInterface $accountRepos; private JournalCLIRepositoryInterface $cliRepos; @@ -90,7 +90,7 @@ class OtherCurrenciesCorrections extends Command { $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); if (null !== $configVar) { - return (bool)$configVar->data; + return (bool) $configVar->data; } return false; @@ -120,7 +120,7 @@ class OtherCurrenciesCorrections extends Command $this->journalRepos->setUser($journal->user); $this->cliRepos->setUser($journal->user); - $leadTransaction = $this->getLeadTransaction($journal); + $leadTransaction = $this->getLeadTransaction($journal); if (null === $leadTransaction) { $this->friendlyError(sprintf('Could not reliably determine which transaction is in the lead for transaction journal #%d.', $journal->id)); @@ -128,8 +128,9 @@ class OtherCurrenciesCorrections extends Command return; } - $account = $leadTransaction->account; - $currency = $this->getCurrency($account); + $account = $leadTransaction->account; + $currency = $this->getCurrency($account); + $isMultiCurrency = $this->isMultiCurrency($account); if (null === $currency) { $this->friendlyError( sprintf( @@ -145,14 +146,14 @@ class OtherCurrenciesCorrections extends Command } // fix each transaction: $journal->transactions->each( - static function (Transaction $transaction) use ($currency): void { + static function (Transaction $transaction) use ($currency, $isMultiCurrency): void { if (null === $transaction->transaction_currency_id) { $transaction->transaction_currency_id = $currency->id; $transaction->save(); } // when mismatch in transaction: - if ($transaction->transaction_currency_id !== $currency->id) { + if ($transaction->transaction_currency_id !== $currency->id && !$isMultiCurrency) { $transaction->foreign_currency_id = $transaction->transaction_currency_id; $transaction->foreign_amount = $transaction->amount; $transaction->transaction_currency_id = $currency->id; @@ -161,7 +162,9 @@ class OtherCurrenciesCorrections extends Command } ); // also update the journal, of course: - $journal->transaction_currency_id = $currency->id; + if (!$isMultiCurrency) { + $journal->transaction_currency_id = $currency->id; + } ++$this->count; $journal->save(); } @@ -217,14 +220,14 @@ class OtherCurrenciesCorrections extends Command private function getCurrency(Account $account): ?TransactionCurrency { - $accountId = $account->id; + $accountId = $account->id; if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { return null; } if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { return $this->accountCurrencies[$accountId]; } - $currency = $this->accountRepos->getAccountCurrency($account); + $currency = $this->accountRepos->getAccountCurrency($account); if (null === $currency) { $this->accountCurrencies[$accountId] = 0; @@ -239,4 +242,13 @@ class OtherCurrenciesCorrections extends Command { app('fireflyconfig')->set(self::CONFIG_NAME, true); } + + private function isMultiCurrency(Account $account): bool + { + $value = $this->accountRepos->getMetaValue($account, 'is_multi_currency', false); + if (false === $value || null === $value) { + return false; + } + return '1' === $value; + } } diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 74226531f7..52ca3bf42a 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -45,6 +45,11 @@ class Amount return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured); } + public function formatByCurrencyId(int $currencyId, string $amount, ?bool $coloured = null): string { + $format = TransactionCurrency::find($currencyId); + return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured); + } + /** * This method will properly format the given number, in color or "black and white", * as a currency, given two things: the currency required and the current locale. diff --git a/app/Support/Models/AccountBalanceCalculator.php b/app/Support/Models/AccountBalanceCalculator.php index ed21efec17..386a922c8d 100644 --- a/app/Support/Models/AccountBalanceCalculator.php +++ b/app/Support/Models/AccountBalanceCalculator.php @@ -45,6 +45,16 @@ class AccountBalanceCalculator // no-op } + /** + * Recalculate all balances. + */ + public static function forceRecalculateAll(): void + { + Transaction::whereNull('deleted_at')->update(['balance_dirty' => true]); + $object = new self(); + $object->optimizedCalculation(new Collection()); + } + /** * Recalculate all balances. */ diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 3e13ed18de..ed69e351d9 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -25,7 +25,6 @@ namespace FireflyIII\Support; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; -use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; @@ -43,26 +42,24 @@ class Steam public function balanceIgnoreVirtual(Account $account, Carbon $date): string { /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); + $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); - $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $currencyId) - ->get(['transactions.amount'])->toArray() - ; - $nativeBalance = $this->sumTransactions($transactions, 'amount'); + $currencyId = (int) $repository->getMetaValue($account, 'currency_id'); + $transactions = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $currencyId) + ->get(['transactions.amount'])->toArray(); + $nativeBalance = $this->sumTransactions($transactions, 'amount'); // get all balances in foreign currency: - $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.foreign_currency_id', $currencyId) - ->where('transactions.transaction_currency_id', '!=', $currencyId) - ->get(['transactions.foreign_amount'])->toArray() - ; + $transactions = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.foreign_currency_id', $currencyId) + ->where('transactions.transaction_currency_id', '!=', $currencyId) + ->get(['transactions.foreign_amount'])->toArray(); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); @@ -75,7 +72,7 @@ class Steam /** @var array $transaction */ foreach ($transactions as $transaction) { - $value = (string)($transaction[$key] ?? '0'); + $value = (string) ($transaction[$key] ?? '0'); $value = '' === $value ? '0' : $value; $sum = bcadd($sum, $value); } @@ -92,7 +89,7 @@ class Steam */ public function balanceInRange(Account $account, Carbon $start, Carbon $end, ?TransactionCurrency $currency = null): array { - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance-in-range'); $cache->addProperty(null !== $currency ? $currency->id : 0); @@ -104,54 +101,53 @@ class Steam $start->subDay(); $end->addDay(); - $balances = []; - $formatted = $start->format('Y-m-d'); - $startBalance = $this->balance($account, $start, $currency); + $balances = []; + $formatted = $start->format('Y-m-d'); + $startBalance = $this->balance($account, $start, $currency); $balances[$formatted] = $startBalance; if (null === $currency) { $repository = app(AccountRepositoryInterface::class); $repository->setUser($account->user); - $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); + $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); } - $currencyId = $currency->id; + $currencyId = $currency->id; $start->addDay(); // query! - $set = $account->transactions() - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) - ->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'), - ] - ) - ; + $set = $account->transactions() + ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) + ->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'), + ] + ); - $currentBalance = $startBalance; + $currentBalance = $startBalance; /** @var Transaction $entry */ foreach ($set as $entry) { // normal amount and foreign amount - $modified = (string)(null === $entry->modified ? '0' : $entry->modified); - $foreignModified = (string)(null === $entry->modified_foreign ? '0' : $entry->modified_foreign); + $modified = (string) (null === $entry->modified ? '0' : $entry->modified); + $foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign); $amount = '0'; - if ($currencyId === (int)$entry->transaction_currency_id || 0 === $currencyId) { + if ($currencyId === (int) $entry->transaction_currency_id || 0 === $currencyId) { // use normal amount: $amount = $modified; } - if ($currencyId === (int)$entry->foreign_currency_id) { + if ($currencyId === (int) $entry->foreign_currency_id) { // use foreign amount: $amount = $foreignModified; } @@ -167,6 +163,45 @@ class Steam return $balances; } + public function balanceByTransactions(Account $account, Carbon $date, ?TransactionCurrency $currency): array + { + $cache = new CacheProperties(); + $cache->addProperty($account->id); + $cache->addProperty('balance-by-transactions'); + $cache->addProperty($date); + $cache->addProperty(null !== $currency ? $currency->id : 0); + if ($cache->has()) { + return $cache->get(); + } + + $query = $account->transactions() + ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->orderBy('transaction_journals.date', 'desc') + ->orderBy('transaction_journals.order', 'asc') + ->orderBy('transaction_journals.description', 'desc') + ->orderBy('transactions.amount', 'desc'); + if (null !== $currency) { + $query->where('transactions.transaction_currency_id', $currency->id); + $query->limit(1); + $result = $query->get(['transactions.transaction_currency_id', 'transactions.balance_after'])->first(); + $key = (int) $result->transaction_currency_id; + $return = [$key => $result->balance_after]; + $cache->store($return); + return $return; + } + + $return = []; + $result = $query->get(['transactions.transaction_currency_id', 'transactions.balance_after']); + foreach ($result as $entry) { + $key = (int) $entry->transaction_currency_id; + if (array_key_exists($key, $return)) { + continue; + } + $return[$key] = $entry->balance_after; + } + return $return; + } + /** * Gets balance at the end of current month by default * @@ -174,8 +209,10 @@ class Steam */ public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string { + //throw new FireflyException('This method is obsolete.'); + Log::warning('This method is obsolete.'); // abuse chart properties: - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance'); $cache->addProperty($date); @@ -185,26 +222,24 @@ class Steam } /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); + $repository = app(AccountRepositoryInterface::class); if (null === $currency) { $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); } // first part: get all balances in own currency: - $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $currency->id) - ->get(['transactions.amount'])->toArray() - ; - $nativeBalance = $this->sumTransactions($transactions, 'amount'); + $transactions = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $currency->id) + ->get(['transactions.amount'])->toArray(); + $nativeBalance = $this->sumTransactions($transactions, 'amount'); // get all balances in foreign currency: $transactions = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.foreign_currency_id', $currency->id) - ->where('transactions.transaction_currency_id', '!=', $currency->id) - ->get(['transactions.foreign_amount'])->toArray() - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.foreign_currency_id', $currency->id) + ->where('transactions.transaction_currency_id', '!=', $currency->id) + ->get(['transactions.foreign_amount'])->toArray(); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); $balance = bcadd($nativeBalance, $foreignBalance); $virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; @@ -222,7 +257,7 @@ class Steam */ public function balanceInRangeConverted(Account $account, Carbon $start, Carbon $end, TransactionCurrency $native): array { - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance-in-range-converted'); $cache->addProperty($native->id); @@ -242,35 +277,34 @@ class Steam Log::debug(sprintf('Start balance on %s is %s', $formatted, $startBalance)); Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); - $converter = new ExchangeRateConverter(); + $converter = new ExchangeRateConverter(); // not sure why this is happening: $start->addDay(); // grab all transactions between start and end: - $set = $account->transactions() - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) - ->orderBy('transaction_journals.date', 'ASC') - ->whereNull('transaction_journals.deleted_at') - ->get( - [ - 'transaction_journals.date', - 'transactions.transaction_currency_id', - 'transactions.amount', - 'transactions.foreign_currency_id', - 'transactions.foreign_amount', - ] - )->toArray() - ; + $set = $account->transactions() + ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) + ->orderBy('transaction_journals.date', 'ASC') + ->whereNull('transaction_journals.deleted_at') + ->get( + [ + 'transaction_journals.date', + 'transactions.transaction_currency_id', + 'transactions.amount', + 'transactions.foreign_currency_id', + 'transactions.foreign_amount', + ] + )->toArray(); // loop the set and convert if necessary: - $currentBalance = $startBalance; + $currentBalance = $startBalance; /** @var Transaction $transaction */ foreach ($set as $transaction) { - $day = false; + $day = false; try { $day = Carbon::parse($transaction['date'], config('app.timezone')); @@ -280,9 +314,9 @@ class Steam if (false === $day) { $day = today(config('app.timezone')); } - $format = $day->format('Y-m-d'); + $format = $day->format('Y-m-d'); // if the transaction is in the expected currency, change nothing. - if ((int)$transaction['transaction_currency_id'] === $native->id) { + if ((int) $transaction['transaction_currency_id'] === $native->id) { // change the current balance, set it to today, continue the loop. $currentBalance = bcadd($currentBalance, $transaction['amount']); $balances[$format] = $currentBalance; @@ -291,7 +325,7 @@ class Steam continue; } // if foreign currency is in the expected currency, do nothing: - if ((int)$transaction['foreign_currency_id'] === $native->id) { + if ((int) $transaction['foreign_currency_id'] === $native->id) { $currentBalance = bcadd($currentBalance, $transaction['foreign_amount']); $balances[$format] = $currentBalance; Log::debug(sprintf('%s: transaction in %s (foreign), new balance is %s.', $format, $native->code, $currentBalance)); @@ -299,25 +333,25 @@ class Steam continue; } // otherwise, convert 'amount' to the necessary currency: - $currencyId = (int)$transaction['transaction_currency_id']; + $currencyId = (int) $transaction['transaction_currency_id']; $currency = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId); $currencies[$currencyId] = $currency; - $rate = $converter->getCurrencyRate($currency, $native, $day); - $convertedAmount = bcmul($transaction['amount'], $rate); - $currentBalance = bcadd($currentBalance, $convertedAmount); - $balances[$format] = $currentBalance; + $rate = $converter->getCurrencyRate($currency, $native, $day); + $convertedAmount = bcmul($transaction['amount'], $rate); + $currentBalance = bcadd($currentBalance, $convertedAmount); + $balances[$format] = $currentBalance; Log::debug(sprintf( - '%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s', - $format, - $currency->code, - $rate, - $currency->code, - $transaction['amount'], - $native->code, - $convertedAmount - )); + '%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s', + $format, + $currency->code, + $rate, + $currency->code, + $transaction['amount'], + $native->code, + $convertedAmount + )); } $cache->store($balances); @@ -348,7 +382,7 @@ class Steam public function balanceConverted(Account $account, Carbon $date, TransactionCurrency $native): string { Log::debug(sprintf('Now in balanceConverted (%s) for account #%d, converting to %s', $date->format('Y-m-d'), $account->id, $native->code)); - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance'); $cache->addProperty($date); @@ -369,72 +403,66 @@ class Steam return $this->balance($account, $date); } - $new = []; - $existing = []; - $new[] = $account->transactions() // 1 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $currency->id) - ->whereNull('transactions.foreign_currency_id') - ->get(['transaction_journals.date', 'transactions.amount'])->toArray() - ; + $new = []; + $existing = []; + $new[] = $account->transactions() // 1 + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $currency->id) + ->whereNull('transactions.foreign_currency_id') + ->get(['transaction_journals.date', 'transactions.amount'])->toArray(); Log::debug(sprintf('%d transaction(s) in set #1', count($new[0]))); $existing[] = $account->transactions() // 2 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $native->id) - ->whereNull('transactions.foreign_currency_id') - ->get(['transactions.amount'])->toArray() - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $native->id) + ->whereNull('transactions.foreign_currency_id') + ->get(['transactions.amount'])->toArray(); Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0]))); - $new[] = $account->transactions() // 3 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', '!=', $currency->id) - ->where('transactions.transaction_currency_id', '!=', $native->id) - ->whereNull('transactions.foreign_currency_id') - ->get(['transaction_journals.date', 'transactions.amount'])->toArray() - ; + $new[] = $account->transactions() // 3 + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', '!=', $currency->id) + ->where('transactions.transaction_currency_id', '!=', $native->id) + ->whereNull('transactions.foreign_currency_id') + ->get(['transaction_journals.date', 'transactions.amount'])->toArray(); Log::debug(sprintf('%d transactions in set #3', count($new[1]))); $existing[] = $account->transactions() // 4 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.foreign_currency_id', $native->id) - ->whereNotNull('transactions.foreign_amount') - ->get(['transactions.foreign_amount'])->toArray() - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.foreign_currency_id', $native->id) + ->whereNotNull('transactions.foreign_amount') + ->get(['transactions.foreign_amount'])->toArray(); Log::debug(sprintf('%d transactions in set #4', count($existing[1]))); - $new[] = $account->transactions()// 5 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', $currency->id) - ->where('transactions.foreign_currency_id', '!=', $native->id) - ->whereNotNull('transactions.foreign_amount') - ->get(['transaction_journals.date', 'transactions.amount'])->toArray() - ; + $new[] = $account->transactions()// 5 + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', $currency->id) + ->where('transactions.foreign_currency_id', '!=', $native->id) + ->whereNotNull('transactions.foreign_amount') + ->get(['transaction_journals.date', 'transactions.amount'])->toArray(); Log::debug(sprintf('%d transactions in set #5', count($new[2]))); - $new[] = $account->transactions()// 6 - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->where('transactions.transaction_currency_id', '!=', $currency->id) - ->where('transactions.foreign_currency_id', '!=', $native->id) - ->whereNotNull('transactions.foreign_amount') - ->get(['transaction_journals.date', 'transactions.amount'])->toArray() - ; + $new[] = $account->transactions()// 6 + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->where('transactions.transaction_currency_id', '!=', $currency->id) + ->where('transactions.foreign_currency_id', '!=', $native->id) + ->whereNotNull('transactions.foreign_amount') + ->get(['transaction_journals.date', 'transactions.amount'])->toArray(); Log::debug(sprintf('%d transactions in set #6', count($new[3]))); // process both sets of transactions. Of course, no need to convert set "existing". - $balance = $this->sumTransactions($existing[0], 'amount'); - $balance = bcadd($balance, $this->sumTransactions($existing[1], 'foreign_amount')); + $balance = $this->sumTransactions($existing[0], 'amount'); + $balance = bcadd($balance, $this->sumTransactions($existing[1], 'foreign_amount')); Log::debug(sprintf('Balance from set #2 and #4 is %f', $balance)); // need to convert the others. All sets use the "amount" value as their base (that's easy) // but we need to convert each transaction separately because the date difference may // incur huge currency changes. Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); - $start = clone $date; - $end = clone $date; - $converter = new ExchangeRateConverter(); + $start = clone $date; + $end = clone $date; + $converter = new ExchangeRateConverter(); foreach ($new as $set) { foreach ($set as $transaction) { $currentDate = false; @@ -457,7 +485,7 @@ class Steam foreach ($new as $set) { foreach ($set as $transaction) { - $currentDate = false; + $currentDate = false; try { $currentDate = Carbon::parse($transaction['date'], config('app.timezone')); @@ -474,9 +502,9 @@ class Steam } // add virtual balance (also needs conversion) - $virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; - $virtual = $converter->convert($currency, $native, $account->created_at, $virtual); - $balance = bcadd($balance, $virtual); + $virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; + $virtual = $converter->convert($currency, $native, $account->created_at, $virtual); + $balance = bcadd($balance, $virtual); $converter->summarize(); $cache->store($balance); @@ -492,9 +520,9 @@ class Steam */ public function balancesByAccounts(Collection $accounts, Carbon $date): array { - $ids = $accounts->pluck('id')->toArray(); + $ids = $accounts->pluck('id')->toArray(); // cache this property. - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($ids); $cache->addProperty('balances'); $cache->addProperty($date); @@ -522,9 +550,9 @@ class Steam */ public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array { - $ids = $accounts->pluck('id')->toArray(); + $ids = $accounts->pluck('id')->toArray(); // cache this property. - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($ids); $cache->addProperty('balances-converted'); $cache->addProperty($date); @@ -540,9 +568,9 @@ class Steam $default = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); $result[$account->id] = [ - 'balance' => $this->balance($account, $date), - 'native_balance' => $this->balanceConverted($account, $date, $default), - ]; + 'balance' => $this->balance($account, $date), + 'native_balance' => $this->balanceConverted($account, $date, $default), + ]; } $cache->store($result); @@ -555,9 +583,9 @@ class Steam */ public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array { - $ids = $accounts->pluck('id')->toArray(); + $ids = $accounts->pluck('id')->toArray(); // cache this property. - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($ids); $cache->addProperty('balances-per-currency'); $cache->addProperty($date); @@ -581,7 +609,7 @@ class Steam public function balancePerCurrency(Account $account, Carbon $date): array { // abuse chart properties: - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty('balance-per-currency'); $cache->addProperty($date); @@ -589,16 +617,15 @@ class Steam return $cache->get(); } $query = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) - ->groupBy('transactions.transaction_currency_id') - ; + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) + ->groupBy('transactions.transaction_currency_id'); $balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line $return = []; /** @var \stdClass $entry */ foreach ($balances as $entry) { - $return[(int)$entry->transaction_currency_id] = (string)$entry->sum_for_currency; + $return[(int) $entry->transaction_currency_id] = (string) $entry->sum_for_currency; } $cache->store($return); @@ -624,10 +651,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; @@ -703,22 +730,22 @@ class Steam throw new FireflyException($e->getMessage(), 0, $e); } - return (string)$hostName; + return (string) $hostName; } public function getLastActivities(array $accounts): array { $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[$entry->account_id] = $date; } @@ -738,7 +765,7 @@ class Steam if ('equal' === $locale) { $locale = $this->getLanguage(); } - $locale = (string)$locale; + $locale = (string) $locale; // Check for Windows to replace the locale correctly. if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) { @@ -793,9 +820,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; @@ -832,26 +859,26 @@ class Steam */ public function floatalize(string $value): string { - $value = strtoupper($value); + $value = strtoupper($value); if (!str_contains($value, 'E')) { return $value; } Log::debug(sprintf('Floatalizing %s', $value)); - $number = substr($value, 0, (int)strpos($value, 'E')); + $number = substr($value, 0, (int) strpos($value, 'E')); if (str_contains($number, '.')) { - $post = strlen(substr($number, (int)strpos($number, '.') + 1)); - $mantis = substr($value, (int)strpos($value, 'E') + 1); + $post = strlen(substr($number, (int) strpos($number, '.') + 1)); + $mantis = substr($value, (int) strpos($value, 'E') + 1); if ($mantis < 0) { - $post += abs((int)$mantis); + $post += abs((int) $mantis); } // TODO careless float could break financial math. - return number_format((float)$value, $post, '.', ''); + return number_format((float) $value, $post, '.', ''); } // TODO careless float could break financial math. - return number_format((float)$value, 0, '.', ''); + return number_format((float) $value, 0, '.', ''); } public function opposite(?string $amount = null): ?string @@ -871,24 +898,24 @@ class Steam // has a K in it, remove the K and multiply by 1024. $bytes = bcmul(rtrim($string, 'k'), '1024'); - return (int)$bytes; + return (int) $bytes; } if (false !== stripos($string, 'm')) { // has a M in it, remove the M and multiply by 1048576. $bytes = bcmul(rtrim($string, 'm'), '1048576'); - return (int)$bytes; + return (int) $bytes; } if (false !== stripos($string, 'g')) { // has a G in it, remove the G and multiply by (1024)^3. $bytes = bcmul(rtrim($string, 'g'), '1073741824'); - return (int)$bytes; + return (int) $bytes; } - return (int)$string; + return (int) $string; } public function positive(string $amount): string diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index f1f31767f9..03e291bc16 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -64,8 +64,14 @@ class General extends AbstractExtension /** @var Carbon $date */ $date = session('end', today(config('app.timezone'))->endOfMonth()); + $info = app('steam')->balanceByTransactions($account, $date, null); - return app('steam')->balance($account, $date); + $strings = []; + foreach($info as $currencyId => $balance) { + $strings[] = app('amount')->formatByCurrencyId($currencyId, $balance, false); + } + return implode(', ', $strings); + //return app('steam')->balance($account, $date); } ); } diff --git a/resources/views/index.twig b/resources/views/index.twig index 15dcffb703..e9334c9370 100644 --- a/resources/views/index.twig +++ b/resources/views/index.twig @@ -105,7 +105,7 @@