New method of collecting balance.

This commit is contained in:
James Cole
2024-07-31 13:09:55 +02:00
parent b2954658d8
commit 3560f0388c
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

@@ -130,6 +130,7 @@ class OtherCurrenciesCorrections extends Command
$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:
if (!$isMultiCurrency) {
$journal->transaction_currency_id = $currency->id; $journal->transaction_currency_id = $currency->id;
}
++$this->count; ++$this->count;
$journal->save(); $journal->save();
} }
@@ -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;
@@ -51,8 +50,7 @@ class Steam
->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:
@@ -61,8 +59,7 @@ class Steam
->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');
@@ -136,8 +133,7 @@ class Steam
'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;
@@ -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,6 +209,8 @@ 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);
@@ -194,8 +231,7 @@ class Steam
->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()
@@ -203,8 +239,7 @@ class Steam
->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;
@@ -262,8 +297,7 @@ class Steam
'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;
@@ -376,16 +410,14 @@ class Steam
->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')
@@ -393,16 +425,14 @@ class Steam
->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')
@@ -410,8 +440,7 @@ class Steam
->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')
@@ -419,8 +448,7 @@ class Steam
->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".
@@ -591,8 +619,7 @@ class Steam
$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 = [];

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