mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-30 02:26:58 +00:00
Merge pull request #9952 from firefly-iii/speed-up-account-show
Speed up account show
This commit is contained in:
@@ -29,10 +29,11 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\PeriodOverview;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -114,23 +115,45 @@ class ShowController extends Controller
|
||||
$subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
|
||||
|
||||
Log::debug('Start period overview');
|
||||
Timer::start('period-overview');
|
||||
|
||||
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
|
||||
|
||||
Log::debug('End period overview');
|
||||
Timer::stop('period-overview');
|
||||
|
||||
// if layout = v2, overrule the page title.
|
||||
if ('v1' !== config('view.layout')) {
|
||||
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
|
||||
}
|
||||
|
||||
Log::debug('Collect transactions');
|
||||
Timer::start('collection');
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation()->setRange($start, $end);
|
||||
|
||||
$collector
|
||||
->setAccounts(new Collection([$account]))
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withAPIInformation()
|
||||
->setRange($start, $end);
|
||||
// this search will not include transaction groups where this asset account (or liability)
|
||||
// is just part of ONE of the journals. To force this:
|
||||
$collector->setExpandGroupSearch(true);
|
||||
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
|
||||
Log::debug('End collect transactions');
|
||||
Timer::stop('collection');
|
||||
|
||||
// enrich data in arrays.
|
||||
|
||||
// enrich
|
||||
// $enrichment = new TransactionGroupEnrichment();
|
||||
// $enrichment->setUser(auth()->user());
|
||||
// $groups->setCollection($enrichment->enrich($groups->getCollection()));
|
||||
|
||||
|
||||
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
|
||||
$showAll = false;
|
||||
// correct
|
||||
@@ -184,7 +207,7 @@ class ShowController extends Controller
|
||||
$today = today(config('app.timezone'));
|
||||
$accountCurrency = $this->repository->getAccountCurrency($account);
|
||||
$start = $this->repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth();
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.'.$account->accountType->type);
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
|
||||
|
@@ -641,4 +641,34 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
|
||||
|
||||
return $factory->create($data);
|
||||
}
|
||||
|
||||
#[\Override] public function periodCollection(Account $account, Carbon $start, Carbon $end): array
|
||||
{
|
||||
return $account->transactions()
|
||||
->leftJoin('transaction_journals','transaction_journals.id','=','transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_types','transaction_types.id','=','transaction_journals.transaction_type_id')
|
||||
->leftJoin('transaction_currencies','transaction_currencies.id','=','transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies as foreign_currencies','foreign_currencies.id','=','transactions.foreign_currency_id')
|
||||
->where('transaction_journals.date','>=',$start)
|
||||
->where('transaction_journals.date','<=',$end)
|
||||
->get([
|
||||
// currencies
|
||||
'transaction_currencies.id as currency_id',
|
||||
'transaction_currencies.code as currency_code',
|
||||
'transaction_currencies.name as currency_name',
|
||||
'transaction_currencies.symbol as currency_symbol',
|
||||
'transaction_currencies.decimal_places as currency_decimal_places',
|
||||
|
||||
// foreign
|
||||
'foreign_currencies.id as foreign_currency_id',
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.name as foreign_currency_name',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
|
||||
->toArray();
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -71,6 +71,8 @@ interface AccountRepositoryInterface
|
||||
|
||||
public function findByName(string $name, array $types): ?Account;
|
||||
|
||||
public function periodCollection(Account $account, Carbon $start, Carbon $end): array;
|
||||
|
||||
public function getAccountBalances(Account $account): Collection;
|
||||
|
||||
public function getAccountCurrency(Account $account): ?TransactionCurrency;
|
||||
|
@@ -305,9 +305,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
|
||||
|
||||
public function getPiggyBanks(): Collection
|
||||
{
|
||||
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
|
||||
->where('accounts.user_id', $this->user->id)
|
||||
$query = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id');
|
||||
if (null === $this->user) {
|
||||
$query->where('accounts.user_group_id', $this->userGroup->id);
|
||||
}
|
||||
if (null !== $this->user) {
|
||||
$query->where('accounts.user_id', $this->user->id);
|
||||
}
|
||||
return $query
|
||||
->with(
|
||||
[
|
||||
'objectGroups',
|
||||
|
46
app/Support/Debug/Timer.php
Normal file
46
app/Support/Debug/Timer.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* Timer.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Debug;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Timer
|
||||
{
|
||||
private static array $times = [];
|
||||
|
||||
public static function start(string $title): void
|
||||
{
|
||||
self::$times[$title] = microtime(true);
|
||||
}
|
||||
|
||||
public static function stop(string $title): void
|
||||
{
|
||||
$start = self::$times[$title] ?? 0;
|
||||
$end = microtime(true);
|
||||
$diff = $end - $start;
|
||||
unset(self::$times[$title]);
|
||||
Log::debug(sprintf('Timer "%s" took %f seconds', $title, $diff));
|
||||
}
|
||||
|
||||
}
|
@@ -31,9 +31,12 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Trait PeriodOverview.
|
||||
@@ -65,6 +68,7 @@ use Illuminate\Support\Collection;
|
||||
trait PeriodOverview
|
||||
{
|
||||
protected JournalRepositoryInterface $journalRepos;
|
||||
protected AccountRepositoryInterface $accountRepository;
|
||||
|
||||
/**
|
||||
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
|
||||
@@ -75,6 +79,8 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Timer::start('account-period-total');
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
@@ -92,42 +98,20 @@ trait PeriodOverview
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]));
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$earnedSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all income in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]));
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spentSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all transfers in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]));
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
|
||||
$transferSet = $collector->getExtractedJournals();
|
||||
// run a custom query because doing this with the collector is MEGA slow.
|
||||
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
|
||||
|
||||
// loop dates
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferredAway = $this->filterTransferredAway($account, $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']));
|
||||
$transferredIn = $this->filterTransferredIn($account, $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']));
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away',$transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in',$transactions, $currentDate['start'], $currentDate['end']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
@@ -136,9 +120,50 @@ trait PeriodOverview
|
||||
];
|
||||
}
|
||||
$cache->store($entries);
|
||||
Timer::stop('account-period-total');
|
||||
|
||||
return $entries;
|
||||
}
|
||||
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($date >= $start && $date <= $end) {
|
||||
if ($direction === 'away' && bccomp($item['amount'], '0') === -1) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
}
|
||||
if ($direction === 'in' && bccomp($item['amount'], '0') === 1) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [$transactions, $result];
|
||||
}
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if($item['type'] === $type->value && $date >= $start && $date <= $end) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
return [$transactions, $result];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of journals by a set of dates, and then group them by currency.
|
||||
|
@@ -72,7 +72,7 @@ class Preferences
|
||||
|
||||
public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference
|
||||
{
|
||||
Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
|
||||
//Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
|
||||
// don't care about user group ID, except for some specific preferences.
|
||||
$userGroupId = $this->getUserGroupId($user, $name);
|
||||
$query = Preference::where('user_id', $user->id)->where('name', $name);
|
||||
@@ -90,7 +90,7 @@ class Preferences
|
||||
}
|
||||
|
||||
if (null !== $preference) {
|
||||
Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
|
||||
//Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
|
||||
|
||||
return $preference;
|
||||
}
|
||||
|
@@ -32,7 +32,11 @@
|
||||
<td style="width:33%;">{{ 'earned'|_ }}</td>
|
||||
<td style="text-align: right;">
|
||||
<span title="{{ entry.count }}">
|
||||
{% if entry.amount < 0 %}
|
||||
{{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% else %}
|
||||
{{ formatAmountBySymbol(entry.amount, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -58,7 +62,11 @@
|
||||
<td style="width:33%;">{{ 'transferred_away'|_ }}</td>
|
||||
<td style="text-align: right;">
|
||||
<span title="{{ entry.count }}">
|
||||
{% if entry.amount < 0 %}
|
||||
{{ formatAmountBySymbol(entry.amount, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% else %}
|
||||
{{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -71,7 +79,11 @@
|
||||
<td style="width:33%;">{{ 'transferred_in'|_ }}</td>
|
||||
<td style="text-align: right;">
|
||||
<span title="{{ entry.count }}">
|
||||
{% if entry.amount < 0 %}
|
||||
{{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% else %}
|
||||
{{ formatAmountBySymbol(entry.amount, entry.currency_symbol, entry.currency_decimal_places) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
Reference in New Issue
Block a user