From 15846e157b5e19c03c27fd54a46063715189fbe4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 27 Dec 2015 21:17:04 +0100 Subject: [PATCH] From 200+ queries back to ~17. --- .../Controllers/Chart/BudgetController.php | 59 ++++++------ app/Models/Budget.php | 2 +- app/Repositories/Budget/BudgetRepository.php | 89 +++++++++++++++---- .../Budget/BudgetRepositoryInterface.php | 11 +++ .../Shared/ComponentRepository.php | 31 ++++--- 5 files changed, 131 insertions(+), 61 deletions(-) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 14298452cc..d178dc3724 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -211,11 +211,8 @@ class BudgetController extends Controller */ public function frontpage(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository) { - $budgets = $repository->getBudgets(); - $start = Session::get('start', Carbon::now()->startOfMonth()); - $end = Session::get('end', Carbon::now()->endOfMonth()); - $allEntries = new Collection; - $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + $start = Session::get('start', Carbon::now()->startOfMonth()); + $end = Session::get('end', Carbon::now()->endOfMonth()); // chart properties for cache: $cache = new CacheProperties(); @@ -227,42 +224,48 @@ class BudgetController extends Controller return Response::json($cache->get()); // @codeCoverageIgnore } + $budgets = $repository->getBudgetsAndLimitsInRange($start, $end); + $allEntries = new Collection; + $accounts = $accountRepository->getAccounts(['Default account', 'Asset account', 'Cash account']); + + bcscale(2); /** @var Budget $budget */ foreach ($budgets as $budget) { - $repetitions = $repository->getBudgetLimitRepetitions($budget, $start, $end); - if ($repetitions->count() == 0) { - $expenses = $repository->balanceInPeriod($budget, $start, $end, $accounts) * -1; - $allEntries->push([$budget->name, 0, 0, $expenses, 0, 0]); - continue; + // we already have amount, startdate and enddate. + // if this "is" a limit repetition (as opposed to a budget without one entirely) + // depends on whether startdate and enddate are null. + if (is_null($budget->startdate) && is_null($budget->enddate)) { + $name = $budget->name . ' (' . $start->formatLocalized(trans('config.month')) . ')'; + $currentStart = clone $start; + $currentEnd = clone $end; + $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + $amount = 0; + $left = 0; + $spent = $expenses; + $overspent = 0; + } else { + $name = $budget->name . ' (' . $budget->startdate->formatLocalized(trans('config.month')) . ')'; + $currentStart = clone $budget->startdate; + $currentEnd = clone $budget->enddate; + $expenses = $repository->balanceInPeriod($budget, $currentStart, $currentEnd, $accounts); + $amount = $budget->amount; + // smaller than 1 means spent MORE than budget allows. + $left = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? 0 : bcadd($budget->amount, $expenses); + $spent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? $amount : $expenses; + $overspent = bccomp(bcadd($budget->amount, $expenses), '0') < 1 ? bcadd($budget->amount, $expenses) : 0; } - /** @var LimitRepetition $repetition */ - foreach ($repetitions as $repetition) { - $expenses = $repository->balanceInPeriod($budget, $repetition->startdate, $repetition->enddate, $accounts) * -1; - // $left can be less than zero. - // $overspent can be more than zero ( = overspending) - $left = max(bcsub($repetition->amount, $expenses), 0); // limited at zero. - $overspent = max(bcsub($expenses, $repetition->amount), 0); // limited at zero. - $name = $budget->name; - - // $spent is maxed to the repetition amount: - $spent = $expenses > $repetition->amount ? $repetition->amount : $expenses; - - - $allEntries->push([$name, $left, $spent, $overspent, $repetition->amount, $expenses]); - } + $allEntries->push([$name, $left, $spent, $overspent, $amount, $expenses]); } $noBudgetExpenses = $repository->getWithoutBudgetSum($start, $end) * -1; $allEntries->push([trans('firefly.noBudget'), 0, 0, $noBudgetExpenses, 0, 0]); - $data = $this->generator->frontpage($allEntries); $cache->store($data); return Response::json($data); - } /** @@ -291,7 +294,7 @@ class BudgetController extends Controller return Response::json($cache->get()); // @codeCoverageIgnore } -// filter empty budgets: + // filter empty budgets: foreach ($allBudgets as $budget) { $spent = $repository->balanceInPeriod($budget, $start, $end, $accounts); if ($spent != 0) { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 5184b576e6..1435b50cf9 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -78,7 +78,7 @@ class Budget extends Model */ public function getDates() { - return ['created_at', 'updated_at', 'deleted_at']; + return ['created_at', 'updated_at', 'deleted_at','startdate','enddate']; } /** diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 2338cc8436..15069ba946 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -4,13 +4,16 @@ namespace FireflyIII\Repositories\Budget; use Auth; use Carbon\Carbon; +use DB; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Shared\ComponentRepository; use FireflyIII\Support\CacheProperties; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Builder as QueryBuilder; +use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Input; @@ -76,6 +79,50 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn return $set; } + /** + * Returns a list of budgets, budget limits and limit repetitions + * (doubling any of them in a left join) + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end) + { + /** @var Collection $set */ + $set = Auth::user() + ->budgets() + ->leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') + ->leftJoin('limit_repetitions', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->where( + function (Builder $query) use ($start, $end) { + $query->where( + function (Builder $query) use ($start, $end) { + $query->where('limit_repetitions.startdate', '>=', $start->format('Y-m-d')); + $query->where('limit_repetitions.startdate', '<=', $end->format('Y-m-d')); + } + ); + $query->orWhere( + function (Builder $query) { + $query->whereNull('limit_repetitions.startdate'); + $query->whereNull('limit_repetitions.enddate'); + } + ); + } + ) + ->get(['budgets.*', 'limit_repetitions.startdate', 'limit_repetitions.enddate', 'limit_repetitions.amount']); + + $set = $set->sortBy( + function (Budget $budget) { + return strtolower($budget->name); + } + ); + + return $set; + + } + /** * @param Budget $budget * @param Carbon $start @@ -247,6 +294,7 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn /** * @deprecated + * * @param Budget $budget * @param Carbon $date * @@ -294,25 +342,30 @@ class BudgetRepository extends ComponentRepository implements BudgetRepositoryIn */ public function getWithoutBudgetSum(Carbon $start, Carbon $end) { - $noBudgetSet = Auth::user() - ->transactionjournals() - ->whereNotIn( - 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { - $query - ->select('transaction_journals.id') - ->from('transaction_journals') - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) - ->whereNotNull('budget_transaction_journal.budget_id'); - } - ) - ->after($start) - ->before($end) - ->transactionTypes([TransactionType::WITHDRAWAL]) - ->get(['transaction_journals.*'])->sum('amount'); + $entry = Auth::user() + ->transactionjournals() + ->whereNotIn( + 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { + $query + ->select('transaction_journals.id') + ->from('transaction_journals') + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')) + ->whereNotNull('budget_transaction_journal.budget_id'); + } + ) + ->after($start) + ->before($end) + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->first([DB::Raw('SUM(`transactions`.`amount`) as `journalAmount`')]); - return $noBudgetSet; + return $entry->journalAmount; } /** diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 2e886960bf..39ad90b82f 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -63,6 +63,17 @@ interface BudgetRepositoryInterface */ public function getBudgets(); + /** + * Returns a list of budgets, budget limits and limit repetitions + * (doubling any of them in a left join) + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getBudgetsAndLimitsInRange(Carbon $start, Carbon $end); + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Shared/ComponentRepository.php b/app/Repositories/Shared/ComponentRepository.php index 8a198fe14a..99037c015c 100644 --- a/app/Repositories/Shared/ComponentRepository.php +++ b/app/Repositories/Shared/ComponentRepository.php @@ -3,10 +3,10 @@ namespace FireflyIII\Repositories\Shared; use Carbon\Carbon; +use DB; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionType; use FireflyIII\Support\CacheProperties; -use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; /** @@ -35,27 +35,30 @@ class ComponentRepository $cache->addProperty($accounts); $cache->addProperty('balanceInPeriodList'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $ids = []; /** @var Account $account */ foreach ($accounts as $account) { $ids[] = $account->id; } - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - $sum = $object->transactionjournals() - ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->before($end) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->whereIn('accounts.id', $ids) - ->after($start) - ->get(['transaction_journals.*'])->sum('amount'); - $cache->store($sum); + $entry = $object->transactionjournals() + ->transactionTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) + ->before($end) + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->whereIn('accounts.id', $ids) + ->after($start) + ->first([DB::Raw('SUM(`transactions`.`amount`) as `journalAmount`')]); + $amount = $entry->journalAmount; - return $sum; + $cache->store($amount); + + return $amount; } }