First start making the category report also multi-currency

This commit is contained in:
James Cole
2019-09-02 21:47:20 +02:00
parent 206dae87ba
commit e75c15a61b
9 changed files with 399 additions and 474 deletions

View File

@@ -95,7 +95,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
->render();
} catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.category.month: %s', $e->getMessage()));
$result = 'Could not render report view.';
$result = sprintf('Could not render report view: %s', $e->getMessage());
}
return $result;

View File

@@ -116,6 +116,8 @@ class BudgetController extends Controller
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function accounts(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end)
{
@@ -392,6 +394,8 @@ class BudgetController extends Controller
'currency_name' => $currency['currency_name'],
'currency_symbol' => $currency['currency_symbol'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'budget_id' => $budget['id'],
'budget_name' => $budget['name'],
];
}
}

View File

@@ -24,6 +24,7 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Category\NoCategoryRepositoryInterface;
@@ -41,6 +42,27 @@ class CategoryController extends Controller
{
use BasicDataSupport;
/** @var OperationsRepositoryInterface */
private $opsRepository;
/**
* ExpenseReportController constructor.
*
* @codeCoverageIgnore
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->opsRepository = app(OperationsRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Show overview of expenses in category.
*
@@ -146,6 +168,61 @@ class CategoryController extends Controller
return $result;
}
/**
* @param Collection $accounts
* @param Collection $categories
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function accounts(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{
$spent = $this->opsRepository->listExpenses($start, $end, $accounts, $categories);
$report = [];
$sums = [];
/** @var Account $account */
foreach ($accounts as $account) {
$accountId = $account->id;
$report[$accountId] = $report[$accountId] ?? [
'name' => $account->name,
'id' => $account->id,
'iban' => $account->iban,
'currencies' => [],
];
}
// loop expenses.
foreach ($spent as $currency) {
$currencyId = $currency['currency_id'];
$sums[$currencyId] = $sums[$currencyId] ?? [
'currency_id' => $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_name' => $currency['currency_name'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'sum' => '0',
];
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$sourceAccountId = $journal['source_account_id'];
$report[$sourceAccountId]['currencies'][$currencyId] = $report[$sourceAccountId]['currencies'][$currencyId] ?? [
'currency_id' => $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_name' => $currency['currency_name'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'sum' => '0',
];
$report[$sourceAccountId]['currencies'][$currencyId]['sum'] = bcadd(
$report[$sourceAccountId]['currencies'][$currencyId]['sum'], $journal['amount']
);
$sums[$currencyId]['sum'] = bcadd($sums[$currencyId]['sum'], $journal['amount']);
}
}
}
return view('reports.category.partials.accounts', compact('sums', 'report'));
}
/**
* Show overview of income in category.

View File

@@ -78,7 +78,7 @@ class OperationsRepository implements OperationsRepositoryInterface
if (null === $categories || (null !== $categories && 0 === $categories->count())) {
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation();
$collector->withCategoryInformation()->withAccountInformation();
$journals = $collector->getExtractedJournals();
$array = [];
@@ -115,8 +115,9 @@ class OperationsRepository implements OperationsRepositoryInterface
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
'amount' => app('steam')->negative($journal['amount']),
'date' => $journal['date'],
'amount' => app('steam')->negative($journal['amount']),
'date' => $journal['date'],
'source_account_id' => $journal['source_account_id'],
];
}
@@ -155,8 +156,8 @@ class OperationsRepository implements OperationsRepositoryInterface
$array = [];
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$categoryId = (int)$journal['category_id'];
$currencyId = (int)$journal['currency_id'];
$categoryId = (int)$journal['category_id'];
$categoryName = (string)$journal['category_name'];
// catch "no category" entries.

View File

@@ -17,28 +17,26 @@
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** global: categoryIncomeUri, categoryExpenseUri, accountIncomeUri, accountExpenseUri, mainUri */
$(function () {
"use strict";
drawChart();
$('#categories-in-pie-chart-checked').on('change', function () {
redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart');
});
$('#categories-out-pie-chart-checked').on('change', function () {
redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart');
});
$('#accounts-in-pie-chart-checked').on('change', function () {
redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart');
});
$('#accounts-out-pie-chart-checked').on('change', function () {
redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart');
});
// $('#categories-in-pie-chart-checked').on('change', function () {
// redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart');
// });
//
// $('#categories-out-pie-chart-checked').on('change', function () {
// redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart');
// });
//
// $('#accounts-in-pie-chart-checked').on('change', function () {
// redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart');
// });
//
// $('#accounts-out-pie-chart-checked').on('change', function () {
// redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart');
// });
});
@@ -46,28 +44,21 @@ $(function () {
function drawChart() {
"use strict";
loadAjaxPartial('accountsHolder', accountsUri);
// month view:
doubleYChart(mainUri, 'in-out-chart');
//doubleYChart(mainUri, 'in-out-chart');
// draw pie chart of income, depending on "show other transactions too":
redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart');
redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart');
redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart');
redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart');
// redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart');
// redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart');
// redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart');
// redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart');
}
function redrawPieChart(uri, container) {
"use strict";
var checkbox = $('#' + container + '-checked');
var others = '0';
// check if box is checked:
if (checkbox.prop('checked')) {
others = '1';
}
uri = uri.replace('OTHERS', others);
pieChart(uri, container);
multiCurrencyPieChart(uri, container);
}

View File

@@ -4,6 +4,7 @@
<th data-defaultsort="disabled">{{ 'description'|_ }}</th>
<th data-defaultsign="month">{{ 'date'|_ }}</th>
<th data-defaultsign="az">{{ 'account'|_ }}</th>
<th data-defaultsign="az">{{ 'budget'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'amount'|_ }}</th>
</tr>
</thead>
@@ -28,6 +29,11 @@
{{ row.destination_account_name }}
</a>
</td>
<td data-sortable="false">
<a href="{{ route('budgets.show', row.budget_id) }}">
{{ row.budget_name }}
</a>
</td>
<td data-value="{{ row.amount }}" style="text-align: right;">
{{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_decimal_places) }}

View File

@@ -1,434 +1,213 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, accountIds, categoryIds, start, end) }}
{#{{ Breadcrumbs.render(Route.getCurrentRoute.getName, accountIds, budgetIds, start, end) }}#}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-4 col-md-6">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'accounts'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsign="az">{{ 'name'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'earned'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr>
<td data-value="{{ account.name }}">
<a href="{{ route('accounts.show', account.id) }}"
title="{{ account.name }}">{{ account.name }}</a>
</td>
{% if accountSummary[account.id] %}
<td data-value="{{ accountSummary[account.id].earned }}"
style="text-align: right;">{{ accountSummary[account.id].earned|formatAmount }}</td>
{% else %}
<td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td>
{% endif %}
{% if accountSummary[account.id] %}
<td data-value="{{ accountSummary[account.id].spent }}"
style="text-align: right;">{{ accountSummary[account.id].spent|formatAmount }}</td>
{% else %}
<td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td>{{ 'sum'|_ }}</td>
<td style="text-align: right;">{{ accountSummary.sum.earned|formatAmount }}</td>
<td style="text-align: right;">{{ accountSummary.sum.spent|formatAmount }}</td>
</tr>
</tfoot>
</table>
<div class="box-body table-responsive no-padding" id="accountsHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'categories'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsign="az">{{ 'name'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'earned'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td data-value="{{ category.name }}">
<a href="{{ route('categories.show', category.id) }}"
title="{{ category.name }}">{{ category.name }}</a>
</td>
{% if categorySummary[category.id] %}
<td data-value="{{ categorySummary[category.id].earned }}"
style="text-align: right;">{{ categorySummary[category.id].earned|formatAmount }}</td>
{% else %}
<td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td>
{% endif %}
{% if categorySummary[category.id] %}
<td data-value="{{ categorySummary[category.id].spent }}"
style="text-align: right;">{{ categorySummary[category.id].spent|formatAmount }}</td>
{% else %}
<td data-value="0" style="text-align: right;">{{ 0|formatAmount }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td>{{ 'sum'|_ }}</td>
<td style="text-align: right;">{{ categorySummary.sum.earned|formatAmount }}</td>
<td style="text-align: right;">{{ categorySummary.sum.spent|formatAmount }}</td>
</tr>
</tfoot>
</table>
<div class="box-body table-responsive no-padding" id="categoriesHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
{% if categories.count > 1 %}
<div class="col-lg-2 col-md-3">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_per_category'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="categories-in-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas>
<label style="font-weight:normal;">
<input type="checkbox" id="categories-in-pie-chart-checked">
<small>{{ 'include_income_not_in_category'|_ }}</small>
</label>
</div>
</div>
</div>
<div class="col-lg-2 col-md-3">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expense_per_category'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="categories-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas>
<label style="font-weight:normal;">
<input type="checkbox" id="categories-out-pie-chart-checked">
<small>{{ 'include_expense_not_in_category'|_ }}</small>
</label>
</div>
</div>
</div>
{% endif %}
<div class="col-lg-2 col-md-3" id="pieCharts">
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_per_account'|_ }}</h3>
<h3 class="box-title">{{ 'account_per_category'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="accounts-in-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas>
<label style="font-weight:normal;">
<input type="checkbox" id="accounts-in-pie-chart-checked">
<small>{{ 'include_income_not_in_account'|_ }}</small>
</label>
<div class="box-body table-responsive no-padding" id="accountPerCategoryHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
<div class="col-lg-2 col-md-3">
</div>
<div class="row">
<div class="col-lg-4 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expense_per_account'|_ }}</h3>
<h3 class="box-title">{{ 'expense_per_category'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="accounts-out-pie-chart" style="margin:0 auto;" height="150" width="150"></canvas>
<label style="font-weight:normal;">
<input type="checkbox" id="accounts-out-pie-chart-checked">
<small>{{ 'include_expense_not_in_account'|_ }}</small>
</label>
<div style="width:100%;margin:0 auto;">
<canvas id="category-out-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="box" id="incomeAndExpensesChart">
<div class="col-lg-4 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_and_expenses'|_ }}</h3>
<h3 class="box-title">{{ 'income_per_category'|_ }}</h3>
</div>
<div class="box-body">
<canvas id="in-out-chart" style="width:100%;height:400px;" height="400" width="100%"></canvas>
<div style="width:100%;margin:0 auto;">
<canvas id="category-in-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expense_per_budget'|_ }}</h3>
</div>
<div class="box-body">
<div style="width:100%;margin:0 auto;">
<canvas id="budgets-out-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="row">
{% if averageExpenses|length > 0 %}
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'average_spending_per_account'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsign="az">{{ 'account'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'spent_average'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'total'|_ }}</th>
<th data-defaultsign="_19">{{ 'transaction_count'|_ }}</th>
</tr>
</thead>
<tbody>
{% set totalCount = 0 %}
{% set totalSum = 0 %}
{% for row in averageExpenses %}
{% if loop.index > listLength %}
<tr class="overListLength">
{% else %}
<tr>
{% endif %}
<td data-value="{{ row.name }}">
<a href="{{ route('accounts.show', row.id) }}">{{ row.name }}</a>
</td>
<td data-value="{{ row.average }}" style="text-align: right;">
{{ row.average|formatAmount }}
</td>
<td data-value="{{ row.sum }}" style="text-align: right;">
{{ row.sum|formatAmount }}
</td>
<td data-value="{{ row.count }}">
{{ row.count }}
</td>
</tr>
{% set totalCount = totalCount+ row.count %}
{% set totalSum = totalSum + row.sum %}
{% endfor %}
</tbody>
<tfoot>
{% if averageExpenses|length > listLength %}
<tr>
<td colspan="4" class="active">
<a href="#"
class="listLengthTrigger">{{ trans('firefly.show_full_list',{number:incomeTopLength}) }}</a>
</td>
</tr>
{% endif %}
<tr>
<td colspan="2">
{{ 'sum'|_ }}
</td>
<td style="text-align:right">{{ totalSum|formatAmount }}</td>
<td>{{ totalCount }}</td>
</tr>
</tfoot>
</table>
<div class="col-lg-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expense_per_source_account'|_ }}</h3>
</div>
<div class="box-body">
<div style="width:100%;margin:0 auto;">
<canvas id="source-accounts-out-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
{% endif %}
{% if topExpenses|length > 0 %}
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }}
)</h3>
</div>
<div class="box-body">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsort="disabled">{{ 'description'|_ }}</th>
<th data-defaultsign="month">{{ 'date'|_ }}</th>
<th data-defaultsign="az">{{ 'account'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'amount'|_ }}</th>
</tr>
</thead>
<tbody>
{% set totalSum = 0 %}
{% for row in topExpenses %}
{% set totalSum = totalSum + row.amount %}
{% if loop.index > listLength %}
<tr class="overListLength">
{% else %}
<tr>
{% endif %}
<td data-sortable="false">
<a href="{{ route('transactions.show', 1+row.transaction_group_id) }}">
{% if row.group_title|length > 0 %}
{{ row.group_title }} ({{ row.description }})
{% else %}
{{ row.description }}
{% endif %}
</a>
</td>
<td data-value="{{ row.date.format('Y-m-d') }}">
{{ row.date.formatLocalized(monthAndDayFormat) }}
</td>
<td data-value="{{ row.destination_account_name }}">
<a href="{{ route('accounts.show', row.destination_account_id) }}">
{{ row.destination_account_name }}
</a>
</td>
<td data-value="{{ row.amount }}" style="text-align: right;">
{{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_decimal_places) }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
{% if topExpenses|length > listLength %}
<tr>
<td colspan="3" class="active">
<a href="#"
class="listLengthTrigger">{{ trans('firefly.show_full_list',{number:incomeTopLength}) }}</a>
</td>
</tr>
{% endif %}
<tr>
<td colspan="3">
{{ 'sum'|_ }}
</td>
<td style="text-align:right">{{ totalSum|formatAmount }}</td>
</tr>
</tfoot>
</table>
</div>
<div class="col-lg-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_per_source_account'|_ }}</h3>
</div>
<div class="box-body">
<div style="width:100%;margin:0 auto;">
<canvas id="source-accounts-in-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="row">
{% if averageIncome|length > 0 %}
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'average_income_per_account'|_ }}</h3>
</div>
<div class="box-body">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsign="az">{{ 'account'|_ }}</th>
<th data-defaultsign="_19" style="text-align:right;">{{ 'income_average'|_ }}</th>
<th data-defaultsign="_19" style="text-align:right;">{{ 'total'|_ }}</th>
<th data-defaultsign="_19">{{ 'transaction_count'|_ }}</th>
</tr>
</thead>
<tbody>
{% set totalCount = 0 %}
{% set totalSum = 0 %}
{% for row in averageIncome %}
{% set totalCount = totalCount+ row.count %}
{% set totalSum = totalSum + row.sum %}
<tr>
<td data-value="{{ row.name }}">
<a href="{{ route('accounts.show', row.id) }}">{{ row.name }}</a>
</td>
<td data-value="{{ row.average }}" style="text-align:right;">
{{ (row.average*-1)|formatAmount }}
</td>
<td data-value="{{ row.sum }}" style="text-align:right;">
{{ (row.sum*-1)|formatAmount }}
</td>
<td data-value="{{ row.count }}">
{{ row.count }}
</td>
</tr>
{% endfor %}
<tr>
<td colspan="2">
{{ 'sum'|_ }}
</td>
<td style="text-align:right">{{ (totalSum*-1)|formatAmount }}</td>
<td>{{ totalCount }}</td>
</tr>
</tbody>
</table>
<div class="col-lg-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expense_per_destination_account'|_ }}</h3>
</div>
<div class="box-body">
<div style="width:100%;margin:0 auto;">
<canvas id="dest-accounts-out-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
{% endif %}
</div>
<div class="col-lg-6 col-md-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_per_destination_account'|_ }}</h3>
</div>
<div class="box-body">
<div style="width:100%;margin:0 auto;">
<canvas id="dest-accounts-in-pie-chart" style="width:100%;height:250px;" height="250"></canvas>
</div>
</div>
</div>
</div>
</div>
{% for category in categories %}
<div class="row">
<div class="col-lg-12">
<div class="box main_budget_chart">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income_and_expenses'|_ }} ({{ category.name }})</h3>
</div>
<div class="box-body">
{#<canvas class="main_budget_canvas" data-url="{{ route('chart.category.main', [accountIds, category.id, start.format('Ymd'), end.format('Ymd')]) }}" id="in-out-chart-{{ category.id }}" style="width:100%;height:400px;" height="400" width="100%"></canvas>#}
</div>
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-lg-6">
{% if topIncome|length > 0 %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})</h3>
</div>
<div class="box-body">
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsort="disabled">{{ 'description'|_ }}</th>
<th data-defaultsign="month">{{ 'date'|_ }}</th>
<th data-defaultsign="az">{{ 'account'|_ }}</th>
<th data-defaultsign="_19" style="text-align:right">{{ 'amount'|_ }}</th>
</tr>
</thead>
<tbody>
{% set totalSum = 0 %}
{% for row in topIncome %}
{% set totalSum = totalSum + row.amount %}
{% if loop.index > listLength %}
<tr class="overListLength">
{% else %}
<tr>
{% endif %}
<td data-sortable="false">
<a href="{{ route('transactions.show', 1+row.transaction_group_id) }}">
{% if row.group_title|length > 0 %}
{{ row.group_title }} ({{ row.description }})
{% else %}
{{ row.description }}
{% endif %}
</a>
</td>
<td data-value="{{ row.date.format('Y-m-d') }}">
{{ row.date.formatLocalized(monthAndDayFormat) }}
</td>
<td data-value="{{ row.destination_account_name }}">
<a href="{{ route('accounts.show', row.destination_account_id) }}">
{{ row.destination_account_name }}
</a>
</td>
<td data-value="{{ row.amount }}" style="text-align: right;">
{{ formatAmountBySymbol(row.amount*-1, row.currency_symbol, row.currency_decimal_places) }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
{% if topIncome|length > listLength %}
<tr>
<td colspan="3" class="active">
<a href="#"
class="listLengthTrigger">{{ trans('firefly.show_full_list',{number:incomeTopLength}) }}</a>
</td>
</tr>
{% endif %}
<tr>
<td colspan="3">
{{ 'sum'|_ }}
</td>
<td style="text-align:right">{{ (totalSum*-1)|formatAmount }}</td>
</tr>
</tfoot>
</table>
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'average_spending_per_destination'|_ }}</h3>
</div>
{% endif %}
<div class="box-body table-responsive no-padding" id="avgExpensesHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'average_earning_per_source'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding" id="avgIncomeHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})</h3>
</div>
<div class="box-body table-responsive no-padding" id="topExpensesHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'income'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})</h3>
</div>
<div class="box-body table-responsive no-padding" id="topIncomeHolder">
</div>
{# loading indicator #}
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
</div>
</div>
@@ -447,15 +226,24 @@
var accountIds = '{{ accountIds }}';
var categoryIds = '{{ categoryIds }}';
// html block URI's:
var accountsUri = '{{ route('report-data.category.accounts', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';
{#var categoriesUri = '{{ route('report-data.category.categories', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var accountPerCategoryUri = '{{ route('report-data.category.account-per-category', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var avgExpensesUri = '{{ route('report-data.category.avg-expenses', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var topExpensesUri = '{{ route('report-data.category.top-expenses', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var avgIncomeUri = '{{ route('report-data.category.avg-income', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var topIncomesUri = '{{ route('report-data.category.top-income', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
// chart uri's
var categoryIncomeUri = '{{ route('chart.category.category-income', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}';
var categoryExpenseUri = '{{ route('chart.category.category-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}';
var accountIncomeUri = '{{ route('chart.category.account-income', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}';
var accountExpenseUri = '{{ route('chart.category.account-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}';
var mainUri = '{{ route('chart.category.main', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';
{#var budgetExpenseUri = '{{ route('chart.category.category-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var categoryExpenseUri = '{{ route('chart.category.category-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var sourceExpenseUri = '{{ route('chart.budget.source-account-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
{#var destinationExpenseUri = '{{ route('chart.budget.destination-account-expense', [accountIds, categoryIds, start.format('Ymd'), end.format('Ymd')]) }}';#}
</script>
<script type="text/javascript" src="v1/js/ff/reports/all.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="v1/js/ff/reports/category/month.js?v={{ FF_VERSION }}"></script>

View File

@@ -0,0 +1,32 @@
<table class="table table-hover sortable">
<thead>
<tr>
<th data-defaultsign="az">{{ 'name'|_ }}</th>
<th data-defaultsign="_19" style="text-align: right;">{{ 'spent'|_ }}</th>
</tr>
</thead>
<tbody>
{% for account in report %}
{% for currency in account.currencies %}
<tr>
<td data-value="{{ account.name }} ({{ currency.currency_name }})">
<a href="{{ route('accounts.show', account.id) }}" title="{{ account.iban }}">{{ account.name }} ({{ currency.currency_name }})</a>
</td>
<td data-value="{{ currency.sum }}" style="text-align: right;">
{{ formatAmountBySymbol(currency.sum, currency.currency_symbol, currency.currency_decimal_places) }}
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
<tfoot>
{% for sum in sums %}
<tr>
<td>{{ 'sum'|_ }} ({{ sum.currency_name }})</td>
<td style="text-align: right;">
{{ formatAmountBySymbol(sum.sum, sum.currency_symbol, sum.currency_decimal_places) }}
</td>
</tr>
{% endfor %}
</tfoot>
</table>

View File

@@ -82,12 +82,13 @@ Route::group(
// *
// */
Route::group(
['middleware' => 'user-logged-in-no-2fa', 'prefix' => 'two-factor', 'as' => 'two-factor.', 'namespace' => 'FireflyIII\Http\Controllers\Auth'], static function () {
Route::post('submit', ['uses' => 'TwoFactorController@submitMFA', 'as' => 'submit']);
Route::get('lost', ['uses' => 'TwoFactorController@lostTwoFactor', 'as' => 'lost']);
// Route::post('', ['uses' => 'TwoFactorController@postIndex', 'as' => 'post']);
//
}
['middleware' => 'user-logged-in-no-2fa', 'prefix' => 'two-factor', 'as' => 'two-factor.', 'namespace' => 'FireflyIII\Http\Controllers\Auth'],
static function () {
Route::post('submit', ['uses' => 'TwoFactorController@submitMFA', 'as' => 'submit']);
Route::get('lost', ['uses' => 'TwoFactorController@lostTwoFactor', 'as' => 'lost']);
// Route::post('', ['uses' => 'TwoFactorController@postIndex', 'as' => 'post']);
//
}
);
/**
@@ -320,21 +321,22 @@ Route::group(
* Chart\Account Controller (default report)
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/account', 'as' => 'chart.account.'], static function () {
Route::get('frontpage', ['uses' => 'AccountController@frontpage', 'as' => 'frontpage']);
Route::get('expense', ['uses' => 'AccountController@expenseAccounts', 'as' => 'expense']);
Route::get('revenue', ['uses' => 'AccountController@revenueAccounts', 'as' => 'revenue']);
Route::get('report/{accountList}/{start_date}/{end_date}', ['uses' => 'AccountController@report', 'as' => 'report']);
Route::get('period/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@period', 'as' => 'period']);
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/account', 'as' => 'chart.account.'],
static function () {
Route::get('frontpage', ['uses' => 'AccountController@frontpage', 'as' => 'frontpage']);
Route::get('expense', ['uses' => 'AccountController@expenseAccounts', 'as' => 'expense']);
Route::get('revenue', ['uses' => 'AccountController@revenueAccounts', 'as' => 'revenue']);
Route::get('report/{accountList}/{start_date}/{end_date}', ['uses' => 'AccountController@report', 'as' => 'report']);
Route::get('period/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@period', 'as' => 'period']);
Route::get('income-category/{account}/all/all', ['uses' => 'AccountController@incomeCategoryAll', 'as' => 'income-category-all']);
Route::get('expense-category/{account}/all/all', ['uses' => 'AccountController@expenseCategoryAll', 'as' => 'expense-category-all']);
Route::get('expense-budget/{account}/all/all', ['uses' => 'AccountController@expenseBudgetAll', 'as' => 'expense-budget-all']);
Route::get('income-category/{account}/all/all', ['uses' => 'AccountController@incomeCategoryAll', 'as' => 'income-category-all']);
Route::get('expense-category/{account}/all/all', ['uses' => 'AccountController@expenseCategoryAll', 'as' => 'expense-category-all']);
Route::get('expense-budget/{account}/all/all', ['uses' => 'AccountController@expenseBudgetAll', 'as' => 'expense-budget-all']);
Route::get('income-category/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@incomeCategory', 'as' => 'income-category']);
Route::get('expense-category/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@expenseCategory', 'as' => 'expense-category']);
Route::get('expense-budget/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@expenseBudget', 'as' => 'expense-budget']);
}
Route::get('income-category/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@incomeCategory', 'as' => 'income-category']);
Route::get('expense-category/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@expenseCategory', 'as' => 'expense-category']);
Route::get('expense-budget/{account}/{start_date}/{end_date}', ['uses' => 'AccountController@expenseBudget', 'as' => 'expense-budget']);
}
);
@@ -342,37 +344,50 @@ Route::group(
* Chart\Bill Controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/bill', 'as' => 'chart.bill.'], static function () {
Route::get('frontpage', ['uses' => 'BillController@frontpage', 'as' => 'frontpage']);
Route::get('single/{bill}', ['uses' => 'BillController@single', 'as' => 'single']);
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/bill', 'as' => 'chart.bill.'],
static function () {
Route::get('frontpage', ['uses' => 'BillController@frontpage', 'as' => 'frontpage']);
Route::get('single/{bill}', ['uses' => 'BillController@single', 'as' => 'single']);
}
}
);
/**
* Chart\Budget Controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/budget', 'as' => 'chart.budget.'], static function () {
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/budget', 'as' => 'chart.budget.'],
static function () {
Route::get('frontpage', ['uses' => 'BudgetController@frontpage', 'as' => 'frontpage']);
Route::get('period/0/{currency}/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@periodNoBudget', 'as' => 'period.no-budget']);
Route::get('period/{budget}/{currency}/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@period', 'as' => 'period']);
Route::get('budget/{budget}/{budgetLimit}', ['uses' => 'BudgetController@budgetLimit', 'as' => 'budget-limit']);
Route::get('budget/{budget}', ['uses' => 'BudgetController@budget', 'as' => 'budget']);
Route::get('frontpage', ['uses' => 'BudgetController@frontpage', 'as' => 'frontpage']);
Route::get('period/0/{currency}/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@periodNoBudget', 'as' => 'period.no-budget']);
Route::get('period/{budget}/{currency}/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@period', 'as' => 'period']);
Route::get('budget/{budget}/{budgetLimit}', ['uses' => 'BudgetController@budgetLimit', 'as' => 'budget-limit']);
Route::get('budget/{budget}', ['uses' => 'BudgetController@budget', 'as' => 'budget']);
// these charts are used in budget/show:
Route::get('expense-category/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseCategory', 'as' => 'expense-category']);
Route::get('expense-asset/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseAsset', 'as' => 'expense-asset']);
Route::get('expense-expense/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseExpense', 'as' => 'expense-expense']);
// these charts are used in budget/show:
Route::get('expense-category/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseCategory', 'as' => 'expense-category']);
Route::get('expense-asset/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseAsset', 'as' => 'expense-asset']);
Route::get('expense-expense/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseExpense', 'as' => 'expense-expense']);
// these charts are used in reports (category reports):
Route::get('category/expense/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@categoryExpense', 'as' => 'category-expense']);
Route::get('budget/expense/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@budgetExpense', 'as' => 'budget-expense']);
Route::get('source-account/expense/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@sourceAccountExpense', 'as' => 'source-account-expense']);
Route::get('destination-account/expense/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@destinationAccountExpense', 'as' => 'destination-account-expense']);
Route::get('operations/{accountList}/{budget}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@mainChart', 'as' => 'main']);
}
// these charts are used in reports (category reports):
Route::get(
'category/expense/{accountList}/{budgetList}/{start_date}/{end_date}',
['uses' => 'BudgetReportController@categoryExpense', 'as' => 'category-expense']
);
Route::get(
'budget/expense/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@budgetExpense', 'as' => 'budget-expense']
);
Route::get(
'source-account/expense/{accountList}/{budgetList}/{start_date}/{end_date}',
['uses' => 'BudgetReportController@sourceAccountExpense', 'as' => 'source-account-expense']
);
Route::get(
'destination-account/expense/{accountList}/{budgetList}/{start_date}/{end_date}',
['uses' => 'BudgetReportController@destinationAccountExpense', 'as' => 'destination-account-expense']
);
Route::get('operations/{accountList}/{budget}/{start_date}/{end_date}', ['uses' => 'BudgetReportController@mainChart', 'as' => 'main']);
}
);
/**
@@ -465,12 +480,13 @@ Route::group(
* Chart\Expense Controller (for expense/revenue report).
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/expense', 'as' => 'chart.expense.'], static function () {
Route::get(
'operations/{accountList}/{expenseList}/{start_date}/{end_date}',
['uses' => 'ExpenseReportController@mainChart', 'as' => 'main']
);
}
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/expense', 'as' => 'chart.expense.'],
static function () {
Route::get(
'operations/{accountList}/{expenseList}/{start_date}/{end_date}',
['uses' => 'ExpenseReportController@mainChart', 'as' => 'main']
);
}
);
@@ -488,11 +504,12 @@ Route::group(
* Chart\Report Controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/report', 'as' => 'chart.report.'], static function () {
Route::get('operations/{accountList}/{start_date}/{end_date}', ['uses' => 'ReportController@operations', 'as' => 'operations']);
Route::get('net-worth/{accountList}/{start_date}/{end_date}/', ['uses' => 'ReportController@netWorth', 'as' => 'net-worth']);
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Chart', 'prefix' => 'chart/report', 'as' => 'chart.report.'],
static function () {
Route::get('operations/{accountList}/{start_date}/{end_date}', ['uses' => 'ReportController@operations', 'as' => 'operations']);
Route::get('net-worth/{accountList}/{start_date}/{end_date}/', ['uses' => 'ReportController@netWorth', 'as' => 'net-worth']);
}
}
);
/**
@@ -776,10 +793,15 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Report', 'prefix' => 'report-data/category',
'as' => 'report-data.category.'], static function () {
// TODO still in use?
Route::get('operations/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@operations', 'as' => 'operations']);
Route::get('income/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@income', 'as' => 'income']);
Route::get('expenses/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@expenses', 'as' => 'expenses']);
Route::get('accounts/{accountList}/{categoryList}/{start_date}/{end_date}', ['uses' => 'CategoryController@accounts', 'as' => 'accounts']);
}
);
@@ -788,7 +810,7 @@ Route::group(
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Report', 'prefix' => 'report-data/balance', 'as' => 'report-data.balance.'],
function () {
static function () {
Route::get('general/{accountList}/{start_date}/{end_date}', ['uses' => 'BalanceController@general', 'as' => 'general']);
}
@@ -801,12 +823,16 @@ Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Report', 'prefix' => 'report-data/budget', 'as' => 'report-data.budget.'],
static function () {
// todo are these two routes still used?
Route::get('general/{accountList}/{start_date}/{end_date}/', ['uses' => 'BudgetController@general', 'as' => 'general']);
Route::get('period/{accountList}/{start_date}/{end_date}', ['uses' => 'BudgetController@period', 'as' => 'period']);
Route::get('accounts/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetController@accounts', 'as' => 'accounts']);
Route::get('budgets/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetController@budgets', 'as' => 'budgets']);
Route::get('account-per-budget/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetController@accountPerBudget', 'as' => 'account-per-budget']);
Route::get(
'account-per-budget/{accountList}/{budgetList}/{start_date}/{end_date}',
['uses' => 'BudgetController@accountPerBudget', 'as' => 'account-per-budget']
);
Route::get('top-expenses/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetController@topExpenses', 'as' => 'top-expenses']);
Route::get('avg-expenses/{accountList}/{budgetList}/{start_date}/{end_date}', ['uses' => 'BudgetController@avgExpenses', 'as' => 'avg-expenses']);