Expand native amount things.

This commit is contained in:
James Cole
2024-12-24 06:34:12 +01:00
parent 8805bcf6f6
commit e8ef630424
9 changed files with 233 additions and 286 deletions

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Summary;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Data\DateRequest; use FireflyIII\Api\V1\Requests\Data\DateRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Helpers\Report\NetWorthInterface;
@@ -38,6 +39,7 @@ use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -102,10 +104,10 @@ class BasicController extends Controller
$billData = $this->getBillInformation($start, $end); $billData = $this->getBillInformation($start, $end);
$spentData = $this->getLeftToSpendInfo($start, $end); $spentData = $this->getLeftToSpendInfo($start, $end);
$netWorthData = $this->getNetWorthInfo($start, $end); $netWorthData = $this->getNetWorthInfo($start, $end);
$balanceData = []; // $balanceData = [];
$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
@@ -121,6 +123,9 @@ class BasicController extends Controller
private function getBalanceInformation(Carbon $start, Carbon $end): array private function getBalanceInformation(Carbon $start, Carbon $end): array
{ {
// some config settings
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
// prep some arrays: // prep some arrays:
$incomes = []; $incomes = [];
$expenses = []; $expenses = [];
@@ -134,16 +139,17 @@ class BasicController extends Controller
$set = $collector->getExtractedJournals(); $set = $collector->getExtractedJournals();
/** @var array $transactionJournal */ /** @var array $journal */
foreach ($set as $transactionJournal) { foreach ($set as $journal) {
$currencyId = (int) $transactionJournal['currency_id']; $currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0'; $incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd( $incomes[$currencyId] = bcadd(
$incomes[$currencyId], $incomes[$currencyId],
bcmul($transactionJournal['amount'], '-1') bcmul($amount, '-1')
); );
$sums[$currencyId] ??= '0'; $sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1')); $sums[$currencyId] = bcadd($sums[$currencyId], bcmul($amount, '-1'));
} }
// collect expenses of user using the new group collector. // collect expenses of user using the new group collector.
@@ -152,13 +158,14 @@ class BasicController extends Controller
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); $collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$set = $collector->getExtractedJournals(); $set = $collector->getExtractedJournals();
/** @var array $transactionJournal */ /** @var array $journal */
foreach ($set as $transactionJournal) { foreach ($set as $journal) {
$currencyId = (int) $transactionJournal['currency_id']; $currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$expenses[$currencyId] ??= '0'; $expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']); $expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
$sums[$currencyId] ??= '0'; $sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], $transactionJournal['amount']); $sums[$currencyId] = bcadd($sums[$currencyId], $amount);
} }
// format amounts: // format amounts:
@@ -317,7 +324,7 @@ class BasicController extends Controller
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$date = today(config('app.timezone'))->startOfDay(); $date = now(config('app.timezone'));
// start and end in the future? use $end // start and end in the future? use $end
if ($this->notInDateRange($date, $start, $end)) { if ($this->notInDateRange($date, $start, $end)) {
/** @var Carbon $date */ /** @var Carbon $date */
@@ -327,9 +334,7 @@ class BasicController extends Controller
/** @var NetWorthInterface $netWorthHelper */ /** @var NetWorthInterface $netWorthHelper */
$netWorthHelper = app(NetWorthInterface::class); $netWorthHelper = app(NetWorthInterface::class);
$netWorthHelper->setUser($user); $netWorthHelper->setUser($user);
$allAccounts = $this->accountRepository->getActiveAccountsByType( $allAccounts = $this->accountRepository->getActiveAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value]);
[AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT]
);
// filter list on preference of being included. // filter list on preference of being included.
$filtered = $allAccounts->filter( $filtered = $allAccounts->filter(

View File

@@ -33,7 +33,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Api\ExchangeRateConverter; use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -65,87 +66,58 @@ class NetWorth implements NetWorthInterface
public function byAccounts(Collection $accounts, Carbon $date): array public function byAccounts(Collection $accounts, Carbon $date): array
{ {
// start in the past, end in the future? use $date // start in the past, end in the future? use $date
$ids = implode(',', $accounts->pluck('id')->toArray()); $convertToNative = app('preferences')->get('convert_to_native', false)->data;
$cache = new CacheProperties(); $ids = implode(',', $accounts->pluck('id')->toArray());
$cache = new CacheProperties();
$cache->addProperty($date); $cache->addProperty($date);
$cache->addProperty($convertToNative);
$cache->addProperty('net-worth-by-accounts'); $cache->addProperty('net-worth-by-accounts');
$cache->addProperty($ids); $cache->addProperty($ids);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // return $cache->get();
} }
app('log')->debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d'))); Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); $default = Amount::getDefaultCurrency();
$default = app('amount')->getDefaultCurrency(); $netWorth = [];
$converter = new ExchangeRateConverter(); $balances = Steam::finalAccountsBalance($accounts, $date);
// default "native" currency has everything twice, for consistency.
$netWorth = [
'native' => [
'balance' => '0',
'native_balance' => '0',
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_name' => $default->name,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
'native_currency_id' => $default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
],
];
$balances = app('steam')->finalAccountsBalance($accounts, $date);
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
app('log')->debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name)); Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->getRepository()->getAccountCurrency($account); $currency = $this->getRepository()->getAccountCurrency($account) ?? $default;
if (null === $currency) { $useNative = $convertToNative && $default->id !== $currency->id;
$currency = app('amount')->getDefaultCurrency(); $currency = $useNative ? $default : $currency;
} $currencyCode = $currency->code;
$currencyCode = $currency->code; $balance = '0';
$balance = '0'; $nativeBalance = '0';
$nativeBalance = '0';
if (array_key_exists($account->id, $balances)) { if (array_key_exists($account->id, $balances)) {
$balance = $balances[$account->id]['balance'] ?? '0'; $balance = $balances[$account->id]['balance'] ?? '0';
$nativeBalance = $balances[$account->id]['native_balance'] ?? '0'; $nativeBalance = $balances[$account->id]['native_balance'] ?? '0';
} }
app('log')->debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance)); Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance // always subtract virtual balance again.
$virtualBalance = $account->virtual_balance; $balance = '' !== (string) $account->virtual_balance ? bcsub($balance, $account->virtual_balance) : $balance;
if ('' !== $virtualBalance) { $nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, $account->native_virtual_balance) : $nativeBalance;
$balance = bcsub($balance, $virtualBalance); $amountToUse = $useNative ? $nativeBalance : $balance;
$nativeVirtualBalance = $converter->convert($default, $currency, $account->created_at, $virtualBalance); Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
$nativeBalance = bcsub($nativeBalance, $nativeVirtualBalance);
}
$netWorth[$currencyCode] ??= [ $netWorth[$currencyCode] ??= [
'balance' => '0', 'balance' => '0',
'native_balance' => '0', 'currency_id' => (string) $currency->id,
'currency_id' => (string) $currency->id, 'currency_code' => $currency->code,
'currency_code' => $currency->code, 'currency_name' => $currency->name,
'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol,
'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string) $default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
]; ];
$netWorth[$currencyCode]['balance'] = bcadd($balance, $netWorth[$currencyCode]['balance']); $netWorth[$currencyCode]['balance'] = bcadd($amountToUse, $netWorth[$currencyCode]['balance']);
$netWorth[$currencyCode]['native_balance'] = bcadd($nativeBalance, $netWorth[$currencyCode]['native_balance']);
$netWorth['native']['balance'] = bcadd($nativeBalance, $netWorth['native']['balance']);
$netWorth['native']['native_balance'] = bcadd($nativeBalance, $netWorth['native']['native_balance']);
} }
$cache->store($netWorth); $cache->store($netWorth);
$converter->summarize();
return $netWorth; return $netWorth;
} }
private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface private function getRepository(): AccountRepositoryInterface | AdminAccountRepositoryInterface
{ {
if (null === $this->userGroup) { if (null === $this->userGroup) {
return $this->accountRepository; return $this->accountRepository;
@@ -154,19 +126,19 @@ class NetWorth implements NetWorthInterface
return $this->adminAccountRepository; return $this->adminAccountRepository;
} }
public function setUser(null|Authenticatable|User $user): void public function setUser(null | Authenticatable | User $user): void
{ {
if (!$user instanceof User) { if (!$user instanceof User) {
return; return;
} }
$this->user = $user; $this->user = $user;
$this->userGroup = null; $this->userGroup = null;
// make repository: // make repository:
$this->accountRepository = app(AccountRepositoryInterface::class); $this->accountRepository = app(AccountRepositoryInterface::class);
$this->accountRepository->setUser($this->user); $this->accountRepository->setUser($this->user);
$this->currencyRepos = app(CurrencyRepositoryInterface::class); $this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->currencyRepos->setUser($this->user); $this->currencyRepos->setUser($this->user);
} }
@@ -187,18 +159,18 @@ class NetWorth implements NetWorthInterface
*/ */
$accounts = $this->getAccounts(); $accounts = $this->getAccounts();
$return = []; $return = [];
$balances = app('steam')->finalAccountsBalance($accounts, $date); $balances = Steam::finalAccountsBalance($accounts, $date);
foreach ($accounts as $account) { foreach ($accounts as $account) {
$currency = $this->getRepository()->getAccountCurrency($account); $currency = $this->getRepository()->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0'; $balance = $balances[$account->id]['balance'] ?? '0';
// always subtract virtual balance. // always subtract virtual balance.
$virtualBalance = $account->virtual_balance; $virtualBalance = $account->virtual_balance;
if ('' !== $virtualBalance) { if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance); $balance = bcsub($balance, $virtualBalance);
} }
$return[$currency->id] ??= [ $return[$currency->id] ??= [
'id' => (string) $currency->id, 'id' => (string) $currency->id,
'name' => $currency->name, 'name' => $currency->name,
'symbol' => $currency->symbol, 'symbol' => $currency->symbol,

View File

@@ -29,13 +29,11 @@ use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Controllers\DateCalculation; use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@@ -48,6 +46,7 @@ class BoxController extends Controller
/** /**
* Deprecated method, no longer in use. * Deprecated method, no longer in use.
*
* @deprecated * @deprecated
*/ */
public function available(): JsonResponse public function available(): JsonResponse
@@ -73,7 +72,7 @@ class BoxController extends Controller
$cache->addProperty($convertToNative); $cache->addProperty($convertToNative);
$cache->addProperty('box-balance'); $cache->addProperty('box-balance');
if ($cache->has()) { if ($cache->has()) {
return response()->json($cache->get()); // return response()->json($cache->get());
} }
// prep some arrays: // prep some arrays:
$incomes = []; $incomes = [];
@@ -91,9 +90,8 @@ class BoxController extends Controller
/** @var array $journal */ /** @var array $journal */
foreach ($set as $journal) { foreach ($set as $journal) {
$field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount';
$currencyId = $convertToNative ? $currency->id : (int) $journal['currency_id']; $currencyId = $convertToNative ? $currency->id : (int) $journal['currency_id'];
$amount = $journal[$field] ?? '0'; $amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0'; $incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd($incomes[$currencyId], app('steam')->positive($amount)); $incomes[$currencyId] = bcadd($incomes[$currencyId], app('steam')->positive($amount));
$sums[$currencyId] ??= '0'; $sums[$currencyId] ??= '0';
@@ -109,9 +107,8 @@ class BoxController extends Controller
/** @var array $journal */ /** @var array $journal */
foreach ($set as $journal) { foreach ($set as $journal) {
$field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount';
$currencyId = $convertToNative ? $currency->id : (int) $journal['currency_id']; $currencyId = $convertToNative ? $currency->id : (int) $journal['currency_id'];
$amount = $journal[$field] ?? '0'; $amount = Amount::getAmountFromJournal($journal);
$expenses[$currencyId] ??= '0'; $expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount); $expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
$sums[$currencyId] ??= '0'; $sums[$currencyId] ??= '0';

View File

@@ -61,7 +61,7 @@ class Account extends Model
'virtual_balance' => 'string', 'virtual_balance' => 'string',
]; ];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban']; protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban','native_virtual_balance'];
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];
private bool $joinedAccountTypes = false; private bool $joinedAccountTypes = false;

View File

@@ -37,6 +37,7 @@ use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Services\Internal\Destroy\BillDestroyService; use FireflyIII\Services\Internal\Destroy\BillDestroyService;
use FireflyIII\Services\Internal\Update\BillUpdateService; use FireflyIII\Services\Internal\Update\BillUpdateService;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
@@ -501,6 +502,7 @@ class BillRepository implements BillRepositoryInterface
public function sumPaidInRange(Carbon $start, Carbon $end): array public function sumPaidInRange(Carbon $start, Carbon $end): array
{ {
Log::debug(sprintf('sumPaidInRange from %s to %s', $start->toW3cString(), $end->toW3cString()));
$bills = $this->getActiveBills(); $bills = $this->getActiveBills();
$return = []; $return = [];
$convertToNative = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data; $convertToNative = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data;
@@ -508,12 +510,11 @@ class BillRepository implements BillRepositoryInterface
/** @var Bill $bill */ /** @var Bill $bill */
foreach ($bills as $bill) { foreach ($bills as $bill) {
/** @var Collection $set */ /** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$field = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount' : 'amount'; $return[(int) $currency->id] ??= [
$foreignField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_foreign_amount' : 'foreign_amount';
$return[$currency->id] ??= [
'id' => (string) $currency->id, 'id' => (string) $currency->id,
'name' => $currency->name, 'name' => $currency->name,
'symbol' => $currency->symbol, 'symbol' => $currency->symbol,
@@ -521,20 +522,14 @@ class BillRepository implements BillRepositoryInterface
'decimal_places' => $currency->decimal_places, 'decimal_places' => $currency->decimal_places,
'sum' => '0', 'sum' => '0',
]; ];
$setAmount = '0';
/** @var TransactionJournal $transactionJournal */ /** @var TransactionJournal $transactionJournal */
foreach ($set as $transactionJournal) { foreach ($set as $transactionJournal) {
/** @var null|Transaction $sourceTransaction */ $setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
$sourceTransaction = $transactionJournal->transactions()->where('amount', '<', 0)->first();
if (null !== $sourceTransaction) {
$amount = $sourceTransaction->$field;
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->$foreignField;
}
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount);
}
} }
Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
} }
return $return; return $return;
@@ -568,6 +563,7 @@ class BillRepository implements BillRepositoryInterface
$minField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_min' : 'amount_min'; $minField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_min' : 'amount_min';
$maxField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_max' : 'amount_max'; $maxField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_max' : 'amount_max';
Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
if ($total > 0) { if ($total > 0) {
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;

View File

@@ -31,9 +31,11 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class OperationsRepository * Class OperationsRepository
@@ -209,11 +211,12 @@ class OperationsRepository implements OperationsRepositoryInterface
?TransactionCurrency $currency = null ?TransactionCurrency $currency = null
): array ): array
{ {
// this collector excludes all transfers TO // this collector excludes all transfers TO liabilities (which are also withdrawals)
// liabilities (which are also withdrawals) // because those expenses only become expenses once they move from the liability to the friend.
// because those expenses only become expenses
// once they move from the liability to the friend.
// TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113) // TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113)
// 2024-12-24 disable the exclusion for now.
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user); $repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities')); $subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
@@ -234,7 +237,7 @@ class OperationsRepository implements OperationsRepositoryInterface
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user) $collector->setUser($this->user)
->setRange($start, $end) ->setRange($start, $end)
->excludeDestinationAccounts($selection) // ->excludeDestinationAccounts($selection)
->setTypes([TransactionType::WITHDRAWAL]); ->setTypes([TransactionType::WITHDRAWAL]);
if (null !== $accounts) { if (null !== $accounts) {
@@ -249,13 +252,12 @@ class OperationsRepository implements OperationsRepositoryInterface
$collector->setBudgets($budgets); $collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
// same but for foreign currencies: // same but for transactions in the foreign currency:
if (null !== $currency) { if (null !== $currency) {
// app('log')->debug(sprintf('Currency is "%s".', $currency->name)); // app('log')->debug(sprintf('Currency is "%s".', $currency->name));
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]) $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setForeignCurrency($currency)->setBudgets($budgets);
->setForeignCurrency($currency)->setBudgets($budgets);
if (null !== $accounts) { if (null !== $accounts) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
@@ -273,9 +275,9 @@ class OperationsRepository implements OperationsRepositoryInterface
$currencySymbol = $journal['currency_symbol']; $currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code']; $currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places']; $currencyDecimalPlaces = $journal['currency_decimal_places'];
$field = 'amount';
$foreignField = 'foreign_amount';
// if the user wants everything in native currency, use it. // if the user wants everything in native currency, use it.
// if the foreign amount is in this currency it's OK because Amount::getAmountFromJournal catches that.
if ($convertToNative && $default->id !== (int) $journal['currency_id']) { if ($convertToNative && $default->id !== (int) $journal['currency_id']) {
// use default currency info // use default currency info
$currencyId = $default->id; $currencyId = $default->id;
@@ -283,8 +285,6 @@ class OperationsRepository implements OperationsRepositoryInterface
$currencySymbol = $default->symbol; $currencySymbol = $default->symbol;
$currencyCode = $default->code; $currencyCode = $default->code;
$currencyDecimalPlaces = $default->decimal_places; $currencyDecimalPlaces = $default->decimal_places;
$field = 'native_amount';
$foreignField = 'native_foreign_amount';
} }
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'sum' => '0', 'sum' => '0',
@@ -294,21 +294,9 @@ class OperationsRepository implements OperationsRepositoryInterface
'currency_code' => $currencyCode, 'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces, 'currency_decimal_places' => $currencyDecimalPlaces,
]; ];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal[$field])); $amount = Amount::getAmountFromJournal($journal);
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount));
// also do foreign amount: Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
$foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId && $foreignId !== $currencyId) {
$array[$foreignId] ??= [
'sum' => '0',
'currency_id' => $foreignId,
'currency_name' => $journal['foreign_currency_name'],
'currency_symbol' => $journal['foreign_currency_symbol'],
'currency_code' => $journal['foreign_currency_code'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
$array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount']));
}
} }
return $array; return $array;

