diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php
new file mode 100644
index 0000000000..3d709dcc2b
--- /dev/null
+++ b/app/Helpers/Report/ReportHelper.php
@@ -0,0 +1,176 @@
+_queries->journalsByExpenseAccount($start, $end);
+ $array = $this->_helper->makeArray($result);
+ $limited = $this->_helper->limitArray($array, $limit);
+
+ return $limited;
+
+ }
+
+ /**
+ * @return Carbon
+ */
+ public function firstDate()
+ {
+ $journal = Auth::user()->transactionjournals()->orderBy('date', 'ASC')->first();
+ if ($journal) {
+ return $journal->date;
+ }
+
+ return Carbon::now();
+ }
+
+ /**
+ * This method gets some kind of list for a monthly overview.
+ *
+ * @param Carbon $date
+ *
+ * @return Collection
+ */
+ public function getBudgetsForMonth(Carbon $date)
+ {
+ $start = clone $date;
+ $start->startOfMonth();
+ $end = clone $date;
+ $end->endOfMonth();
+ // all budgets
+ $set = \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 amount']);
+
+
+
+ $budgets = $this->_helper->makeArray($set);
+ $amountSet = $this->_queries->journalsByBudget($start, $end);
+ $amounts = $this->_helper->makeArray($amountSet);
+ $combined = $this->_helper->mergeArrays($budgets, $amounts);
+ $combined[0]['spent'] = isset($combined[0]['spent']) ? $combined[0]['spent'] : 0.0;
+ $combined[0]['amount'] = isset($combined[0]['amount']) ? $combined[0]['amount'] : 0.0;
+ $combined[0]['name'] = 'No budget';
+
+ // find transactions to shared expense accounts, which are without a budget by default:
+ $transfers = $this->_queries->sharedExpenses($start, $end);
+ foreach ($transfers as $transfer) {
+ $combined[0]['spent'] += floatval($transfer->amount) * -1;
+ }
+
+ return $combined;
+ }
+
+ /**
+ * @param Carbon $date
+ *
+ * @return array
+ */
+ public function listOfMonths(Carbon $date)
+ {
+ $start = clone $date;
+ $end = Carbon::now();
+ $months = [];
+ while ($start <= $end) {
+ $months[] = [
+ 'formatted' => $start->format('F Y'),
+ 'month' => intval($start->format('m')),
+ 'year' => intval($start->format('Y')),
+ ];
+ $start->addMonth();
+ }
+
+ return $months;
+ }
+
+ /**
+ * @param Carbon $date
+ *
+ * @return array
+ */
+ public function listOfYears(Carbon $date)
+ {
+ $start = clone $date;
+ $end = Carbon::now();
+ $years = [];
+ while ($start <= $end) {
+ $years[] = $start->format('Y');
+ $start->addYear();
+ }
+
+ return $years;
+ }
+
+ /**
+ * @param Carbon $date
+ *
+ * @return array
+ */
+ public function yearBalanceReport(Carbon $date)
+ {
+ $start = clone $date;
+ $end = clone $date;
+ $sharedAccounts = [];
+ $sharedCollection = \Auth::user()->accounts()
+ ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
+ ->where('account_meta.name', '=', 'accountRole')
+ ->where('account_meta.data', '=', json_encode('sharedExpense'))
+ ->get(['accounts.id']);
+
+ foreach ($sharedCollection as $account) {
+ $sharedAccounts[] = $account->id;
+ }
+
+ $accounts = \Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->get()->filter(
+ function (Account $account) use ($sharedAccounts) {
+ if (!in_array($account->id, $sharedAccounts)) {
+ return $account;
+ }
+
+ return null;
+ }
+ );
+ $report = [];
+ $start->startOfYear()->subDay();
+ $end->endOfYear();
+
+ foreach ($accounts as $account) {
+ $report[] = [
+ 'start' => \Steam::balance($account, $start),
+ 'end' => \Steam::balance($account, $end),
+ 'account' => $account,
+ 'shared' => $account->accountRole == 'sharedExpense'
+ ];
+ }
+
+ return $report;
+ }
+}
\ No newline at end of file
diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php
new file mode 100644
index 0000000000..2284523ff8
--- /dev/null
+++ b/app/Helpers/Report/ReportHelperInterface.php
@@ -0,0 +1,62 @@
+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 (Builder $query) {
+ $query->where('account_meta.data', '!=', '"sharedExpense"');
+ $query->orWhereNull('account_meta.data');
+ }
+ )
+ ->get(['accounts.*']);
+ }
+
+ /**
+ * This method returns all "income" journals in a certain period, which are both transfers from a shared account
+ * and "ordinary" deposits. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does
+ * not group and returns different fields.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function incomeByPeriod(Carbon $start, Carbon $end)
+ {
+ return TransactionJournal::
+ leftJoin(
+ 'transactions as t_from', function (JoinClause $join) {
+ $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_from', 't_from.account_id', '=', 'ac_from.id')
+ ->leftJoin(
+ 'account_meta as acm_from', function (JoinClause $join) {
+ $join->on('ac_from.id', '=', 'acm_from.account_id')->where('acm_from.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin(
+ 'transactions as t_to', function (JoinClause $join) {
+ $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_to', 't_to.account_id', '=', 'ac_to.id')
+ ->leftJoin(
+ 'account_meta as acm_to', function (JoinClause $join) {
+ $join->on('ac_to.id', '=', 'acm_to.account_id')->where('acm_to.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where(
+ function ($query) {
+ $query->where(
+ function ($q) {
+ $q->where('transaction_types.type', 'Deposit');
+ $q->where('acm_to.data', '!=', '"sharedExpense"');
+ }
+ );
+ $query->orWhere(
+ function ($q) {
+ $q->where('transaction_types.type', 'Transfer');
+ $q->where('acm_from.data', '=', '"sharedExpense"');
+ }
+ );
+ }
+ )
+ ->before($end)->after($start)
+ ->where('transaction_journals.user_id', Auth::user()->id)
+ ->groupBy('t_from.account_id')->orderBy('transaction_journals.date')
+ ->get(
+ ['transaction_journals.id',
+ 'transaction_journals.description',
+ 'transaction_journals.encrypted',
+ 'transaction_types.type',
+ 't_to.amount', 'transaction_journals.date', 't_from.account_id as account_id',
+ 'ac_from.name as name']
+ );
+ }
+
+ /**
+ * Gets a list of expenses grouped by the budget they were filed under.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsByBudget(Carbon $start, Carbon $end)
+ {
+ return \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('budgets.name', 'ASC')
+ ->get(['budgets.id', 'budgets.name', \DB::Raw('SUM(`transactions`.`amount`) AS `spent`')]);
+ }
+
+ /**
+ * Gets a list of categories and the expenses therein, grouped by the relevant category.
+ * This result excludes transfers to shared accounts which are expenses, technically.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsByCategory(Carbon $start, Carbon $end)
+ {
+ return \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('amount')
+ ->get(['categories.id', 'categories.name', \DB::Raw('SUM(`transactions`.`amount`) AS `amount`')]);
+
+ }
+
+ /**
+ * Gets a list of expense accounts and the expenses therein, grouped by that expense account.
+ * This result excludes transfers to shared accounts which are expenses, technically.
+ *
+ * So now it will include them!
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsByExpenseAccount(Carbon $start, Carbon $end)
+ {
+ return TransactionJournal::
+ leftJoin(
+ 'transactions as t_from', function (JoinClause $join) {
+ $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_from', 't_from.account_id', '=', 'ac_from.id')
+ ->leftJoin(
+ 'account_meta as acm_from', function (JoinClause $join) {
+ $join->on('ac_from.id', '=', 'acm_from.account_id')->where('acm_from.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin(
+ 'transactions as t_to', function (JoinClause $join) {
+ $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_to', 't_to.account_id', '=', 'ac_to.id')
+ ->leftJoin(
+ 'account_meta as acm_to', function (JoinClause $join) {
+ $join->on('ac_to.id', '=', 'acm_to.account_id')->where('acm_to.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where(
+ function ($query) {
+ $query->where(
+ function ($q) {
+ $q->where('transaction_types.type', 'Withdrawal');
+ $q->where('acm_from.data', '!=', '"sharedExpense"');
+ }
+ );
+ $query->orWhere(
+ function ($q) {
+ $q->where('transaction_types.type', 'Transfer');
+ $q->where('acm_to.data', '=', '"sharedExpense"');
+ }
+ );
+ }
+ )
+ ->before($end)
+ ->after($start)
+ ->where('transaction_journals.user_id', Auth::user()->id)
+ ->groupBy('t_to.account_id')
+ ->orderBy('amount', 'DESC')
+ ->get(['t_to.account_id as id', 'ac_to.name as name', DB::Raw('SUM(t_to.amount) as `amount`')]);
+ }
+
+ /**
+ * This method returns all deposits into asset accounts, grouped by the revenue account,
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function journalsByRevenueAccount(Carbon $start, Carbon $end)
+ {
+ return TransactionJournal::
+ leftJoin(
+ 'transactions as t_from', function (JoinClause $join) {
+ $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_from', 't_from.account_id', '=', 'ac_from.id')
+ ->leftJoin(
+ 'account_meta as acm_from', function (JoinClause $join) {
+ $join->on('ac_from.id', '=', 'acm_from.account_id')->where('acm_from.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin(
+ 'transactions as t_to', function (JoinClause $join) {
+ $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0);
+ }
+ )
+ ->leftJoin('accounts as ac_to', 't_to.account_id', '=', 'ac_to.id')
+ ->leftJoin(
+ 'account_meta as acm_to', function (JoinClause $join) {
+ $join->on('ac_to.id', '=', 'acm_to.account_id')->where('acm_to.name', '=', 'accountRole');
+ }
+ )
+ ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->where(
+ function ($query) {
+ $query->where(
+ function ($q) {
+ $q->where('transaction_types.type', 'Deposit');
+ $q->where('acm_to.data', '!=', '"sharedExpense"');
+ }
+ );
+ $query->orWhere(
+ function ($q) {
+ $q->where('transaction_types.type', 'Transfer');
+ $q->where('acm_from.data', '=', '"sharedExpense"');
+ }
+ );
+ }
+ )
+ ->before($end)->after($start)
+ ->where('transaction_journals.user_id', Auth::user()->id)
+ ->groupBy('t_from.account_id')->orderBy('amount')
+ ->get(['t_from.account_id as account_id', 'ac_from.name as name', DB::Raw('SUM(t_from.amount) as `amount`')]);
+ }
+
+ /**
+ * With an equally misleading name, this query returns are transfers to shared accounts. These are considered
+ * expenses.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function sharedExpenses(Carbon $start, Carbon $end)
+ {
+ 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"')
+ ->after($start)
+ ->before($end)
+ ->where('transaction_types.type', 'Transfer')
+ ->where('transaction_journals.user_id', \Auth::user()->id)
+ ->get(
+ ['transaction_journals.id', 'transaction_journals.description', 'transactions.account_id', 'accounts.name',
+ 'transactions.amount']
+ );
+
+ }
+
+ /**
+ * With a slightly misleading name, this query returns all transfers to shared accounts
+ * which are technically expenses, since it won't be just your money that gets spend.
+ *
+ * @param Carbon $start
+ * @param Carbon $end
+ *
+ * @return Collection
+ */
+ public function sharedExpensesByCategory(Carbon $start, Carbon $end)
+ {
+ 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');
+ }
+ )
+ ->leftJoin(
+ 'category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'
+ )
+ ->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id')
+ ->where('account_meta.data', '"sharedExpense"')
+ ->after($start)
+ ->before($end)
+ ->where('transaction_types.type', 'Transfer')
+ ->where('transaction_journals.user_id', \Auth::user()->id)
+ ->groupBy('categories.name')
+ ->get(
+ [
+ 'categories.id',
+ 'categories.name as name',
+ \DB::Raw('SUM(`transactions`.`amount`) * -1 AS `amount`')
+ ]
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php
new file mode 100644
index 0000000000..3d90390fe8
--- /dev/null
+++ b/app/Helpers/Report/ReportQueryInterface.php
@@ -0,0 +1,106 @@
+with('message', 'Invalid year.');
+ }
+ $chart->addColumn('Month', 'date');
+ $chart->addColumn('Income', 'number');
+ $chart->addColumn('Expenses', 'number');
+
+ // get report query interface.
+
+ $end = clone $start;
+ $end->endOfYear();
+ while ($start < $end) {
+ $currentEnd = clone $start;
+ $currentEnd->endOfMonth();
+ // total income:
+ $income = $query->incomeByPeriod($start, $currentEnd);
+ $incomeSum = 0;
+ foreach ($income as $entry) {
+ $incomeSum += floatval($entry->amount);
+ }
+
+ // total expenses:
+ $expense = $query->journalsByExpenseAccount($start, $currentEnd);
+ $expenseSum = 0;
+ foreach ($expense as $entry) {
+ $expenseSum += floatval($entry->amount);
+ }
+
+ $chart->addRow(clone $start, $incomeSum, $expenseSum);
+ $start->addMonth();
+ }
+
+
+ $chart->generate();
+
+ return Response::json($chart->getData());
+
+ }
+
+ /**
+ *
+ * @param $year
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function yearInExpSum($year, GChart $chart, ReportQueryInterface $query)
+ {
+ try {
+ $start = new Carbon('01-01-' . $year);
+ } catch (Exception $e) {
+ return view('error')->with('message', 'Invalid year.');
+ }
+ $chart->addColumn('Summary', 'string');
+ $chart->addColumn('Income', 'number');
+ $chart->addColumn('Expenses', 'number');
+
+ $income = 0;
+ $expense = 0;
+ $count = 0;
+
+ $end = clone $start;
+ $end->endOfYear();
+ while ($start < $end) {
+ $currentEnd = clone $start;
+ $currentEnd->endOfMonth();
+ // total income:
+ $incomeResult = $query->incomeByPeriod($start, $currentEnd);
+ $incomeSum = 0;
+ foreach ($incomeResult as $entry) {
+ $incomeSum += floatval($entry->amount);
+ }
+
+ // total expenses:
+ $expenseResult = $query->journalsByExpenseAccount($start, $currentEnd);
+ $expenseSum = 0;
+ foreach ($expenseResult as $entry) {
+ $expenseSum += floatval($entry->amount);
+ }
+
+ $income += $incomeSum;
+ $expense += $expenseSum;
+ $count++;
+ $start->addMonth();
+ }
+
+
+ $chart->addRow('Sum', $income, $expense);
+ $count = $count > 0 ? $count : 1;
+ $chart->addRow('Average', ($income / $count), ($expense / $count));
+
+ $chart->generate();
+
+ return Response::json($chart->getData());
+
+ }
+
+
+ /**
+ * @param int $year
+ *
+ * @return $this|\Illuminate\Http\JsonResponse
+ */
+ public function allBudgetsAndSpending($year, GChart $chart, BudgetRepositoryInterface $repository)
+ {
+ try {
+ new Carbon('01-01-' . $year);
+ } catch (Exception $e) {
+ return view('error')->with('message', 'Invalid year.');
+ }
+ $budgets = Auth::user()->budgets()->get();
+ $budgets->sortBy('name');
+ $chart->addColumn('Month', 'date');
+ foreach ($budgets as $budget) {
+ $chart->addColumn($budget->name, 'number');
+ }
+ $start = Carbon::createFromDate(intval($year), 1, 1);
+ $end = clone $start;
+ $end->endOfYear();
+
+
+ while ($start <= $end) {
+ $row = [clone $start];
+ foreach ($budgets as $budget) {
+ $spent = $repository->spentInMonth($budget, $start);
+ $row[] = $spent;
+ }
+ $chart->addRowArray($row);
+ $start->addMonth();
+ }
+
+
+ $chart->generate();
+
+ return Response::json($chart->getData());
+
+ }
+
}
diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php
new file mode 100644
index 0000000000..f0adba9b4c
--- /dev/null
+++ b/app/Http/Controllers/ReportController.php
@@ -0,0 +1,206 @@
+firstDate();
+ $months = $helper->listOfMonths($start);
+ $years = $helper->listOfYears($start);
+ $title = 'Reports';
+ $mainTitleIcon = 'fa-line-chart';
+
+ return view('reports.index', compact('years', 'months', 'title', 'mainTitleIcon'));
+ }
+
+ /**
+ * @param string $year
+ * @param string $month
+ *
+ * @return \Illuminate\View\View
+ */
+ public function month($year = '2014', $month = '1', ReportQueryInterface $query)
+ {
+ try {
+ new Carbon($year . '-' . $month . '-01');
+ } catch (Exception $e) {
+ return View::make('error')->with('message', 'Invalid date.');
+ }
+ $date = new Carbon($year . '-' . $month . '-01');
+ $subTitle = 'Report for ' . $date->format('F Y');
+ $subTitleIcon = 'fa-calendar';
+ $displaySum = true; // to show sums in report.
+
+
+ /**
+ *
+ * get income for month (date)
+ *
+ */
+
+ $start = clone $date;
+ $start->startOfMonth();
+ $end = clone $date;
+ $end->endOfMonth();
+
+ /**
+ * Start getIncomeForMonth DONE
+ */
+ $income = $query->incomeByPeriod($start, $end);
+ /**
+ * End getIncomeForMonth DONE
+ */
+ /**
+ * Start getExpenseGroupedForMonth DONE
+ */
+ $set = $query->journalsByExpenseAccount($start, $end);
+ $expenses = Steam::makeArray($set);
+ $expenses = Steam::sortArray($expenses);
+ $expenses = Steam::limitArray($expenses, 10);
+ /**
+ * End getExpenseGroupedForMonth DONE
+ */
+ /**
+ * Start getBudgetsForMonth DONE
+ */
+ $set = 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 amount']);
+ $budgets = Steam::makeArray($set);
+ $amountSet = $query->journalsByBudget($start, $end);
+ $amounts = Steam::makeArray($amountSet);
+ $budgets = Steam::mergeArrays($budgets, $amounts);
+ $budgets[0]['spent'] = isset($budgets[0]['spent']) ? $budgets[0]['spent'] : 0.0;
+ $budgets[0]['amount'] = isset($budgets[0]['amount']) ? $budgets[0]['amount'] : 0.0;
+ $budgets[0]['name'] = 'No budget';
+
+ // find transactions to shared expense accounts, which are without a budget by default:
+ $transfers = $query->sharedExpenses($start, $end);
+ foreach ($transfers as $transfer) {
+ $budgets[0]['spent'] += floatval($transfer->amount) * -1;
+ }
+
+ /**
+ * End getBudgetsForMonth DONE
+ */
+ /**
+ * Start getCategoriesForMonth DONE
+ */
+ // all categories.
+ $result = $query->journalsByCategory($start, $end);
+ $categories = Steam::makeArray($result);
+
+ // all transfers
+ $result = $query->sharedExpensesByCategory($start, $end);
+ $transfers = Steam::makeArray($result);
+ $merged = Steam::mergeArrays($categories, $transfers);
+
+ // sort.
+ $sorted = Steam::sortNegativeArray($merged);
+
+ // limit to $limit:
+ $categories = Steam::limitArray($sorted, 10);
+ /**
+ * End getCategoriesForMonth DONE
+ */
+ /**
+ * Start getAccountsForMonth
+ */
+ $list = $query->accountList();
+ $accounts = [];
+ /** @var Account $account */
+ foreach ($list as $account) {
+ $id = intval($account->id);
+ /** @noinspection PhpParamsInspection */
+ $accounts[$id] = [
+ 'name' => $account->name,
+ 'startBalance' => Steam::balance($account, $start),
+ 'endBalance' => Steam::balance($account, $end)
+ ];
+
+ $accounts[$id]['difference'] = $accounts[$id]['endBalance'] - $accounts[$id]['startBalance'];
+ }
+
+ /**
+ * End getAccountsForMonth
+ */
+
+
+ return View::make(
+ 'reports.month',
+ compact(
+ 'income', 'expenses', 'budgets', 'accounts', 'categories',
+ 'date', 'subTitle', 'displaySum', 'subTitleIcon'
+ )
+ );
+ }
+
+ /**
+ * @param $year
+ *
+ * @return $this
+ */
+ public function year($year, ReportHelperInterface $helper, ReportQueryInterface $query)
+ {
+ try {
+ new Carbon('01-01-' . $year);
+ } catch (Exception $e) {
+ return View::make('error')->with('message', 'Invalid date.');
+ }
+ $date = new Carbon('01-01-' . $year);
+ $end = clone $date;
+ $end->endOfYear();
+ $title = 'Reports';
+ $subTitle = $year;
+ $subTitleIcon = 'fa-bar-chart';
+ $mainTitleIcon = 'fa-line-chart';
+ $balances = $helper->yearBalanceReport($date);
+ $groupedIncomes = $query->journalsByRevenueAccount($date, $end);
+ $groupedExpenses = $query->journalsByExpenseAccount($date, $end);
+
+ //$groupedExpenses = $helper-> expensesGroupedByAccount($date, $end, 15);
+
+ return View::make(
+ 'reports.year', compact('date', 'groupedIncomes', 'groupedExpenses', 'year', 'balances', 'title', 'subTitle', 'subTitleIcon', 'mainTitleIcon')
+ );
+ }
+
+
+}
diff --git a/app/Http/routes.php b/app/Http/routes.php
index 0a6fab87ea..955a9e6867 100644
--- a/app/Http/routes.php
+++ b/app/Http/routes.php
@@ -149,9 +149,11 @@ Route::group(
Route::get('/chart/account/{account}/{view?}', ['uses' => 'GoogleChartController@accountBalanceChart']);
Route::get('/chart/budget/{budget}/spending/{year?}', ['uses' => 'GoogleChartController@budgetsAndSpending']);
+ Route::get('/chart/budgets/spending/{year?}', ['uses' => 'GoogleChartController@allBudgetsAndSpending']);
Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'GoogleChartController@budgetLimitSpending']);
- //Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']);
- //Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']);
+
+ Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']);
+ Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']);
//Route::get('/chart/bills/{bill}', ['uses' => 'GoogleChartController@billOverview']);
@@ -191,9 +193,9 @@ Route::group(
* Report Controller
*/
Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']);
- //Route::get('/reports/{year}', ['uses' => 'ReportController@year', 'as' => 'reports.year']);
- //Route::get('/reports/{year}/{month}', ['uses' => 'ReportController@month', 'as' => 'reports.month']);
- //Route::get('/reports/budget/{year}/{month}', ['uses' => 'ReportController@budget', 'as' => 'reports.budget']);
+ Route::get('/reports/{year}', ['uses' => 'ReportController@year', 'as' => 'reports.year']);
+ Route::get('/reports/{year}/{month}', ['uses' => 'ReportController@month', 'as' => 'reports.month']);
+ Route::get('/reports/budget/{year}/{month}', ['uses' => 'ReportController@budget', 'as' => 'reports.budget']);
/**
* Search Controller
diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php
index 26ab9d0ebd..49c7c2907a 100644
--- a/app/Providers/FireflyServiceProvider.php
+++ b/app/Providers/FireflyServiceProvider.php
@@ -56,12 +56,14 @@ class FireflyServiceProvider extends ServiceProvider
}
);
- // preferences
$this->app->bind('FireflyIII\Repositories\Account\AccountRepositoryInterface', 'FireflyIII\Repositories\Account\AccountRepository');
$this->app->bind('FireflyIII\Repositories\Budget\BudgetRepositoryInterface', 'FireflyIII\Repositories\Budget\BudgetRepository');
$this->app->bind('FireflyIII\Repositories\Category\CategoryRepositoryInterface', 'FireflyIII\Repositories\Category\CategoryRepository');
$this->app->bind('FireflyIII\Repositories\Journal\JournalRepositoryInterface', 'FireflyIII\Repositories\Journal\JournalRepository');
+ $this->app->bind('FireflyIII\Helpers\Report\ReportHelperInterface', 'FireflyIII\Helpers\Report\ReportHelper');
+ $this->app->bind('FireflyIII\Helpers\Report\ReportQueryInterface', 'FireflyIII\Helpers\Report\ReportQuery');
+
}
}
\ No newline at end of file
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index 2f0acb6b61..750ac26777 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -4,6 +4,7 @@ namespace FireflyIII\Support;
use Carbon\Carbon;
use FireflyIII\Models\Account;
+use Illuminate\Support\Collection;
/**
* Class Steam
@@ -31,4 +32,133 @@ class Steam
return $balance;
}
+ /**
+ * Turns a collection into an array. Needs the field 'id' for the key,
+ * and saves only 'name' and 'amount' as a sub array.
+ *
+ * @param Collection $collection
+ *
+ * @return array
+ */
+ public function makeArray(Collection $collection)
+ {
+ $array = [];
+ foreach ($collection as $entry) {
+ $entry->spent = isset($entry->spent) ? floatval($entry->spent) : 0.0;
+ $id = intval($entry->id);
+ if (isset($array[$id])) {
+ $array[$id]['amount'] += floatval($entry->amount);
+ $array[$id]['spent'] += floatval($entry->spent);
+ } else {
+ $array[$id] = [
+ 'amount' => floatval($entry->amount),
+ 'spent' => floatval($entry->spent),
+ 'name' => $entry->name
+ ];
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Merges two of the arrays as defined above. Can't handle more (yet)
+ *
+ * @param array $one
+ * @param array $two
+ *
+ * @return array
+ */
+ public function mergeArrays(array $one, array $two)
+ {
+ foreach ($two as $id => $value) {
+ // $otherId also exists in $one:
+ if (isset($one[$id])) {
+ $one[$id]['amount'] += $value['amount'];
+ $one[$id]['spent'] += $value['spent'];
+ } else {
+ $one[$id] = $value;
+ }
+ }
+
+ return $one;
+ }
+
+ /**
+ * Sort an array where all 'amount' keys are positive floats.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function sortArray(array $array)
+ {
+ uasort(
+ $array, function ($left, $right) {
+ if ($left['amount'] == $right['amount']) {
+ return 0;
+ }
+
+ return ($left['amount'] < $right['amount']) ? 1 : -1;
+ }
+ );
+
+ return $array;
+
+ }
+
+ /**
+ * Only return the top X entries, group the rest by amount
+ * and described as 'Others'. id = 0 as well
+ *
+ * @param array $array
+ * @param int $limit
+ *
+ * @return array
+ */
+ public function limitArray(array $array, $limit = 10)
+ {
+ $others = [
+ 'name' => 'Others',
+ 'amount' => 0
+ ];
+ $return = [];
+ $count = 0;
+ foreach ($array as $id => $entry) {
+ if ($count < ($limit - 1)) {
+ $return[$id] = $entry;
+ } else {
+ $others['amount'] += $entry['amount'];
+ }
+
+ $count++;
+ }
+ $return[0] = $others;
+
+ return $return;
+
+ }
+
+ /**
+ * Sort an array where all 'amount' keys are negative floats.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function sortNegativeArray(array $array)
+ {
+ uasort(
+ $array, function ($left, $right) {
+ if ($left['amount'] == $right['amount']) {
+ return 0;
+ }
+
+ return ($left['amount'] < $right['amount']) ? -1 : 1;
+ }
+ );
+
+ return $array;
+ }
+
}
\ No newline at end of file
diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php
index 34a6acfd61..b48e34dade 100644
--- a/database/seeds/TestDataSeeder.php
+++ b/database/seeds/TestDataSeeder.php
@@ -1,12 +1,12 @@
$user->id, 'account_type_id' => $assetType->id, 'name' => 'Savings account', 'active' => 1]);
$acc_c = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Delete me', 'active' => 1]);
+ // create account meta:
+ $meta_a = AccountMeta::create(['account_id' => $acc_a->id, 'name' => 'accountRole', 'data' => 'defaultExpense']);
+ $meta_b = AccountMeta::create(['account_id' => $acc_b->id, 'name' => 'accountRole', 'data' => 'defaultExpense']);
+ $meta_c = AccountMeta::create(['account_id' => $acc_c->id, 'name' => 'accountRole', 'data' => 'defaultExpense']);
+// var_dump($meta_a->toArray());
+// var_dump($meta_b->toArray());
+// var_dump($meta_c->toArray());
+
$acc_d = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Checking account initial balance', 'active' => 0]);
$acc_e = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Savings account initial balance', 'active' => 0]);
$acc_f = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Delete me initial balance', 'active' => 0]);
@@ -215,9 +223,9 @@ class TestDataSeeder extends Seeder
);
// and because we have no filters, some repetitions:
-// LimitRepetition::create(['budget_limit_id' => $groceriesLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 201]);
-// LimitRepetition::create(['budget_limit_id' => $billsLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 202]);
-// LimitRepetition::create(['budget_limit_id' => $deleteMeLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 203]);
+ // LimitRepetition::create(['budget_limit_id' => $groceriesLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 201]);
+ // LimitRepetition::create(['budget_limit_id' => $billsLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 202]);
+ // LimitRepetition::create(['budget_limit_id' => $deleteMeLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 203]);
}
/**
diff --git a/public/js/reports.js b/public/js/reports.js
new file mode 100644
index 0000000000..4dee6cbbdd
--- /dev/null
+++ b/public/js/reports.js
@@ -0,0 +1,9 @@
+if (typeof(google) != 'undefined') {
+ google.setOnLoadCallback(drawChart);
+ function drawChart() {
+ googleColumnChart('chart/reports/income-expenses/' + year, 'income-expenses-chart');
+ googleColumnChart('chart/reports/income-expenses-sum/' + year, 'income-expenses-sum-chart')
+
+ googleStackedColumnChart('chart/budgets/spending/' + year, 'budgets');
+ }
+}
\ No newline at end of file
diff --git a/resources/views/reports/budget.blade.php b/resources/views/reports/budget.blade.php
new file mode 100644
index 0000000000..bab3a63918
--- /dev/null
+++ b/resources/views/reports/budget.blade.php
@@ -0,0 +1,156 @@
+@extends('layouts.default')
+@section('content')
+{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $date) }}
+
+
+
+
+ Account |
+ Start of month |
+ Current balance |
+ Spent |
+
+ @foreach($accounts as $account)
+
+ {{{$account->name}}} |
+ {{Amount::format($account->startBalance)}} |
+ {{Amount::format($account->endBalance)}} |
+ {{Amount::format($account->startBalance - $account->endBalance,false)}} |
+
+ @endforeach
+
+
+
+
+
+
+
+
+ Budgets |
+
+ @foreach($accounts as $account)
+ {{{$account->name}}} |
+ id] = 0;
+ ?>
+ @endforeach
+
+ Left in budget
+ |
+
+ @foreach($budgets as $id => $budget)
+
+ {{{$budget['name']}}}
+ @if($id == 0)
+
+ @endif
+ |
+ {{Amount::format($budget['amount'])}} |
+
+ @foreach($accounts as $account)
+ @if(isset($account->budgetInformation[$id]))
+
+ @if($id == 0)
+ {{Amount::format($account->budgetInformation[$id]['amount'])}}
+ @else
+ {{Amount::format($account->budgetInformation[$id]['amount'])}}
+ @endif
+ |
+ budgetInformation[$id]['amount']);
+ $accountSums[$account->id] += floatval($account->budgetInformation[$id]['amount']);
+ ?>
+ @else
+ {{Amount::format(0)}} |
+ @endif
+ @endforeach
+ {{Amount::format($budget['amount'] + $budget['spent'])}} |
+ {{Amount::format($budget['amount'] + $spent)}} |
+
+ @endforeach
+
+ Without budget
+
+ |
+ @foreach($accounts as $account)
+ @if(isset($account->budgetInformation[0]))
+
+ {{Amount::format($account->budgetInformation[0]['amount'])}}
+ |
+ @else
+ {{Amount::format(0)}} |
+ @endif
+ @endforeach
+ |
+
+
+ Balanced by transfers |
+ @foreach($accounts as $account)
+
+ {{Amount::format($account->balancedAmount)}}
+ |
+ @endforeach
+ |
+
+
+
+ Left unbalanced |
+ @foreach($accounts as $account)
+ id] += $account->balancedAmount;
+ ?>
+ @if(isset($account->budgetInformation[0]))
+
+ {{Amount::format($account->budgetInformation[0]['amount'] + $account->balancedAmount)}}
+ |
+ @else
+ {{Amount::format(0)}} |
+ @endif
+ @endforeach
+ |
+
+
+ Sum |
+ @foreach($accounts as $account)
+ {{Amount::format($accountSums[$account->id])}} |
+ @endforeach
+ |
+
+
+ Expected balance |
+ @foreach($accounts as $account)
+ {{Amount::format($account->startBalance + $accountSums[$account->id])}} |
+ @endforeach
+ |
+
+
+
+
+
+
+
+
+
+
+
+@stop
+@section('scripts')
+{{HTML::script('assets/javascript/firefly/reports.js')}}
+@stop
diff --git a/resources/views/reports/index.blade.php b/resources/views/reports/index.blade.php
new file mode 100644
index 0000000000..13cc5236e3
--- /dev/null
+++ b/resources/views/reports/index.blade.php
@@ -0,0 +1,50 @@
+@extends('layouts.default')
+@section('content')
+{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!}
+
+
+
+
+ Yearly reports
+
+
+
+ @foreach($years as $year)
+ - {{$year}}
+ @endforeach
+
+
+
+
+
+
+
+
+ Monthly reports
+
+
+
+
+
+
+
+@stop
diff --git a/resources/views/reports/month.blade.php b/resources/views/reports/month.blade.php
new file mode 100644
index 0000000000..c9cef65b18
--- /dev/null
+++ b/resources/views/reports/month.blade.php
@@ -0,0 +1,235 @@
+@extends('layouts.default')
+@section('content')
+{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $date) !!}
+
+
+
+
Income
+
+
+ @foreach($income as $entry)
+
+
+ @if($entry->encrypted === true)
+ {{{Crypt::decrypt($entry->description)}}}
+ @else
+ {{{$entry->description}}}
+ @endif
+ |
+
+ amount);?>
+ @if($entry->type == 'Withdrawal')
+ {{Amount::format($entry->amount,false)}}
+ @endif
+ @if($entry->type == 'Deposit')
+ {{Amount::format($entry->amount,false)}}
+ @endif
+ @if($entry->type == 'Transfer')
+ {{Amount::format($entry->amount,false)}}
+ @endif
+ |
+
+ {{$entry->date->format('j F Y')}}
+ |
+
+ {{{$entry->name}}}
+ |
+
+ @endforeach
+ @if(isset($displaySum) && $displaySum === true)
+
+ Sum |
+ {!! Amount::format($tableSum) !!} |
+
+
+ @endif
+
+
+
+
+
+
Expenses (top 10)
+
+
+ @foreach($expenses as $id => $expense)
+
+
+ @if($id > 0)
+ {{{$expense['name']}}} |
+ @else
+ {{{$expense['name']}}} |
+ @endif
+ {!! Amount::format($expense['amount']) !!} |
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sum) !!} |
+
+
+
+
+
+
+
Sums
+ transactions[1]->amount);
+ }
+ ?>
+
+
+ In |
+ {!! Amount::format($in) !!} |
+
+
+ Out |
+ {!! Amount::format($sum) !!} |
+
+
+ Difference |
+ {!! Amount::format($in - $sum) !!} |
+
+
+
+
+
+
+
+
+
Budgets
+
+
+ Budget |
+ Envelope |
+ Spent |
+ Left |
+
+
+ @foreach($budgets as $id => $budget)
+
+
+
+ @if($id > 0)
+ {{{$budget['name']}}}
+ @else
+ {{{$budget['name']}}}
+ @endif
+ |
+ {!! Amount::format($budget['amount']) !!} |
+ {!! Amount::format($budget['spent'],false) !!} |
+ {!! Amount::format($budget['amount'] + $budget['spent']) !!} |
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sumEnvelope) !!} |
+ {!! Amount::format($sumSpent) !!} |
+ {!! Amount::format($sumLeft) !!} |
+
+
+
+
+
+
+
Categories
+
+
+ Category |
+ Spent |
+
+
+ @foreach($categories as $id => $category)
+
+
+
+ @if($id > 0)
+ {{{$category['name']}}}
+ @else
+ {{{$category['name']}}}
+ @endif
+ |
+ {!! Amount::format($category['amount'],false) !!} |
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sum) !!} |
+
+
+
+
+
+
+
+
+
Accounts
+
+
+ @foreach($accounts as $id => $account)
+
+
+ {{{$account['name']}}} |
+ {!! Amount::format($account['startBalance']) !!} |
+ {!! Amount::format($account['endBalance']) !!} |
+ {!! Amount::format($account['difference']) !!} |
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sumStart) !!} |
+ {!! Amount::format($sumEnd) !!} |
+ {!! Amount::format($sumDiff) !!} |
+
+
+
+
+
+
+
+
+
+
Repeated expenses
+
Body
+
+
+
+
+
+
+
+
Outside of budgets
+
Body
+
+
+
+@stop
diff --git a/resources/views/reports/year.blade.php b/resources/views/reports/year.blade.php
new file mode 100644
index 0000000000..75ad2b879f
--- /dev/null
+++ b/resources/views/reports/year.blade.php
@@ -0,0 +1,170 @@
+@extends('layouts.default')
+@section('content')
+{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $date) !!}
+
+
+
+
+ Income vs. expenses
+
+
+
+
+
+
+
+ Income vs. expenses
+
+
+
+
+
+
+
+
+
+
+ Account balance
+
+
+
+ @foreach($balances as $balance)
+
+
+
+ {{{$balance['account']->name}}}
+ @if($balance['shared'])
+ shared
+ @endif
+ |
+ {!! Amount::format($balance['start']) !!} |
+ {!! Amount::format($balance['end']) !!} |
+ {!! Amount::format($balance['end']-$balance['start']) !!} |
+
+ @endforeach
+
+ Sum of sums |
+ {!! Amount::format($start) !!} |
+ {!! Amount::format($end) !!} |
+ {!! Amount::format($diff) !!} |
+
+
+
+
+
+
+ Income vs. expense
+
+ amount);
+ }
+ foreach($groupedExpenses as $exp) {
+ $expenseSum += floatval($exp['amount']);
+ }
+ $incomeSum = floatval($incomeSum*-1);
+
+ ?>
+
+
+
+ In |
+ {!! Amount::format($incomeSum) !!} |
+
+
+ Out |
+ {!! Amount::format($expenseSum*-1) !!} |
+
+
+ Difference |
+ {!! Amount::format($incomeSum - $expenseSum) !!} |
+
+
+
+
+
+
+
+ Income
+
+
+
+ @foreach($groupedIncomes as $income)
+ amount)*-1;?>
+
+ {{{$income->name}}} |
+ {!! Amount::format(floatval($income->amount)*-1) !!} |
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sum) !!} |
+
+
+
+
+
+
+
+ Expenses
+
+
+
+ @foreach($groupedExpenses as $id => $expense)
+
+ {{{$expense['name']}}} |
+ {!! Amount::format(floatval($expense['amount'])*-1) !!} |
+
+
+ @endforeach
+
+ Sum |
+ {!! Amount::format($sum) !!} |
+
+
+
+
+
+
+
+
+@stop
+@section('scripts')
+
+
+
+
+
+
+
+
+
+@stop