New method of collecting balance.

This commit is contained in:
James Cole 2024-07-31 13:09:55 +02:00
parent b2954658d8
commit 3560f0388c
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
10 changed files with 273 additions and 209 deletions

View File

@ -1,4 +1,6 @@
parameters: parameters:
scanFiles:
- ../_ide_helper_models.php
universalObjectCratesClasses: universalObjectCratesClasses:
- Illuminate\Database\Eloquent\Model - Illuminate\Database\Eloquent\Model
# TODO: slowly remove these parameters and fix the issues found. # 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. - '#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. - '#has no value type specified in iterable type array#' # remove this rule when all other issues are solved.
- '#is not allowed to extend#' - '#is not allowed to extend#'
- '#does not specify its types#'
- '#switch is forbidden to use#' - '#switch is forbidden to use#'
- '#is neither abstract nor final#' - '#is neither abstract nor final#'
- '#on left side of \?\?\= always exists and is not nullable#' - '#on left side of \?\?\= always exists and is not nullable#'

View File

@ -102,6 +102,7 @@ class PreferencesController extends Controller
* TODO This endpoint is not documented. * TODO This endpoint is not documented.
* *
* Return a single preference by name. * Return a single preference by name.
* @param Collection<int, Preference> $collection
*/ */
public function showList(Collection $collection): JsonResponse public function showList(Collection $collection): JsonResponse
{ {

View File

@ -48,7 +48,7 @@ class BalanceController extends Controller
private AccountRepositoryInterface $repository; private AccountRepositoryInterface $repository;
private GroupCollectorInterface $collector; private GroupCollectorInterface $collector;
private ChartData $chartData; private ChartData $chartData;
private TransactionCurrency $default; // private TransactionCurrency $default;
public function __construct() public function __construct()
{ {
@ -61,7 +61,7 @@ class BalanceController extends Controller
$this->repository->setUserGroup($userGroup); $this->repository->setUserGroup($userGroup);
$this->collector->setUserGroup($userGroup); $this->collector->setUserGroup($userGroup);
$this->chartData = new ChartData(); $this->chartData = new ChartData();
$this->default = app('amount')->getDefaultCurrency(); // $this->default = app('amount')->getDefaultCurrency();
return $next($request); return $next($request);
} }

View File

@ -51,7 +51,7 @@ class FixUnevenAmount extends Command
$this->convertOldStyleTransfers(); $this->convertOldStyleTransfers();
$this->fixUnevenAmounts(); $this->fixUnevenAmounts();
$this->matchCurrencies(); $this->matchCurrencies();
AccountBalanceCalculator::recalculateAll(); AccountBalanceCalculator::forceRecalculateAll();
return 0; return 0;
} }

View File

