diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index d52e414c3a..1547eca723 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use ExpandedForm; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI; use FireflyIII\Support\CacheProperties; use Illuminate\Pagination\LengthAwarePaginator; @@ -222,25 +223,21 @@ class AccountController extends Controller if ($cache->has()) { - $entries = $cache->get(); - - return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); + // $entries = $cache->get(); + // return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } - - $accountCollection = new Collection([$account]); while ($end >= $start) { $end = Navigation::startOfPeriod($end, $range); $currentEnd = Navigation::endOfPeriod($end, $range); - $spent = $repository->spentInPeriod($accountCollection, $end, $currentEnd); - $earned = $repository->earnedInPeriod($accountCollection, $end, $currentEnd); + $spent = $this->spentInPeriod($account, $end, $currentEnd); + $earned = $this->earnedInPeriod($account, $end, $currentEnd); $dateStr = $end->format('Y-m-d'); $dateName = Navigation::periodShow($end, $range); $entries->push([$dateStr, $dateName, $spent, $earned]); $end = Navigation::subtractPeriod($end, $range, 1); } - $cache->store($entries); return view('accounts.show', compact('account', 'what', 'entries', 'subTitleIcon', 'journals', 'subTitle')); } @@ -375,4 +372,68 @@ class AccountController extends Controller return ''; } + /** + * Asset accounts actually earn money by being the destination of a deposit or the destination + * of a transfer. The money moves to them. + * + * A revenue account doesn't really earn money itself. Money is earned "from" the revenue account. + * So, the call to find out how many money has been earned by/from a revenue account is slightly different. + * + * + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + private function earnedInPeriod(Account $account, Carbon $start, Carbon $end) + { + /** @var ARI $repository */ + $repository = app(ARI::class); + $collection = new Collection([$account]); + $type = $account->accountType->type; + switch ($type) { + case AccountType::DEFAULT: + case AccountType::ASSET: + return $repository->earnedInPeriod($collection, $start, $end); + case AccountType::REVENUE: + return $repository->earnedFromInPeriod($collection, $start, $end); + default: + return '0'; + } + } + + /** + * Asset accounts actually spend money by being the source of a withdrawal or the source + * of a transfer. The money moves away from them. + * + * An expense account doesn't really spend money itself. Money is spent "at" the expense account. + * So, the call to find out how many money has been spent on/at an expense account is slightly different. + * + * + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + private function spentInPeriod(Account $account, Carbon $start, Carbon $end): string + { + /** @var ARI $repository */ + $repository = app(ARI::class); + $collection = new Collection([$account]); + $type = $account->accountType->type; + switch ($type) { + case AccountType::DEFAULT: + case AccountType::ASSET: + return $repository->spentInPeriod($collection, $start, $end); + case AccountType::EXPENSE: + return $repository->spentAtInPeriod($collection, $start, $end); + default: + return '0'; + } + } + } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 9e7ea99f6b..96ceb39d3b 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -73,6 +73,48 @@ class AccountRepository implements AccountRepositoryInterface return true; } + /** + * This method is almost the same as ::earnedInPeriod, but only works for revenue accounts + * instead of the implied asset accounts for ::earnedInPeriod. ::earnedInPeriod will tell you + * how much money was earned by the given asset accounts. This method will tell you how much money + * these given revenue accounts sent. Ie. how much money was made FROM these revenue accounts. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedFromInPeriod(Collection $accounts, Carbon $start, Carbon $end): string + { + $query = $this->user->transactionjournals()->expanded()->sortCorrectly() + ->transactionTypes([TransactionType::DEPOSIT]); + + if ($end >= $start) { + $query->before($end)->after($start); + } + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); + } + ); + $query->whereIn('source.account_id', $accountIds); + + } + // remove group by + $query->getQuery()->getQuery()->groups = null; + + // that should do it: + $sum = strval($query->sum('source.amount')); + $sum = bcmul($sum, '-1'); + + return $sum; + + } + /** * @param Collection $accounts * @param Carbon $start @@ -529,6 +571,47 @@ class AccountRepository implements AccountRepositoryInterface return $journal; } + /** + * This method is almost the same as ::spentInPeriod, but only works for expense accounts + * instead of the implied asset accounts for ::spentInPeriod. ::spentInPeriod will tell you + * how much money was spent by the given asset accounts. This method will tell you how much money + * these given expense accounts received. Ie. how much money was spent AT these expense accounts. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentAtInPeriod(Collection $accounts, Carbon $start, Carbon $end): string + { + /** @var HasMany $query */ + $query = $this->user->transactionjournals()->expanded()->sortCorrectly() + ->transactionTypes([TransactionType::WITHDRAWAL]); + if ($end >= $start) { + $query->before($end)->after($start); + } + + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $query->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); + } + ); + $query->whereIn('destination.account_id', $accountIds); + + } + // remove group by + $query->getQuery()->getQuery()->groups = null; + + // that should do it: + $sum = strval($query->sum('destination.amount')); + $sum = bcmul($sum, '-1'); + + return $sum; + } + /** * @param Collection $accounts * @param Carbon $start diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 7238a81086..45fe15a939 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -33,6 +33,20 @@ interface AccountRepositoryInterface */ public function destroy(Account $account, Account $moveTo): bool; + /** + * This method is almost the same as ::earnedInPeriod, but only works for revenue accounts + * instead of the implied asset accounts for ::earnedInPeriod. ::earnedInPeriod will tell you + * how much money was earned by the given asset accounts. This method will tell you how much money + * these given revenue accounts sent. Ie. how much money was made FROM these revenue accounts. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedFromInPeriod(Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param Collection $accounts * @param Carbon $start @@ -42,6 +56,21 @@ interface AccountRepositoryInterface */ public function earnedInPeriod(Collection $accounts, Carbon $start, Carbon $end): string; + /** + * This method will call AccountRepositoryInterface::journalsInPeriod and get all withdrawaks made from the given $accounts, + * as well as the transfers that move away from those $accounts. This is a slightly sharper selection + * than made by journalsInPeriod itself. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @see AccountRepositoryInterface::journalsInPeriod + * + * @return Collection + */ + public function expensesInPeriod(Collection $accounts, Carbon $start, Carbon $end): Collection; + /** * @param int $accountId * @@ -107,21 +136,6 @@ interface AccountRepositoryInterface */ public function getSavingsAccounts(Carbon $start, Carbon $end): Collection; - /** - * This method will call AccountRepositoryInterface::journalsInPeriod and get all withdrawaks made from the given $accounts, - * as well as the transfers that move away from those $accounts. This is a slightly sharper selection - * than made by journalsInPeriod itself. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @see AccountRepositoryInterface::journalsInPeriod - * - * @return Collection - */ - public function expensesInPeriod(Collection $accounts, Carbon $start, Carbon $end): Collection; - /** * This method will call AccountRepositoryInterface::journalsInPeriod and get all deposits made to the given $accounts, * as well as the transfers that move to to those $accounts. This is a slightly sharper selection @@ -182,6 +196,20 @@ interface AccountRepositoryInterface */ public function openingBalanceTransaction(Account $account) : TransactionJournal; + /** + * This method is almost the same as ::spentInPeriod, but only works for expense accounts + * instead of the implied asset accounts for ::spentInPeriod. ::spentInPeriod will tell you + * how much money was spent by the given asset accounts. This method will tell you how much money + * these given expense accounts received. Ie. how much money was spent AT these expense accounts. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentAtInPeriod(Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param Collection $accounts * @param Carbon $start diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index 29511b80d7..ee96ad85cf 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -52,7 +52,7 @@ class Journal extends Twig_Extension // check if this sum is the same as the journal: $journalSum = TransactionJournal::amount($journal); $full = Amount::formatJournal($journal); - if (bccomp($journalSum, $amount) === 0) { + if (bccomp($journalSum, $amount) === 0 || bccomp(bcmul($journalSum, '-1'), $amount) === 0) { $cache->store($full); return $full; @@ -79,6 +79,16 @@ class Journal extends Twig_Extension { return new Twig_SimpleFunction( 'formatBudgetPerspective', function (TransactionJournal $journal, Budget $budget) { + + $cache = new CacheProperties; + $cache->addProperty('formatBudgetPerspective'); + $cache->addProperty($journal->id); + $cache->addProperty($budget->id); + + if ($cache->has()) { + return $cache->get(); + } + // get the account amount: $transactions = $journal->transactions()->where('transactions.amount', '<', 0)->get(['transactions.*']); $amount = '0'; @@ -88,10 +98,17 @@ class Journal extends Twig_Extension $amount = bcadd($amount, strval($transaction->amount)); } } + if ($amount === '0') { + $formatted = Amount::formatJournal($journal); + $cache->store($formatted); - $formatted = Amount::format($amount, true); + return $formatted; + } - return $formatted . ' (' . Amount::formatJournal($journal) . ')'; + $formatted = Amount::format($amount, true) . ' (' . Amount::formatJournal($journal) . ')'; + $cache->store($formatted); + + return $formatted; } ); }