This makes the expense chart on the frontpage multi-currency.

This commit is contained in:
James Cole 2018-08-27 08:08:51 +02:00
parent 3764499714
commit 4fc13037d2
4 changed files with 155 additions and 13 deletions

View File

@ -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'

View File

@ -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);
}
}

View File

@ -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

View File

@ -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',