diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index 875774e4de..60bce88e62 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -39,6 +39,8 @@ class ChartJsGenerator implements GeneratorInterface * 'type' => bar or line, optional * 'yAxisID' => ID of yAxis, optional, will not be included when unused. * 'fill' => if to fill a line? optional, will not be included when unused. + * 'currency_symbol' => 'x', + * 'backgroundColor' => 'x', * 'entries' => * [ * 'label-of-entry' => 'value' diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index b9d6b00430..c4a481b655 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -65,7 +65,7 @@ class AccountController extends Controller /** - * Shows the balances for all the user's expense accounts. + * Shows the balances for all the user's expense accounts (on the front page). * * @param AccountRepositoryInterface $repository * @@ -86,23 +86,50 @@ class AccountController extends Controller } $start->subDay(); + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + $currencies = []; + $chartData = [ + [ + 'label' => (string)trans('firefly.spent'), + 'type' => 'bar', + 'currency_symbol' => 'ยค', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red + 'entries' => [], + ], + ]; $accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]); - $startBalances = app('steam')->balancesByAccounts($accounts, $start); - $endBalances = app('steam')->balancesByAccounts($accounts, $end); - $chartData = []; + $accountNames = $this->extractNames($accounts); + $startBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $start); + $endBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $end); + $tempData = []; - foreach ($accounts as $account) { - $id = $account->id; - $startBalance = $startBalances[$id] ?? '0'; - $endBalance = $endBalances[$id] ?? '0'; - $diff = bcsub($endBalance, $startBalance); - if (0 !== bccomp($diff, '0')) { - $chartData[$account->name] = $diff; + + foreach ($endBalances as $accountId => $expenses) { + $accountId = (int)$accountId; + foreach ($expenses as $currencyId => $endAmount) { + $currencyId = (int)$currencyId; + $startAmount = $startBalances[$accountId][$currencyId] ?? '0'; + $diff = bcsub($endAmount, $startAmount); + $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepos->findNull($currencyId); + $title = (string)trans( + 'firefly.account_in_currency', ['account' => $accountNames[$accountId], 'currency' => $currencies[$currencyId]->name] + ); + $tempData[$title] = $diff; } } + arsort($tempData, SORT_NUMERIC); - arsort($chartData); - $data = $this->generator->singleSet((string)trans('firefly.spent'), $chartData); + foreach ($tempData as $label => $entry) { + if (0 !== bccomp($entry, '0')) { + $chartData[0]['entries'][$label] = $entry; + } + } + if (1 === \count($currencies)) { + $first = array_first($currencies); + $chartData[0]['currency_symbol'] = $first->symbol; + } + $data = $this->generator->multiSet($chartData); $cache->store($data); return response()->json($data); @@ -507,4 +534,51 @@ class AccountController extends Controller return $data; } + /** + * @param Collection $accounts + * + * @return array + */ + private function extractNames(Collection $accounts): array + { + $return = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $return[$account->id] = $account->name; + } + + return $return; + } + + /** + * This method extracts the unique currency ID's from an array of balances. + * + * The given array is expected to be in this format: + * + * accountID1: + * currencyID1: balance + * currencyID2: balance + * accountID2: + * currencyID1: balance + * currencyID2: balance + * + * + * @param array $balances + * + * @return array + */ + private function getCurrencyIDs(array $balances): array + { + $currencies = []; + /** + * @var int $accountId + * @var array $info + */ + foreach ($balances as $accountId => $info) { + $currencies = array_merge(array_keys($info)); + } + + return array_unique($currencies); + } + } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 3256f37f0a..a30bcc7778 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -31,6 +31,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Collection; use Log; +use stdClass; /** * Class Steam. @@ -211,6 +212,38 @@ class Steam return $balances; } + /** + * @param \FireflyIII\Models\Account $account + * @param \Carbon\Carbon $date + * + * @return string + */ + public function balancePerCurrency(Account $account, Carbon $date): array + { + + // abuse chart properties: + $cache = new CacheProperties; + $cache->addProperty($account->id); + $cache->addProperty('balance-per-currency'); + $cache->addProperty($date); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $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'); + $balances = $query->get(['transactions.transaction_currency_id', DB::raw('SUM(transactions.amount) as sum_for_currency')]); + $return = []; + /** @var stdClass $entry */ + foreach ($balances as $entry) { + $return[(int)$entry->transaction_currency_id] = $entry->sum_for_currency; + } + $cache->store($return); + + return $return; + } + /** * This method always ignores the virtual balance. * @@ -243,6 +276,38 @@ class Steam return $result; } + /** + * Same as above, but also groups per currency. + * + * @param \Illuminate\Support\Collection $accounts + * @param \Carbon\Carbon $date + * + * @return array + */ + public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array + { + $ids = $accounts->pluck('id')->toArray(); + // cache this property. + $cache = new CacheProperties; + $cache->addProperty($ids); + $cache->addProperty('balances-per-currency'); + $cache->addProperty($date); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + // need to do this per account. + $result = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $result[$account->id] = $this->balancePerCurrency($account, $date); + } + + $cache->store($result); + + return $result; + } + /** * @param int $isEncrypted * @param $value diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ad4002464f..ddf829a604 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -183,6 +183,7 @@ return [ 'scopes_will_be_able' => 'This application will be able to:', 'button_authorize' => 'Authorize', 'none_in_select_list' => '(none)', + 'account_in_currency' => ':account in :currency', // check for updates: 'update_check_title' => 'Check for updates',