Make sure the balance box converts to native.

This commit is contained in:
James Cole
2025-03-23 15:04:29 +01:00
parent a3fcd636e7
commit 5e1ecb2b11
3 changed files with 128 additions and 61 deletions

View File

@@ -39,6 +39,8 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -93,10 +95,10 @@ class BasicController extends Controller
public function basic(DateRequest $request): JsonResponse public function basic(DateRequest $request): JsonResponse
{ {
// parameters for boxes: // parameters for boxes:
$dates = $request->getAll(); $dates = $request->getAll();
$start = $dates['start']; $start = $dates['start'];
$end = $dates['end']; $end = $dates['end'];
$code = $request->get('currency_code'); $code = $request->get('currency_code');
// balance information: // balance information:
$balanceData = $this->getBalanceInformation($start, $end); $balanceData = $this->getBalanceInformation($start, $end);
@@ -107,10 +109,10 @@ class BasicController extends Controller
// $billData = []; // $billData = [];
// $spentData = []; // $spentData = [];
// $netWorthData = []; // $netWorthData = [];
$total = array_merge($balanceData, $billData, $spentData, $netWorthData); $total = array_merge($balanceData, $billData, $spentData, $netWorthData);
// give new keys // give new keys
$return = []; $return = [];
foreach ($total as $entry) { foreach ($total as $entry) {
if (null === $code || ($code === $entry['currency_code'])) { if (null === $code || ($code === $entry['currency_code'])) {
$return[$entry['key']] = $entry; $return[$entry['key']] = $entry;
@@ -122,55 +124,119 @@ class BasicController extends Controller
private function getBalanceInformation(Carbon $start, Carbon $end): array private function getBalanceInformation(Carbon $start, Carbon $end): array
{ {
Log::debug('getBalanceInformation');
// some config settings // some config settings
$convertToNative = Amount::convertToNative(); $convertToNative = Amount::convertToNative();
$default = Amount::getNativeCurrency(); $default = Amount::getNativeCurrency();
// prep some arrays: // prep some arrays:
$incomes = []; $incomes = [];
$expenses = []; $expenses = [];
$sums = []; $sums = [];
$return = []; $return = [];
$currencies = [
$default->id => $default,
];
// collect income of user using the new group collector. // collect income of user using the new group collector.
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::DEPOSIT->value]); $summarizer = new TransactionSummarizer();
$set = $collector->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value])->getExtractedJournals();
$incomes = $summarizer->groupByCurrencyId($set, 'positive', false);
$set = $collector->getExtractedJournals();
/** @var array $journal */ // collect expenses of user.
foreach ($set as $journal) { // collect expenses of user using the new group collector.
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd(
$incomes[$currencyId],
bcmul($amount, '-1')
);
$sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($amount, '-1'));
}
// collect expenses of user using the new group collector.
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); $set = $collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->getExtractedJournals();
$set = $collector->getExtractedJournals(); $expenses = $summarizer->groupByCurrencyId($set, 'negative', false);
/** @var array $journal */ // if convert to native, do so right now.
foreach ($set as $journal) { if ($convertToNative) {
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id']; $newExpenses = [
$amount = Amount::getAmountFromJournal($journal); $default->id => [
$expenses[$currencyId] ??= '0'; 'currency_id' => $default->id,
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount); 'currency_code' => $default->code,
$sums[$currencyId] ??= '0'; 'currency_symbol' => $default->symbol,
$sums[$currencyId] = bcadd($sums[$currencyId], $amount); 'currency_decimal_places' => $default->decimal_places,
'sum' => '0',
],
];
$newIncomes = [
$default->id => [
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
'sum' => '0',
],
];
$sums = [
$default->id => [
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
'sum' => '0',
],
];
$converter = new ExchangeRateConverter();
// loop over income and expenses
foreach([$expenses, $incomes] as $index => $array) {
// loop over either one.
foreach ($array as $entry) {
// if it is the native currency already.
if ($entry['currency_id'] === $default->id) {
$sums[$default->id]['sum'] = bcadd($entry['sum'], $sums[$default->id]['sum']);
// don't forget to add it to newExpenses and newIncome
if(0 === $index) {
$newExpenses[$default->id]['sum'] = bcadd($newExpenses[$default->id]['sum'], $entry['sum']);
}
if(1 === $index) {
$newIncomes[$default->id]['sum'] = bcadd($newIncomes[$default->id]['sum'], $entry['sum']);
}
continue;
}
$currencies[$entry['currency_id']] ??= $this->currencyRepos->find($entry['currency_id']);
$convertedSum = $converter->convert($currencies[$entry['currency_id']], $default, $start, $entry['sum']);
$sums[$default->id]['sum'] = bcadd($sums[$default->id]['sum'], $convertedSum);
if(0 === $index) {
$newExpenses[$default->id]['sum'] = bcadd($newExpenses[$default->id]['sum'], $convertedSum);
}
if(1 === $index) {
$newIncomes[$default->id]['sum'] = bcadd($newIncomes[$default->id]['sum'], $convertedSum);
}
}
}
$incomes = $newIncomes;
$expenses = $newExpenses;
}
if(!$convertToNative) {
foreach([$expenses, $incomes] as $array) {
foreach ($array as $entry) {
$currencyId = $entry['currency_id'];
$sums[$currencyId] ??= [
'currency_id' => $entry['currency_id'],
'currency_code' => $entry['currency_code'],
'currency_symbol' => $entry['currency_symbol'],
'currency_decimal_places' => $entry['currency_decimal_places'],
'sum' => '0',
];
$sums[$currencyId]['sum'] = bcadd($sums[$currencyId]['sum'], $entry['sum']);
}
}
} }
// format amounts: // format amounts:
$keys = array_keys($sums); $keys = array_keys($sums);
foreach ($keys as $currencyId) { foreach ($keys as $currencyId) {
$currency = $this->currencyRepos->find($currencyId); $currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
if (null === $currency) { if (null === $currency) {
continue; continue;
} }
@@ -178,37 +244,37 @@ class BasicController extends Controller
$return[] = [ $return[] = [
'key' => sprintf('balance-in-%s', $currency->code), 'key' => sprintf('balance-in-%s', $currency->code),
'title' => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]), 'title' => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $sums[$currencyId] ?? '0', 'monetary_value' => $sums[$currencyId]['sum'] ?? '0',
'currency_id' => (string) $currency->id, 'currency_id' => (string) $currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId] ?? '0', false), 'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale', 'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false) 'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false), . ' + ' . app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
]; ];
$return[] = [ $return[] = [
'key' => sprintf('spent-in-%s', $currency->code), 'key' => sprintf('spent-in-%s', $currency->code),
'title' => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]), 'title' => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $expenses[$currencyId] ?? '0', 'monetary_value' => $expenses[$currencyId]['sum'] ?? '0',
'currency_id' => (string) $currency->id, 'currency_id' => (string) $currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false), 'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale', 'local_icon' => 'balance-scale',
'sub_title' => '', 'sub_title' => '',
]; ];
$return[] = [ $return[] = [
'key' => sprintf('earned-in-%s', $currency->code), 'key' => sprintf('earned-in-%s', $currency->code),
'title' => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]), 'title' => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $incomes[$currencyId] ?? '0', 'monetary_value' => $incomes[$currencyId]['sum'] ?? '0',
'currency_id' => (string) $currency->id, 'currency_id' => (string) $currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false), 'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale', 'local_icon' => 'balance-scale',
'sub_title' => '', 'sub_title' => '',
]; ];
@@ -227,7 +293,7 @@ class BasicController extends Controller
'value_parsed' => app('amount')->formatAnything($currency, '0', false), 'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'local_icon' => 'balance-scale', 'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, '0', false) 'sub_title' => app('amount')->formatAnything($currency, '0', false)
.' + '.app('amount')->formatAnything($currency, '0', false), . ' + ' . app('amount')->formatAnything($currency, '0', false),
]; ];
$return[] = [ $return[] = [
'key' => sprintf('spent-in-%s', $currency->code), 'key' => sprintf('spent-in-%s', $currency->code),
@@ -268,7 +334,7 @@ class BasicController extends Controller
$paidAmount = $this->billRepository->sumPaidInRange($start, $end); $paidAmount = $this->billRepository->sumPaidInRange($start, $end);
$unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end); $unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end);
$return = []; $return = [];
/** /**
* @var array $info * @var array $info
@@ -369,7 +435,7 @@ class BasicController extends Controller
Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum'])); Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum']));
$return[] = [ $return[] = [
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']), 'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]), 'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]),
'monetary_value' => $leftToSpend, 'monetary_value' => $leftToSpend,
@@ -416,16 +482,16 @@ class BasicController extends Controller
$end->endOfDay(); $end->endOfDay();
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s'))); Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s')));
/** @var NetWorthInterface $netWorthHelper */ /** @var NetWorthInterface $netWorthHelper */
$netWorthHelper = app(NetWorthInterface::class); $netWorthHelper = app(NetWorthInterface::class);
$netWorthHelper->setUser($user); $netWorthHelper->setUser($user);
$allAccounts = $this->accountRepository->getActiveAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value]); $allAccounts = $this->accountRepository->getActiveAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value]);
// filter list on preference of being included. // filter list on preference of being included.
$filtered = $allAccounts->filter( $filtered = $allAccounts->filter(
function (Account $account) { function (Account $account) {
$includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth'); $includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth');
@@ -433,13 +499,13 @@ class BasicController extends Controller
} }
); );
$netWorthSet = $netWorthHelper->byAccounts($filtered, $end); $netWorthSet = $netWorthHelper->byAccounts($filtered, $end);
$return = []; $return = [];
foreach ($netWorthSet as $key => $data) { foreach ($netWorthSet as $key => $data) {
if ('native' === $key) { if ('native' === $key) {
continue; continue;
} }
$amount = $data['balance']; $amount = $data['balance'];
if (0 === bccomp($amount, '0')) { if (0 === bccomp($amount, '0')) {
continue; continue;
} }

View File

@@ -49,7 +49,7 @@ class TransactionSummarizer
$this->convertToNative = Amount::convertToNative($user); $this->convertToNative = Amount::convertToNative($user);
} }
public function groupByCurrencyId(array $journals, string $method = 'negative'): array public function groupByCurrencyId(array $journals, string $method = 'negative', bool $includeForeign = true): array
{ {
Log::debug(sprintf('Now in groupByCurrencyId(array, "%s")', $method)); Log::debug(sprintf('Now in groupByCurrencyId(array, "%s")', $method));
$array = []; $array = [];
@@ -94,10 +94,10 @@ class TransactionSummarizer
} }
} }
if (!$this->convertToNative) { if (!$this->convertToNative) {
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
// use foreign amount? // use foreign amount?
$foreignCurrencyId = (int) $journal['foreign_currency_id']; $foreignCurrencyId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignCurrencyId) { if (0 !== $foreignCurrencyId) {
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is "%s")', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
$foreignCurrencyName = $journal['foreign_currency_name']; $foreignCurrencyName = $journal['foreign_currency_name'];
$foreignCurrencySymbol = $journal['foreign_currency_symbol']; $foreignCurrencySymbol = $journal['foreign_currency_symbol'];
$foreignCurrencyCode = $journal['foreign_currency_code']; $foreignCurrencyCode = $journal['foreign_currency_code'];
@@ -124,7 +124,7 @@ class TransactionSummarizer
} }
// then process foreign amount, if it exists. // then process foreign amount, if it exists.
if (0 !== $foreignCurrencyId) { if (0 !== $foreignCurrencyId && true === $includeForeign) {
$amount = (string) ($journal['foreign_amount'] ?? '0'); $amount = (string) ($journal['foreign_amount'] ?? '0');
$array[$foreignCurrencyId] ??= [ $array[$foreignCurrencyId] ??= [
'sum' => '0', 'sum' => '0',

View File

@@ -187,6 +187,7 @@ return [
'currencyPreference' => 'EUR', 'currencyPreference' => 'EUR',
'language' => 'en_US', 'language' => 'en_US',
'locale' => 'equal', 'locale' => 'equal',
'convertToNative' => false,
], ],
'default_currency' => 'EUR', 'default_currency' => 'EUR',
'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'), 'default_language' => envNonEmpty('DEFAULT_LANGUAGE', 'en_US'),