View File

@@ -24,7 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Support; namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -45,6 +47,46 @@ class Amount
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured); return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
} }
/**
* Experimental function to see if we can quickly and quietly get the amount from a journal.
* This depends on the user's default currency and the wish to have it converted.
*/
public function getAmountFromJournal(array $journal): string
{
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$currency = app('amount')->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount';
$amount = $journal[$field] ?? '0';
// fallback, the transaction has a foreign amount in $currency.
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === $journal['foreign_currency_id']) {
$amount = $journal['foreign_amount'];
}
return $amount;
}
/**
* Experimental function to see if we can quickly and quietly get the amount from a journal.
* This depends on the user's default currency and the wish to have it converted.
*/
public function getAmountFromJournalObject(TransactionJournal $journal): string
{
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$currency = app('amount')->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $sourceTransaction) {
return '0';
}
$amount = $sourceTransaction->$field;
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
}
return $amount;
}
/** /**
* This method will properly format the given number, in color or "black and white", * 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. * as a currency, given two things: the currency required and the current locale.
@@ -55,15 +97,15 @@ class Amount
*/ */
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
{ {
$locale = app('steam')->getLocale(); $locale = app('steam')->getLocale();
$rounded = app('steam')->bcround($amount, $decimalPlaces); $rounded = app('steam')->bcround($amount, $decimalPlaces);
$coloured ??= true; $coloured ??= true;
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$fmt->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $symbol); $fmt->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $symbol);
$fmt->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces); $fmt->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
$fmt->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces); $fmt->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
$result = (string) $fmt->format((float) $rounded); // intentional float $result = (string) $fmt->format((float) $rounded); // intentional float
if (true === $coloured) { if (true === $coloured) {
if (1 === bccomp($rounded, '0')) { if (1 === bccomp($rounded, '0')) {
@@ -109,7 +151,7 @@ class Amount
public function getDefaultCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency public function getDefaultCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
{ {
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty('getDefaultCurrencyByGroup'); $cache->addProperty('getDefaultCurrencyByGroup');
$cache->addProperty($userGroup->id); $cache->addProperty($userGroup->id);
if ($cache->has()) { if ($cache->has()) {
@@ -172,20 +214,20 @@ class Amount
private function getLocaleInfo(): array private function getLocaleInfo(): array
{ {
// get config from preference, not from translation: // get config from preference, not from translation:
$locale = app('steam')->getLocale(); $locale = app('steam')->getLocale();
$array = app('steam')->getLocaleArray($locale); $array = app('steam')->getLocaleArray($locale);
setlocale(LC_MONETARY, $array); setlocale(LC_MONETARY, $array);
$info = localeconv(); $info = localeconv();
// correct variables // correct variables
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
$fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL); $info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
$info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); $info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
@@ -208,7 +250,7 @@ class Amount
public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string
{ {
// negative first: // negative first:
$space = ' '; $space = ' ';
// require space between symbol and amount? // require space between symbol and amount?
if (false === $sepBySpace) { if (false === $sepBySpace) {
@@ -217,11 +259,11 @@ class Amount
// there are five possible positions for the "+" or "-" sign (if it is even used) // there are five possible positions for the "+" or "-" sign (if it is even used)
// pos_a and pos_e could be the ( and ) symbol. // pos_a and pos_e could be the ( and ) symbol.
$posA = ''; // before everything $posA = ''; // before everything
$posB = ''; // before currency symbol $posB = ''; // before currency symbol
$posC = ''; // after currency symbol $posC = ''; // after currency symbol
$posD = ''; // before amount $posD = ''; // before amount
$posE = ''; // after everything $posE = ''; // after everything
// format would be (currency before amount) // format would be (currency before amount)
// AB%sC_D%vE // AB%sC_D%vE
@@ -263,11 +305,11 @@ class Amount
} }
// default is amount before currency // default is amount before currency
$format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE; $format = $posA . $posD . '%v' . $space . $posB . '%s' . $posC . $posE;
if ($csPrecedes) { if ($csPrecedes) {
// alternative is currency before amount // alternative is currency before amount
$format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE; $format = $posA . $posB . '%s' . $posC . $space . $posD . '%v' . $posE;
} }
return $format; return $format;

View File

@@ -289,7 +289,7 @@ class Steam
; ;
$return['native_balance'] = $this->sumTransactions($array, 'native_amount'); $return['native_balance'] = $this->sumTransactions($array, 'native_amount');
// Log::debug(sprintf('native_balance is %s', $return['native_balance'])); // Log::debug(sprintf('native_balance is %s', $return['native_balance']));
$return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['balance']); $return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']);
// Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance'])); // Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance']));
} }

View File

@@ -26,6 +26,7 @@ $(function () {
}); });
function drawChart() { function drawChart() {
"use strict"; "use strict";
lineChart(accountFrontpageUrl, 'accounts-chart'); lineChart(accountFrontpageUrl, 'accounts-chart');
@@ -37,13 +38,77 @@ function drawChart() {
columnChart('chart/category/frontpage', 'categories-chart'); columnChart('chart/category/frontpage', 'categories-chart');
columnChart(accountExpenseUrl, 'expense-accounts-chart'); columnChart(accountExpenseUrl, 'expense-accounts-chart');
columnChart(accountRevenueUrl, 'revenue-accounts-chart'); columnChart(accountRevenueUrl, 'revenue-accounts-chart');
// get balance box:
getBalanceBox();
getBillsBox();
getAvailableBox();
getNetWorthBox();
getPiggyBanks(); getPiggyBanks();
getAllBoxes();
function getAllBoxes() {
// get summary.
$.getJSON('api/v1/summary/basic?start=' + sessionStart + '&end=' + sessionEnd).done(function (data) {
var key;
// balance.
var balance_top = [];
var balance_bottom = [];
// bills
var unpaid = [];
var paid = [];
// left to spend.
var left_to_spend_top = [];
var left_to_spend_bottom = [];
// net worth
var net_worth = [];
for (key in data) {
// balance
if (key.substring(0, 11) === 'balance-in-') {
balance_top.push(data[key].value_parsed);
balance_bottom.push(data[key].sub_title);
}
// bills
if (key.substring(0, 16) === 'bills-unpaid-in-') {
unpaid.push(data[key].value_parsed);
}
if (key.substring(0, 14) === 'bills-paid-in-') {
paid.push(data[key].value_parsed);
}
// left to spend
if (key.substring(0, 17) === 'left-to-spend-in-') {
left_to_spend_top.push(data[key].value_parsed);
left_to_spend_bottom.push(data[key].sub_title);
if(parseFloat(data[key].monetary_value) < 0) {
$('#box-left-to-spend-box').removeClass('bg-green-gradient').addClass('bg-red-gradient');
}
}
// net worth
if (key.substring(0, 13) === 'net-worth-in-') {
net_worth.push(data[key].value_parsed);
}
}
// balance
$('#box-balance-sums').html(balance_top.join(', '));
$('#box-balance-list').html(balance_bottom.join(', '));
// bills
$('#box-bills-unpaid').html(unpaid.join(', '));
$('#box-bills-paid').html(paid.join(', '));
// left to spend
$('#box-left-to-spend').html(left_to_spend_top.join(', '));
$('#box-left-per-day').html(left_to_spend_bottom.join(', '));
// net worth
$('#box-net-worth').html(net_worth.join(', '));
});
}
//getBoxAmounts(); //getBoxAmounts();
} }
@@ -58,121 +123,3 @@ function getPiggyBanks() {
} }
}); });
} }
function getNetWorthBox() {
// box-net-worth
$.getJSON('json/box/net-worth').done(function (data) {
$('#box-net-worth').html(data.net_worths.join(', '));
});
}
/**
*
*/
function getAvailableBox() {
// box-left-to-spend
// box-left-per-day
// * 0) If the user has available amount this period and has overspent: overspent box.
// * 1) If the user has available amount this period and has NOT overspent: left to spend box.
// * 2) if the user has no available amount set this period: spent per day
$.getJSON('json/box/available').done(function (data) {
$('#box-left-to-spend-text').text(data.title);
if (0 === data.display) {
$('#box-left-to-spend-box').removeClass('bg-green-gradient').addClass('bg-red-gradient');
$('#box-left-to-spend').html(data.left_to_spend);
$('#box-left-per-day').html(data.left_per_day);
}
if (1 === data.display) {
$('#box-left-to-spend').html(data.left_to_spend);
$('#box-left-per-day').html(data.left_per_day);
}
if (2 === data.display) {
$('#box-left-to-spend').html(data.spent_total);
$('#box-left-per-day').html(data.spent_per_day);
}
});
}
/**
*
*/
function getBillsBox() {
// box-bills-unpaid
// box-bills-paid
// get summary.
$.getJSON('api/v1/summary/basic?start=' + sessionStart + '&end=' + sessionEnd).done(function (data) {
var key;
var unpaid = [];
var paid = [];
for (key in data) {
//console.log(key);
if (key.substr(0, 16) === 'bills-unpaid-in-') {
// only when less than 3.
if (unpaid.length < 3) {
unpaid.push(data[key].value_parsed);
}
}
if (key.substr(0, 14) === 'bills-paid-in-') {
// only when less than 5.
if (paid.length < 3) {
paid.push(data[key].value_parsed);
}
}
}
$('#box-bills-unpaid').html(unpaid.join(', '));
$('#box-bills-paid').html(paid.join(', '));
});
}
/**
*
*/
function getBalanceBox() {
// box-balance-sums
// box-balance-list
$.getJSON('json/box/balance').done(function (data) {
if (data.size === 1) {
// show balance in "sums", show single entry in list.
for (var x in data.sums) {
$('#box-balance-sums').html(data.sums[x]);
$('#box-balance-list').html(data.incomes[x] + ' + ' + data.expenses[x]);
}
return;
}
// do not use "sums", only use list.
$('#box-balance-progress').remove();
var expense, string, sum, income, current;
// first loop, echo only "preferred".
for (x in data.sums) {
current = $('#box-balance-list').html();
sum = data.sums[x];
expense = data.expenses[x];
income = data.incomes[x];
string = income + ' + ' + expense + ': ' + sum;
if (data.preferred == x) {
$('#box-balance-list').html(current + '<span title="' + string + '">' + string + '</span>' + '<br>');
}
}
// then list the others (only 1 space)
var count = 0;
for (x in data.sums) {
if (count > 2) {
return;
}
current = $('#box-balance-list').html();
sum = data.sums[x];
expense = data.expenses[x];
income = data.incomes[x];
string = income + ' + ' + expense + ': ' + sum;
if (data.preferred != x) {
$('#box-balance-list').html(current + '<span title="' + string + '">' + string + '</span>' + '<br>');
}
count++;
}
});
}