From 2d67a3159d7707639b47ffb5ae0d83e5c243284c Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 26 Dec 2014 21:08:44 +0100 Subject: [PATCH] Expanded reports --- app/controllers/ReportController.php | 20 +- .../2014_12_24_191544_changes_for_v322.php | 37 +- app/lib/FireflyIII/Report/Report.php | 345 +++++++++++++++++- app/lib/FireflyIII/Report/ReportInterface.php | 73 +++- app/views/list/journals-small.blade.php | 11 +- app/views/reports/month.blade.php | 106 +++++- 6 files changed, 557 insertions(+), 35 deletions(-) diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php index 81e7de76b8..a759ba17cc 100644 --- a/app/controllers/ReportController.php +++ b/app/controllers/ReportController.php @@ -62,18 +62,20 @@ class ReportController extends BaseController } catch (Exception $e) { View::make('error')->with('message', 'Invalid date'); } - $date = new Carbon($year . '-' . $month . '-01'); + $date = new Carbon($year . '-' . $month . '-01'); $subTitle = 'Report for ' . $date->format('F Y'); $subTitleIcon = 'fa-calendar'; - $income = $this->_repository->getIncomeForMonth($date,false); + $displaySum = true; // to show sums in report. + $income = $this->_repository->getIncomeForMonth($date); + $expenses = $this->_repository->getExpenseGroupedForMonth($date, 10); + $budgets = $this->_repository->getBudgetsForMonth($date); + $categories = $this->_repository->getCategoriesForMonth($date, 10); + $accounts = $this->_repository->getAccountsForMonth($date); -// var_dump($income->toArray()); -// exit; - - - - - return View::make('reports.month', compact('date', 'subTitle', 'subTitleIcon','income')); + return View::make( + 'reports.month', + compact('accounts', 'categories', 'budgets', 'expenses', 'subTitle', 'displaySum', 'subTitleIcon', 'income') + ); } /** diff --git a/app/database/migrations/2014_12_24_191544_changes_for_v322.php b/app/database/migrations/2014_12_24_191544_changes_for_v322.php index 143c4fe376..4d400e97b7 100644 --- a/app/database/migrations/2014_12_24_191544_changes_for_v322.php +++ b/app/database/migrations/2014_12_24_191544_changes_for_v322.php @@ -13,7 +13,29 @@ class ChangesForV322 extends Migration */ public function down() { - // TODO + // rename tables: + Schema::rename('piggy_bank_repetitions', 'piggybank_repetitions'); + Schema::rename('piggy_banks', 'piggybanks'); + + // rename fields + Schema::table( + 'piggy_bank_events', function (Blueprint $table) { + $table->renameColumn('piggy_bank_id', 'piggybank_id'); + } + ); + + Schema::table( + 'piggybank_repetitions', function (Blueprint $table) { + $table->renameColumn('piggy_bank_id', 'piggybank_id'); + } + ); + + // remove soft delete to piggy banks + Schema::table( + 'piggybanks', function (Blueprint $table) { + $table->dropSoftDeletes(); + } + ); } @@ -28,6 +50,19 @@ class ChangesForV322 extends Migration Schema::rename('piggybank_repetitions', 'piggy_bank_repetitions'); Schema::rename('piggybanks', 'piggy_banks'); + // drop an invalid index. + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropIndex('limits_component_id_startdate_repeat_freq_unique'); + } + ); + // recreate it the correct way: + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->unique(['budget_id', 'startdate', 'repeat_freq'], 'unique_bl_combi'); + } + ); + // rename fields Schema::table( 'piggy_bank_events', function (Blueprint $table) { diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index 0b59efcc4c..eb3afeb88b 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -6,7 +6,9 @@ use Carbon\Carbon; use FireflyIII\Database\Account\Account as AccountRepository; use FireflyIII\Database\SwitchUser; use FireflyIII\Database\TransactionJournal\TransactionJournal as JournalRepository; +use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use stdClass; // todo add methods to itnerface @@ -80,6 +82,273 @@ class Report implements ReportInterface } + /** + * @param Carbon $date + * + * @return Collection + */ + public function getAccountsForMonth(Carbon $date) + { + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + $list = \Auth::user()->accounts() + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', "accountRole"); + } + ) + ->whereIn('account_types.type', ['Default account', 'Cash account', 'Asset account']) + ->where('active', 1) + ->where( + function ($query) { + $query->where('account_meta.data', '!=', '"sharedExpense"'); + $query->orWhereNull('account_meta.data'); + } + ) + ->get(['accounts.*']); + $list->each( + function (\Account $account) use ($start, $end) { + $account->startBalance = \Steam::balance($account, $start); + $account->endBalance = \Steam::balance($account, $end); + $account->difference = $account->endBalance - $account->startBalance; + } + ); + + return $list; + } + + /** + * @param Carbon $date + * + * @return Collection + */ + public function getBudgetsForMonth(Carbon $date) + { + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + // all budgets + /** @var Collection $budgets */ + $budgets = \Auth::user()->budgets() + ->leftJoin( + 'budget_limits', function (JoinClause $join) use ($date) { + $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); + } + ) + ->get(['budgets.*', 'budget_limits.amount as budget_amount']); + $amounts = \Auth::user()->transactionjournals() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('budgets.id') + ->orderBy('name','ASC') + ->get(['budgets.id', 'budgets.name', \DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + + + $spentNoBudget = 0; + foreach ($budgets as $budget) { + $budget->spent = 0; + foreach ($amounts as $amount) { + if (intval($budget->id) == intval($amount->id)) { + $budget->spent = floatval($amount->sum) * -1; + } + if (is_null($amount->id)) { + $spentNoBudget = floatval($amount->sum) * -1; + } + } + } + + $noBudget = new stdClass; + $noBudget->id = 0; + $noBudget->name = '(no budget)'; + $noBudget->budget_amount = 0; + $noBudget->spent = $spentNoBudget; + + // also get transfers to expense accounts (which are without a budget, and grouped). + $transfers = $this->getTransfersToSharedAccounts($date); + foreach($transfers as $transfer) { + $noBudget->spent += floatval($transfer->sum) * -1; + } + + + $budgets->push($noBudget); + + return $budgets; + } + + /** + * @param Carbon $date + * @param int $limit + * + * @return Collection + */ + public function getCategoriesForMonth(Carbon $date, $limit = 15) + { + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + // all categories. + $amounts = \Auth::user()->transactionjournals() + ->leftJoin( + 'category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id' + ) + ->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('categories.id') + ->orderBy('sum') + ->get(['categories.id', 'categories.name', \DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + $spentNoCategory = 0; + foreach ($amounts as $amount) { + if (is_null($amount->id)) { + $spentNoCategory = floatval($amount->sum) * -1; + } + } + $noCategory = new stdClass; + $noCategory->id = 0; + $noCategory->name = '(no category)'; + $noCategory->sum = $spentNoCategory; + $amounts->push($noCategory); + + $return = new Collection; + $bottom = new stdClass(); + $bottom->name = 'Others'; + $bottom->id = 0; + $bottom->sum = 0; + + foreach ($amounts as $index => $entry) { + if ($index < $limit) { + $return->push($entry); + } else { + $bottom->sum += floatval($entry->sum); + } + } + $return->push($bottom); + + return $return; + } + + /** + * @param Carbon $date + * @param int $limit + * + * @return Collection + */ + public function getExpenseGroupedForMonth(Carbon $date, $limit = 15) + { + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + $userId = $this->_accounts->getUser()->id; + + $set = \TransactionJournal:: + leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where( + 'transactions.amount', '>', 0 + ); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'transactions AS otherTransactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'otherTransactions.transaction_journal_id')->where( + 'otherTransactions.amount', '<', 0 + ); + } + ) + ->leftJoin('accounts as otherAccounts', 'otherAccounts.id', '=', 'otherTransactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('otherAccounts.id', '=', 'account_meta.account_id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->where('date', '>=', $start->format('Y-m-d')) + ->where('date', '<=', $end->format('Y-m-d')) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->where('transaction_types.type', 'Withdrawal') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $userId) + ->groupBy('account_id') + ->orderBy('sum', 'ASC') + ->get( + [ + 'transactions.account_id', + 'accounts.name', + \DB::Raw('SUM(`transactions`.`amount`) * -1 AS `sum`') + ] + ); + $transfers = $this->getTransfersToSharedAccounts($date); + // merge $transfers into $set + foreach ($transfers as $transfer) { + if (!is_null($transfer->account_id)) { + $set->push($transfer); + } + } + // sort the list. + $set = $set->sortBy( + function ($entry) { + return floatval($entry->sum); + } + ); + $return = new Collection; + $bottom = new stdClass(); + $bottom->name = 'Others'; + $bottom->account_id = 0; + $bottom->sum = 0; + + $count = 0; + foreach ($set as $entry) { + if ($count < $limit) { + $return->push($entry); + } else { + $bottom->sum += floatval($entry->sum); + } + $count++; + } + + $return->push($bottom); + + return $return; + + } + /** * @param Carbon $date * @param bool $shared @@ -95,15 +364,87 @@ class Report implements ReportInterface $userId = $this->_accounts->getUser()->id; $list = \TransactionJournal::withRelevantData() + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) ->transactionTypes(['Deposit']) - ->where('user_id', $userId) - ->orderBy('date','DESC') + ->where('transaction_journals.user_id', $userId) + ->where('transactions.amount', '>', 0) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->orderBy('date', 'ASC') ->before($end)->after($start)->get(['transaction_journals.*']); + // incoming from a shared account: it's profit (income): + $transfers = \TransactionJournal::withRelevantData() + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->transactionTypes(['Transfer']) + ->where('transaction_journals.user_id', $userId) + ->where('transactions.amount', '<', 0) + ->where('account_meta.data', '=', '"sharedExpense"') + ->orderBy('date', 'ASC') + ->before($end)->after($start)->get(['transaction_journals.*']); + + $list = $list->merge($transfers); + $list->sort( + function (\TransactionJournal $journal) { + return $journal->date->format('U'); + } + ); return $list; } + /** + * @param Carbon $date + * + * @return Collection + */ + public function getTransfersToSharedAccounts(Carbon $date) + { + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + + return \TransactionJournal:: + leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where( + 'transactions.amount', '>', 0 + ); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->where('account_meta.data', '"sharedExpense"') + ->where('date', '>=', $start->format('Y-m-d')) + ->where('date', '<=', $end->format('Y-m-d')) + ->where('transaction_types.type', 'Transfer') + ->where('transaction_journals.user_id', \Auth::user()->id) + ->get( + [ + 'transactions.account_id', + 'accounts.name', + \DB::Raw('SUM(`transactions`.`amount`) * -1 AS `sum`') + ] + ); + } + /** * @param Carbon $start * diff --git a/app/lib/FireflyIII/Report/ReportInterface.php b/app/lib/FireflyIII/Report/ReportInterface.php index 7cef787cb4..c3dbc57b03 100644 --- a/app/lib/FireflyIII/Report/ReportInterface.php +++ b/app/lib/FireflyIII/Report/ReportInterface.php @@ -15,21 +15,57 @@ interface ReportInterface /** * @param Carbon $start * @param Carbon $end - * @param int $limit - * - * @return Collection - */ - public function revenueGroupedByAccount(Carbon $start, Carbon $end, $limit = 15); - - /** - * @param Carbon $start - * @param Carbon $end - * @param int $limit + * @param int $limit * * @return Collection */ public function expensesGroupedByAccount(Carbon $start, Carbon $end, $limit = 15); + /** + * @param Carbon $date + * + * @return Collection + */ + public function getBudgetsForMonth(Carbon $date); + + /** + * @param Carbon $date + * + * @return Collection + */ + public function getTransfersToSharedAccounts(Carbon $date); + + /** + * @param Carbon $date + * @param int $limit + * + * @return Collection + */ + public function getCategoriesForMonth(Carbon $date, $limit = 15); + + /** + * @param Carbon $date + * + * @return Collection + */ + public function getAccountsForMonth(Carbon $date); + + /** + * @param Carbon $date + * @param int $limit + * + * @return Collection + */ + public function getExpenseGroupedForMonth(Carbon $date, $limit = 15); + + /** + * @param Carbon $date + * @param bool $shared + * + * @return Collection + */ + public function getIncomeForMonth(Carbon $date, $shared = false); + /** * @param Carbon $start * @@ -44,18 +80,19 @@ interface ReportInterface */ public function listOfYears(Carbon $start); + /** + * @param Carbon $start + * @param Carbon $end + * @param int $limit + * + * @return Collection + */ + public function revenueGroupedByAccount(Carbon $start, Carbon $end, $limit = 15); + /** * @param Carbon $date * * @return array */ public function yearBalanceReport(Carbon $date); - - /** - * @param Carbon $date - * @param bool $shared - * - * @return Collection - */ - public function getIncomeForMonth(Carbon $date, $shared = false); } \ No newline at end of file diff --git a/app/views/list/journals-small.blade.php b/app/views/list/journals-small.blade.php index 31af0154ff..02ea6c7939 100644 --- a/app/views/list/journals-small.blade.php +++ b/app/views/list/journals-small.blade.php @@ -1,10 +1,13 @@ + @foreach($journals as $journal) + - @endforeach + @if(isset($displaySum) && $displaySum === true) + + + + + + @endif
{{{$journal->description}}} + transactions[1]->amount);?> @if($journal->transactiontype->type == 'Withdrawal') {{mf($journal->transactions[1]->amount,false)}} @endif @@ -26,6 +29,12 @@ @endif
Sum{{mf($tableSum)}}
\ No newline at end of file diff --git a/app/views/reports/month.blade.php b/app/views/reports/month.blade.php index 44ff3e529a..796de9cd9c 100644 --- a/app/views/reports/month.blade.php +++ b/app/views/reports/month.blade.php @@ -11,7 +11,24 @@
Expenses (top 10)
-
Body
+ + + @foreach($expenses as $expense) + sum);?> + + @if($expense->account_id != 0) + + @else + + @endif + + + @endforeach + + + + +
{{{$expense->name}}}{{{$expense->name}}}{{mf($expense->sum)}}
Sum{{mf($sum)}}
@@ -19,13 +36,69 @@
Budgets
-
Body
+ + + + + + + + + @foreach($budgets as $budget) + spent);?> + + + + + + + @endforeach + + + + +
BudgetEnvelopeSpentLeft
+ @if($budget->id > 0) + {{{$budget->name}}} + @else + {{{$budget->name}}} + @endif + {{mf($budget->budget_amount)}}{{mf($budget->spent,false)}}{{mf(floatval($budget->budget_amount) - floatval($budget->spent))}}
Sum{{mf($sum)}}
+
+ This list does not take in account outgoing transfers to shared accounts. +
Categories
-
Body
+ + + + + + + @foreach($categories as $category) + sum);?> + + + + + @endforeach + + + + +
CategorySpent
+ @if($category->id > 0) + {{{$category->name}}} + @else + {{{$category->name}}} + @endif + {{mf($category->sum,false)}}
Sum{{mf($sum)}}
+
+ This list does not take in account outgoing transfers to shared accounts. +
@@ -33,7 +106,32 @@
Accounts
-
Body
+ + + @foreach($accounts as $account) + startBalance; + $sumEnd += $account->endBalance; + $sumDiff += $account->difference; + ?> + + + + + + + @endforeach + + + + + + +
{{{$account->name}}}{{mf($account->startBalance)}}{{mf($account->endBalance)}}{{mf($account->difference)}}
Sum{{mf($sumStart)}}{{mf($sumEnd)}}{{mf($sumDiff)}}