@ -43,8 +43,8 @@ class OtherCurrenciesCorrections extends Command
use ShowsFriendlyMessages; use ShowsFriendlyMessages;
public const string CONFIG_NAME = '480_other_currencies'; public const string CONFIG_NAME = '480_other_currencies';
protected $description = 'Update all journal currency information.'; protected $description = 'Update all journal currency information.';
protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}';
private array $accountCurrencies; private array $accountCurrencies;
private AccountRepositoryInterface $accountRepos; private AccountRepositoryInterface $accountRepos;
private JournalCLIRepositoryInterface $cliRepos; private JournalCLIRepositoryInterface $cliRepos;
@ -90,7 +90,7 @@ class OtherCurrenciesCorrections extends Command
{ {
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) { if (null !== $configVar) {
return (bool)$configVar->data; return (bool) $configVar->data;
} }
return false; return false;
@ -120,7 +120,7 @@ class OtherCurrenciesCorrections extends Command
$this->journalRepos->setUser($journal->user); $this->journalRepos->setUser($journal->user);
$this->cliRepos->setUser($journal->user); $this->cliRepos->setUser($journal->user);
$leadTransaction = $this->getLeadTransaction($journal); $leadTransaction = $this->getLeadTransaction($journal);
if (null === $leadTransaction) { if (null === $leadTransaction) {
$this->friendlyError(sprintf('Could not reliably determine which transaction is in the lead for transaction journal #%d.', $journal->id)); $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; return;
} }
$account = $leadTransaction->account; $account = $leadTransaction->account;
$currency = $this->getCurrency($account); $currency = $this->getCurrency($account);
$isMultiCurrency = $this->isMultiCurrency($account);
if (null === $currency) { if (null === $currency) {
$this->friendlyError( $this->friendlyError(
sprintf( sprintf(
@ -145,14 +146,14 @@ class OtherCurrenciesCorrections extends Command
} }
// fix each transaction: // fix each transaction:
$journal->transactions->each( $journal->transactions->each(
static function (Transaction $transaction) use ($currency): void { static function (Transaction $transaction) use ($currency, $isMultiCurrency): void {
if (null === $transaction->transaction_currency_id) { if (null === $transaction->transaction_currency_id) {
$transaction->transaction_currency_id = $currency->id; $transaction->transaction_currency_id = $currency->id;
$transaction->save(); $transaction->save();
} }
// when mismatch in transaction: // 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_currency_id = $transaction->transaction_currency_id;
$transaction->foreign_amount = $transaction->amount; $transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id; $transaction->transaction_currency_id = $currency->id;
@ -161,7 +162,9 @@ class OtherCurrenciesCorrections extends Command
} }
); );
// also update the journal, of course: // also update the journal, of course:
$journal->transaction_currency_id = $currency->id; if (!$isMultiCurrency) {
$journal->transaction_currency_id = $currency->id;
}
++$this->count; ++$this->count;
$journal->save(); $journal->save();
} }
@ -217,14 +220,14 @@ class OtherCurrenciesCorrections extends Command
private function getCurrency(Account $account): ?TransactionCurrency private function getCurrency(Account $account): ?TransactionCurrency
{ {
$accountId = $account->id; $accountId = $account->id;
if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) { if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) {
return null; return null;
} }
if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) {
return $this->accountCurrencies[$accountId]; return $this->accountCurrencies[$accountId];
} }
$currency = $this->accountRepos->getAccountCurrency($account); $currency = $this->accountRepos->getAccountCurrency($account);
if (null === $currency) { if (null === $currency) {
$this->accountCurrencies[$accountId] = 0; $this->accountCurrencies[$accountId] = 0;
@ -239,4 +242,13 @@ class OtherCurrenciesCorrections extends Command
{ {
app('fireflyconfig')->set(self::CONFIG_NAME, true); 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;
}
} }

View File

@ -45,6 +45,11 @@ class Amount
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured); 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", * 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.

View File

@ -45,6 +45,16 @@ class AccountBalanceCalculator
// no-op // 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. * Recalculate all balances.
*/ */

View File

@ -25,7 +25,6 @@ namespace FireflyIII\Support;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException; use Carbon\Exceptions\InvalidFormatException;
use Exception;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
@ -43,26 +42,24 @@ class Steam
public function balanceIgnoreVirtual(Account $account, Carbon $date): string public function balanceIgnoreVirtual(Account $account, Carbon $date): string
{ {
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user); $repository->setUser($account->user);
$currencyId = (int)$repository->getMetaValue($account, 'currency_id'); $currencyId = (int) $repository->getMetaValue($account, 'currency_id');
$transactions = $account->transactions() $transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currencyId) ->where('transactions.transaction_currency_id', $currencyId)
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
; $nativeBalance = $this->sumTransactions($transactions, 'amount');
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency: // get all balances in foreign currency:
$transactions = $account->transactions() $transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currencyId) ->where('transactions.foreign_currency_id', $currencyId)
->where('transactions.transaction_currency_id', '!=', $currencyId) ->where('transactions.transaction_currency_id', '!=', $currencyId)
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
@ -75,7 +72,7 @@ class Steam
/** @var array $transaction */ /** @var array $transaction */
foreach ($transactions as $transaction) { foreach ($transactions as $transaction) {
$value = (string)($transaction[$key] ?? '0'); $value = (string) ($transaction[$key] ?? '0');
$value = '' === $value ? '0' : $value; $value = '' === $value ? '0' : $value;
$sum = bcadd($sum, $value); $sum = bcadd($sum, $value);
} }
@ -92,7 +89,7 @@ class Steam
*/ */
public function balanceInRange(Account $account, Carbon $start, Carbon $end, ?TransactionCurrency $currency = null): array 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($account->id);
$cache->addProperty('balance-in-range'); $cache->addProperty('balance-in-range');
$cache->addProperty(null !== $currency ? $currency->id : 0); $cache->addProperty(null !== $currency ? $currency->id : 0);
@ -104,54 +101,53 @@ class Steam
$start->subDay(); $start->subDay();
$end->addDay(); $end->addDay();
$balances = []; $balances = [];
$formatted = $start->format('Y-m-d'); $formatted = $start->format('Y-m-d');
$startBalance = $this->balance($account, $start, $currency); $startBalance = $this->balance($account, $start, $currency);
$balances[$formatted] = $startBalance; $balances[$formatted] = $startBalance;
if (null === $currency) { if (null === $currency) {
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user); $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(); $start->addDay();
// query! // query!
$set = $account->transactions() $set = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->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', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'))
->groupBy('transaction_journals.date') ->groupBy('transaction_journals.date')
->groupBy('transactions.transaction_currency_id') ->groupBy('transactions.transaction_currency_id')
->groupBy('transactions.foreign_currency_id') ->groupBy('transactions.foreign_currency_id')
->orderBy('transaction_journals.date', 'ASC') ->orderBy('transaction_journals.date', 'ASC')
->whereNull('transaction_journals.deleted_at') ->whereNull('transaction_journals.deleted_at')
->get( ->get(
[ // @phpstan-ignore-line [ // @phpstan-ignore-line
'transaction_journals.date', 'transaction_journals.date',
'transactions.transaction_currency_id', 'transactions.transaction_currency_id',
\DB::raw('SUM(transactions.amount) AS modified'), \DB::raw('SUM(transactions.amount) AS modified'),
'transactions.foreign_currency_id', 'transactions.foreign_currency_id',
\DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), \DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
] ]
) );
;
$currentBalance = $startBalance; $currentBalance = $startBalance;
/** @var Transaction $entry */ /** @var Transaction $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
// normal amount and foreign amount // normal amount and foreign amount
$modified = (string)(null === $entry->modified ? '0' : $entry->modified); $modified = (string) (null === $entry->modified ? '0' : $entry->modified);
$foreignModified = (string)(null === $entry->modified_foreign ? '0' : $entry->modified_foreign); $foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign);
$amount = '0'; $amount = '0';
if ($currencyId === (int)$entry->transaction_currency_id || 0 === $currencyId) { if ($currencyId === (int) $entry->transaction_currency_id || 0 === $currencyId) {
// use normal amount: // use normal amount:
$amount = $modified; $amount = $modified;
} }
if ($currencyId === (int)$entry->foreign_currency_id) { if ($currencyId === (int) $entry->foreign_currency_id) {
// use foreign amount: // use foreign amount:
$amount = $foreignModified; $amount = $foreignModified;
} }
@ -167,6 +163,45 @@ class Steam
return $balances; 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 * 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 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: // abuse chart properties:
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($account->id); $cache->addProperty($account->id);
$cache->addProperty('balance'); $cache->addProperty('balance');
$cache->addProperty($date); $cache->addProperty($date);
@ -185,26 +222,24 @@ class Steam
} }
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
if (null === $currency) { if (null === $currency) {
$currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); $currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
} }
// first part: get all balances in own currency: // first part: get all balances in own currency:
$transactions = $account->transactions() $transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
; $nativeBalance = $this->sumTransactions($transactions, 'amount');
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency: // get all balances in foreign currency:
$transactions = $account->transactions() $transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currency->id) ->where('transactions.foreign_currency_id', $currency->id)
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount'); $foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
$balance = bcadd($nativeBalance, $foreignBalance); $balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; $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 public function balanceInRangeConverted(Account $account, Carbon $start, Carbon $end, TransactionCurrency $native): array
{ {
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($account->id); $cache->addProperty($account->id);
$cache->addProperty('balance-in-range-converted'); $cache->addProperty('balance-in-range-converted');
$cache->addProperty($native->id); $cache->addProperty($native->id);
@ -242,35 +277,34 @@ class Steam
Log::debug(sprintf('Start balance on %s is %s', $formatted, $startBalance)); Log::debug(sprintf('Start balance on %s is %s', $formatted, $startBalance));
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
// not sure why this is happening: // not sure why this is happening:
$start->addDay(); $start->addDay();
// grab all transactions between start and end: // grab all transactions between start and end:
$set = $account->transactions() $set = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->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', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'))
->orderBy('transaction_journals.date', 'ASC') ->orderBy('transaction_journals.date', 'ASC')
->whereNull('transaction_journals.deleted_at') ->whereNull('transaction_journals.deleted_at')
->get( ->get(
[ [
'transaction_journals.date', 'transaction_journals.date',
'transactions.transaction_currency_id', 'transactions.transaction_currency_id',
'transactions.amount', 'transactions.amount',
'transactions.foreign_currency_id', 'transactions.foreign_currency_id',
'transactions.foreign_amount', 'transactions.foreign_amount',
] ]
)->toArray() )->toArray();
;
// loop the set and convert if necessary: // loop the set and convert if necessary:
$currentBalance = $startBalance; $currentBalance = $startBalance;
/** @var Transaction $transaction */ /** @var Transaction $transaction */
foreach ($set as $transaction) { foreach ($set as $transaction) {
$day = false; $day = false;
try { try {
$day = Carbon::parse($transaction['date'], config('app.timezone')); $day = Carbon::parse($transaction['date'], config('app.timezone'));
@ -280,9 +314,9 @@ class Steam
if (false === $day) { if (false === $day) {
$day = today(config('app.timezone')); $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 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. // change the current balance, set it to today, continue the loop.
$currentBalance = bcadd($currentBalance, $transaction['amount']); $currentBalance = bcadd($currentBalance, $transaction['amount']);
$balances[$format] = $currentBalance; $balances[$format] = $currentBalance;
@ -291,7 +325,7 @@ class Steam
continue; continue;
} }
// if foreign currency is in the expected currency, do nothing: // 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']); $currentBalance = bcadd($currentBalance, $transaction['foreign_amount']);
$balances[$format] = $currentBalance; $balances[$format] = $currentBalance;
Log::debug(sprintf('%s: transaction in %s (foreign), new balance is %s.', $format, $native->code, $currentBalance)); Log::debug(sprintf('%s: transaction in %s (foreign), new balance is %s.', $format, $native->code, $currentBalance));
@ -299,25 +333,25 @@ class Steam
continue; continue;
} }
// otherwise, convert 'amount' to the necessary currency: // 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); $currency = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$currencies[$currencyId] = $currency; $currencies[$currencyId] = $currency;
$rate = $converter->getCurrencyRate($currency, $native, $day); $rate = $converter->getCurrencyRate($currency, $native, $day);
$convertedAmount = bcmul($transaction['amount'], $rate); $convertedAmount = bcmul($transaction['amount'], $rate);
$currentBalance = bcadd($currentBalance, $convertedAmount); $currentBalance = bcadd($currentBalance, $convertedAmount);
$balances[$format] = $currentBalance; $balances[$format] = $currentBalance;
Log::debug(sprintf( Log::debug(sprintf(
'%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s', '%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s',
$format, $format,
$currency->code, $currency->code,
$rate, $rate,
$currency->code, $currency->code,
$transaction['amount'], $transaction['amount'],
$native->code, $native->code,
$convertedAmount $convertedAmount
)); ));
} }
$cache->store($balances); $cache->store($balances);
@ -348,7 +382,7 @@ class Steam
public function balanceConverted(Account $account, Carbon $date, TransactionCurrency $native): string 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)); 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($account->id);
$cache->addProperty('balance'); $cache->addProperty('balance');
$cache->addProperty($date); $cache->addProperty($date);
@ -369,72 +403,66 @@ class Steam
return $this->balance($account, $date); return $this->balance($account, $date);
} }
$new = []; $new = [];
$existing = []; $existing = [];
$new[] = $account->transactions() // 1 $new[] = $account->transactions() // 1
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transaction(s) in set #1', count($new[0]))); Log::debug(sprintf('%d transaction(s) in set #1', count($new[0])));
$existing[] = $account->transactions() // 2 $existing[] = $account->transactions() // 2
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $native->id) ->where('transactions.transaction_currency_id', $native->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0]))); Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0])));
$new[] = $account->transactions() // 3 $new[] = $account->transactions() // 3
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.transaction_currency_id', '!=', $native->id) ->where('transactions.transaction_currency_id', '!=', $native->id)
->whereNull('transactions.foreign_currency_id') ->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #3', count($new[1]))); Log::debug(sprintf('%d transactions in set #3', count($new[1])));
$existing[] = $account->transactions() // 4 $existing[] = $account->transactions() // 4
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $native->id) ->where('transactions.foreign_currency_id', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transactions.foreign_amount'])->toArray() ->get(['transactions.foreign_amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #4', count($existing[1]))); Log::debug(sprintf('%d transactions in set #4', count($existing[1])));
$new[] = $account->transactions()// 5 $new[] = $account->transactions()// 5
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id) ->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #5', count($new[2]))); Log::debug(sprintf('%d transactions in set #5', count($new[2])));
$new[] = $account->transactions()// 6 $new[] = $account->transactions()// 6
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', '!=', $currency->id) ->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id) ->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount') ->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray() ->get(['transaction_journals.date', 'transactions.amount'])->toArray();
;
Log::debug(sprintf('%d transactions in set #6', count($new[3]))); Log::debug(sprintf('%d transactions in set #6', count($new[3])));
// process both sets of transactions. Of course, no need to convert set "existing". // process both sets of transactions. Of course, no need to convert set "existing".
$balance = $this->sumTransactions($existing[0], 'amount'); $balance = $this->sumTransactions($existing[0], 'amount');
$balance = bcadd($balance, $this->sumTransactions($existing[1], 'foreign_amount')); $balance = bcadd($balance, $this->sumTransactions($existing[1], 'foreign_amount'));
Log::debug(sprintf('Balance from set #2 and #4 is %f', $balance)); 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) // 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 // but we need to convert each transaction separately because the date difference may
// incur huge currency changes. // incur huge currency changes.
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__)); Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$start = clone $date; $start = clone $date;
$end = clone $date; $end = clone $date;
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
foreach ($new as $set) { foreach ($new as $set) {
foreach ($set as $transaction) { foreach ($set as $transaction) {
$currentDate = false; $currentDate = false;
@ -457,7 +485,7 @@ class Steam
foreach ($new as $set) { foreach ($new as $set) {
foreach ($set as $transaction) { foreach ($set as $transaction) {
$currentDate = false; $currentDate = false;
try { try {
$currentDate = Carbon::parse($transaction['date'], config('app.timezone')); $currentDate = Carbon::parse($transaction['date'], config('app.timezone'));
@ -474,9 +502,9 @@ class Steam
} }
// add virtual balance (also needs conversion) // add virtual balance (also needs conversion)
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance; $virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
$virtual = $converter->convert($currency, $native, $account->created_at, $virtual); $virtual = $converter->convert($currency, $native, $account->created_at, $virtual);
$balance = bcadd($balance, $virtual); $balance = bcadd($balance, $virtual);
$converter->summarize(); $converter->summarize();
$cache->store($balance); $cache->store($balance);
@ -492,9 +520,9 @@ class Steam
*/ */
public function balancesByAccounts(Collection $accounts, Carbon $date): array public function balancesByAccounts(Collection $accounts, Carbon $date): array
{ {
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
// cache this property. // cache this property.
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($ids); $cache->addProperty($ids);
$cache->addProperty('balances'); $cache->addProperty('balances');
$cache->addProperty($date); $cache->addProperty($date);
@ -522,9 +550,9 @@ class Steam
*/ */
public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array
{ {
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
// cache this property. // cache this property.
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($ids); $cache->addProperty($ids);
$cache->addProperty('balances-converted'); $cache->addProperty('balances-converted');
$cache->addProperty($date); $cache->addProperty($date);
@ -540,9 +568,9 @@ class Steam
$default = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); $default = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$result[$account->id] $result[$account->id]
= [ = [
'balance' => $this->balance($account, $date), 'balance' => $this->balance($account, $date),
'native_balance' => $this->balanceConverted($account, $date, $default), 'native_balance' => $this->balanceConverted($account, $date, $default),
]; ];
} }
$cache->store($result); $cache->store($result);
@ -555,9 +583,9 @@ class Steam
*/ */
public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array
{ {
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
// cache this property. // cache this property.
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($ids); $cache->addProperty($ids);
$cache->addProperty('balances-per-currency'); $cache->addProperty('balances-per-currency');
$cache->addProperty($date); $cache->addProperty($date);
@ -581,7 +609,7 @@ class Steam
public function balancePerCurrency(Account $account, Carbon $date): array public function balancePerCurrency(Account $account, Carbon $date): array
{ {
// abuse chart properties: // abuse chart properties:
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($account->id); $cache->addProperty($account->id);
$cache->addProperty('balance-per-currency'); $cache->addProperty('balance-per-currency');
$cache->addProperty($date); $cache->addProperty($date);
@ -589,16 +617,15 @@ class Steam
return $cache->get(); return $cache->get();
} }
$query = $account->transactions() $query = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->groupBy('transactions.transaction_currency_id') ->groupBy('transactions.transaction_currency_id');
;
$balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line $balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line
$return = []; $return = [];
/** @var \stdClass $entry */ /** @var \stdClass $entry */
foreach ($balances as $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); $cache->store($return);
@ -624,10 +651,10 @@ class Steam
// Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision)); // Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
if (str_contains($number, '.')) { if (str_contains($number, '.')) {
if ('-' !== $number[0]) { 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; return $number;
@ -703,22 +730,22 @@ class Steam
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
return (string)$hostName; return (string) $hostName;
} }
public function getLastActivities(array $accounts): array public function getLastActivities(array $accounts): array
{ {
$list = []; $list = [];
$set = auth()->user()->transactions() $set = auth()->user()->transactions()
->whereIn('transactions.account_id', $accounts) ->whereIn('transactions.account_id', $accounts)
->groupBy(['transactions.account_id', 'transaction_journals.user_id']) ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line ->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
; ;
/** @var Transaction $entry */ /** @var Transaction $entry */
foreach ($set as $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')); $date->setTimezone(config('app.timezone'));
$list[$entry->account_id] = $date; $list[$entry->account_id] = $date;
} }
@ -738,7 +765,7 @@ class Steam
if ('equal' === $locale) { if ('equal' === $locale) {
$locale = $this->getLanguage(); $locale = $this->getLanguage();
} }
$locale = (string)$locale; $locale = (string) $locale;
// Check for Windows to replace the locale correctly. // Check for Windows to replace the locale correctly.
if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) { if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
@ -793,9 +820,9 @@ class Steam
public function getSafeUrl(string $unknownUrl, string $safeUrl): string public function getSafeUrl(string $unknownUrl, string $safeUrl): string
{ {
// Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl)); // Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
$returnUrl = $safeUrl; $returnUrl = $safeUrl;
$unknownHost = parse_url($unknownUrl, PHP_URL_HOST); $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
$safeHost = parse_url($safeUrl, PHP_URL_HOST); $safeHost = parse_url($safeUrl, PHP_URL_HOST);
if (null !== $unknownHost && $unknownHost === $safeHost) { if (null !== $unknownHost && $unknownHost === $safeHost) {
$returnUrl = $unknownUrl; $returnUrl = $unknownUrl;
@ -832,26 +859,26 @@ class Steam
*/ */
public function floatalize(string $value): string public function floatalize(string $value): string
{ {
$value = strtoupper($value); $value = strtoupper($value);
if (!str_contains($value, 'E')) { if (!str_contains($value, 'E')) {
return $value; return $value;
} }
Log::debug(sprintf('Floatalizing %s', $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, '.')) { if (str_contains($number, '.')) {
$post = strlen(substr($number, (int)strpos($number, '.') + 1)); $post = strlen(substr($number, (int) strpos($number, '.') + 1));
$mantis = substr($value, (int)strpos($value, 'E') + 1); $mantis = substr($value, (int) strpos($value, 'E') + 1);
if ($mantis < 0) { if ($mantis < 0) {
$post += abs((int)$mantis); $post += abs((int) $mantis);
} }
// TODO careless float could break financial math. // 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. // 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 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. // has a K in it, remove the K and multiply by 1024.
$bytes = bcmul(rtrim($string, 'k'), '1024'); $bytes = bcmul(rtrim($string, 'k'), '1024');
return (int)$bytes; return (int) $bytes;
} }
if (false !== stripos($string, 'm')) { if (false !== stripos($string, 'm')) {
// has a M in it, remove the M and multiply by 1048576. // has a M in it, remove the M and multiply by 1048576.
$bytes = bcmul(rtrim($string, 'm'), '1048576'); $bytes = bcmul(rtrim($string, 'm'), '1048576');
return (int)$bytes; return (int) $bytes;
} }
if (false !== stripos($string, 'g')) { if (false !== stripos($string, 'g')) {
// has a G in it, remove the G and multiply by (1024)^3. // has a G in it, remove the G and multiply by (1024)^3.
$bytes = bcmul(rtrim($string, 'g'), '1073741824'); $bytes = bcmul(rtrim($string, 'g'), '1073741824');
return (int)$bytes; return (int) $bytes;
} }
return (int)$string; return (int) $string;
} }
public function positive(string $amount): string public function positive(string $amount): string

View File

@ -64,8 +64,14 @@ class General extends AbstractExtension
/** @var Carbon $date */ /** @var Carbon $date */
$date = session('end', today(config('app.timezone'))->endOfMonth()); $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);
} }
); );
} }

View File

@ -105,7 +105,7 @@
<div class="btn-group"> <div class="btn-group">
<a type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" <a type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" aria-haspopup="true" aria-expanded="false"
href="{{ route('accounts.show', [data.account.id]) }}">{{ formatAmountByAccount(data.account, data.account|balance, false) }} href="{{ route('accounts.show', [data.account.id]) }}">{{ data.account|balance }}
<span class="caret"></span> <span class="caret"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">