diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index 1242272dfb..3ada70161c 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -185,6 +185,7 @@ class ChartJsGenerator implements GeneratorInterface // make larger than 0 $chartData['datasets'][0]['data'][] = (float)app('steam')->positive((string)$value); $chartData['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index); + $chartData['labels'][] = $key; ++$index; } diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index d1adeb82b0..ab9832c1e2 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -79,7 +79,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface ->render(); } catch (Throwable $e) { Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); - $result = 'Could not render report view.'; + $result = sprintf('Could not render report view: %s', $e->getMessage()); } return $result; diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 1f3a2537f4..7f22f41a12 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -1,4 +1,4 @@ -middleware( function ($request, $next) { - $this->generator = app(GeneratorInterface::class); - $this->budgetRepository = app(BudgetRepositoryInterface::class); - $this->blRepository = app(BudgetLimitRepositoryInterface::class); + $this->generator = app(GeneratorInterface::class); + $this->opsRepository = app(OperationsRepositoryInterface::class); return $next($request); } ); } - /** - * Chart that groups expenses by the account. - * - * TODO this chart is not multi-currency aware. + * Chart that groups the expenses by budget. * * @param Collection $accounts * @param Collection $budgets * @param Carbon $start * @param Carbon $end - * @param string $others * * @return JsonResponse */ - public function accountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others): JsonResponse + public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse { - /** @var MetaPieChartInterface $helper */ - $helper = app(MetaPieChartInterface::class); - $helper->setAccounts($accounts); - $helper->setBudgets($budgets); - $helper->setStart($start); - $helper->setEnd($end); - $helper->setCollectOtherObjects(1 === (int)$others); - $chartData = $helper->generate('expense', 'account'); - $data = $this->generator->pieChart($chartData); + $result = []; + $spent = $this->opsRepository->listExpenses($start, $end, $accounts, $budgets); + + // loop expenses. + foreach ($spent as $currency) { + foreach ($currency['budgets'] as $budget) { + $title = sprintf('%s (%s)', $budget['name'], $currency['currency_name']); + $result[$title] = $result[$title] ?? [ + 'amount' => '0', + 'currency_symbol' => $currency['currency_symbol'], + ]; + foreach ($budget['transaction_journals'] as $journal) { + $amount = app('steam')->positive($journal['amount']); + $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); + } + } + } + + $data = $this->generator->multiCurrencyPieChart($result); return response()->json($data); } - /** * Chart that groups the expenses by budget. * - * TODO this chart is not multi-currency aware. + * @param Collection $accounts + * @param Collection $budgets + * @param Carbon $start + * @param Carbon $end + * + * @return JsonResponse + */ + public function categoryExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse + { + $result = []; + $spent = $this->opsRepository->listExpenses($start, $end, $accounts, $budgets); + // loop expenses. + foreach ($spent as $currency) { + foreach ($currency['budgets'] as $budget) { + + + foreach ($budget['transaction_journals'] as $journal) { + $categoryName = $journal['category_name'] ?? trans('firefly.no_category'); + $title = sprintf('%s (%s)', $categoryName, $currency['currency_name']); + $result[$title] = $result[$title] ?? [ + 'amount' => '0', + 'currency_symbol' => $currency['currency_symbol'], + ]; + + $amount = app('steam')->positive($journal['amount']); + $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); + } + } + } + + $data = $this->generator->multiCurrencyPieChart($result); + + return response()->json($data); + } + + /** + * Chart that groups expenses by the account. * * @param Collection $accounts * @param Collection $budgets * @param Carbon $start * @param Carbon $end - * @param string $others * * @return JsonResponse */ - public function budgetExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end, string $others): JsonResponse + public function destinationAccountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse { - /** @var MetaPieChartInterface $helper */ - $helper = app(MetaPieChartInterface::class); - $helper->setAccounts($accounts); - $helper->setBudgets($budgets); - $helper->setStart($start); - $helper->setEnd($end); - $helper->setCollectOtherObjects(1 === (int)$others); - $chartData = $helper->generate('expense', 'budget'); - $data = $this->generator->pieChart($chartData); + $result = []; + $spent = $this->opsRepository->listExpenses($start, $end, $accounts, $budgets); + + // loop expenses. + foreach ($spent as $currency) { + foreach ($currency['budgets'] as $budget) { + + + foreach ($budget['transaction_journals'] as $journal) { + $title = sprintf('%s (%s)', $journal['destination_account_name'], $currency['currency_name']); + $result[$title] = $result[$title] ?? [ + 'amount' => '0', + 'currency_symbol' => $currency['currency_symbol'], + ]; + + $amount = app('steam')->positive($journal['amount']); + $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); + } + } + } + + $data = $this->generator->multiCurrencyPieChart($result); return response()->json($data); } - /** * Main overview of a budget in the budget report. * - * TODO this chart is not multi-currency aware. + * @param Collection $accounts + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return JsonResponse + */ + public function mainChart(Collection $accounts, Budget $budget, Carbon $start, Carbon $end): JsonResponse + { + $chartData = []; + $spent = $this->opsRepository->listExpenses($start, $end, $accounts, new Collection([$budget])); + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + + // loop expenses. + foreach ($spent as $currency) { + // add things to chart Data for each currency: + $spentKey = sprintf('%d-spent', $currency['currency_id']); + $chartData[$spentKey] = $chartData[$spentKey] ?? [ + 'label' => sprintf( + '%s (%s)', (string)trans('firefly.spent_in_specific_budget', ['budget' => $budget['name']]), $currency['currency_name'] + ), + 'type' => 'bar', + 'currency_symbol' => $currency['currency_symbol'], + 'currency_id' => $currency['currency_id'], + 'entries' => $this->makeEntries($start, $end), + ]; + + foreach ($currency['budgets'] as $currentBudget) { + foreach ($currentBudget['transaction_journals'] as $journal) { + $key = $journal['date']->formatLocalized($format); + $amount = app('steam')->positive($journal['amount']); + $chartData[$spentKey]['entries'][$key] = $chartData[$spentKey]['entries'][$key] ?? '0'; + $chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $amount); + } + } + } + + $data = $this->generator->multiSet($chartData); + + return response()->json($data); + } + + /** + * Chart that groups expenses by the account. * * @param Collection $accounts * @param Collection $budgets @@ -140,82 +230,56 @@ class BudgetReportController extends Controller * @param Carbon $end * * @return JsonResponse - * */ - public function mainChart(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse + public function sourceAccountExpense(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): JsonResponse { - $cache = new CacheProperties; - $cache->addProperty('chart.budget.report.main'); - $cache->addProperty($accounts); - $cache->addProperty($budgets); - $cache->addProperty($start); - $cache->addProperty($end); - if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore - } - $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); - $function = app('navigation')->preferredEndOfPeriod($start, $end); - $chartData = []; - $currentStart = clone $start; + $result = []; + $spent = $this->opsRepository->listExpenses($start, $end, $accounts, $budgets); - // prep chart data: - foreach ($budgets as $budget) { - $chartData[$budget->id] = [ - 'label' => (string)trans('firefly.spent_in_specific_budget', ['budget' => $budget->name]), - 'type' => 'bar', - 'yAxisID' => 'y-axis-0', - 'entries' => [], - ]; - $chartData[$budget->id . '-sum'] = [ - 'label' => (string)trans('firefly.sum_of_expenses_in_budget', ['budget' => $budget->name]), - 'type' => 'line', - 'fill' => false, - 'yAxisID' => 'y-axis-1', - 'entries' => [], - ]; - $chartData[$budget->id . '-left'] = [ - 'label' => (string)trans('firefly.left_in_budget_limit', ['budget' => $budget->name]), - 'type' => 'bar', - 'fill' => false, - 'yAxisID' => 'y-axis-0', - 'entries' => [], - ]; - } - $allBudgetLimits = $this->blRepository->getAllBudgetLimits($start, $end); - $sumOfExpenses = []; - $leftOfLimits = []; - while ($currentStart < $end) { - $currentEnd = clone $currentStart; - $currentEnd = $currentEnd->$function(); - $expenses = $this->groupByBudget($this->getExpensesInBudgets($accounts, $budgets, $currentStart, $currentEnd)); - $label = $currentStart->formatLocalized($format); + // loop expenses. + foreach ($spent as $currency) { + foreach ($currency['budgets'] as $budget) { - /** @var Budget $budget */ - foreach ($budgets as $budget) { - // get budget limit(s) for this period): - $budgetLimits = $this->filterBudgetLimits($allBudgetLimits, $budget, $currentStart, $currentEnd); - $currentExpenses = $expenses[$budget->id] ?? '0'; - $sumOfExpenses[$budget->id] = $sumOfExpenses[$budget->id] ?? '0'; - $sumOfExpenses[$budget->id] = bcadd($currentExpenses, $sumOfExpenses[$budget->id]); - $chartData[$budget->id]['entries'][$label] = bcmul($currentExpenses, '-1'); - $chartData[$budget->id . '-sum']['entries'][$label] = bcmul($sumOfExpenses[$budget->id], '-1'); - if (count($budgetLimits) > 0) { - $budgetLimitId = $budgetLimits->first()->id; - $leftOfLimits[$budgetLimitId] = $leftOfLimits[$budgetLimitId] ?? (string)$budgetLimits->sum('amount'); - $leftOfLimits[$budgetLimitId] = bcadd($leftOfLimits[$budgetLimitId], $currentExpenses); - $chartData[$budget->id . '-left']['entries'][$label] = $leftOfLimits[$budgetLimitId]; + foreach ($budget['transaction_journals'] as $journal) { + $title = sprintf('%s (%s)', $journal['source_account_name'], $currency['currency_name']); + $result[$title] = $result[$title] ?? [ + 'amount' => '0', + 'currency_symbol' => $currency['currency_symbol'], + ]; + + $amount = app('steam')->positive($journal['amount']); + $result[$title]['amount'] = bcadd($result[$title]['amount'], $amount); } } - /** @var Carbon $currentStart */ - $currentStart = clone $currentEnd; - $currentStart->addDay(); } - $data = $this->generator->multiSet($chartData); - $cache->store($data); + $data = $this->generator->multiCurrencyPieChart($result); return response()->json($data); } + /** + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + private function makeEntries(Carbon $start, Carbon $end): array + { + $return = []; + $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); + $preferredRange = app('navigation')->preferredRangeFormat($start, $end); + $currentStart = clone $start; + while ($currentStart <= $end) { + $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); + $key = $currentStart->formatLocalized($format); + $return[$key] = '0'; + $currentStart = clone $currentEnd; + $currentStart->addDay()->startOfDay(); + } + + return $return; + } + } diff --git a/app/Http/Controllers/Report/BudgetController.php b/app/Http/Controllers/Report/BudgetController.php index 61963427d1..ccd3df3563 100644 --- a/app/Http/Controllers/Report/BudgetController.php +++ b/app/Http/Controllers/Report/BudgetController.php @@ -148,6 +148,58 @@ class BudgetController extends Controller return view('reports.budget.partials.accounts', compact('sums', 'report')); } + /** + * @param Collection $accounts + * @param Collection $budgets + * @param Carbon $start + * @param Carbon $end + */ + public function avgExpenses(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end) + { + // get all journals. + $opsRepository = app(OperationsRepositoryInterface::class); + $spent = $opsRepository->listExpenses($start, $end, $accounts, $budgets); + $result = []; + foreach ($spent as $currency) { + $currencyId = $currency['currency_id']; + foreach ($currency['budgets'] as $budget) { + foreach ($budget['transaction_journals'] as $journal) { + $destinationId = $journal['destination_account_id']; + $result[$destinationId] = $result[$destinationId] ?? [ + 'transactions' => 0, + 'sum' => '0', + 'avg' => '0', + 'avg_float' => 0, + 'destination_account_name' => $journal['destination_account_name'], + 'destination_account_id' => $journal['destination_account_id'], + 'currency_id' => $currency['currency_id'], + 'currency_name' => $currency['currency_name'], + 'currency_symbol' => $currency['currency_symbol'], + 'currency_decimal_places' => $currency['currency_decimal_places'], + ]; + $result[$destinationId]['transactions']++; + $result[$destinationId]['sum'] = bcadd($journal['amount'], $result[$destinationId]['sum']); + $result[$destinationId]['avg'] = bcdiv($result[$destinationId]['sum'], (string)$result[$destinationId]['transactions']); + $result[$destinationId]['avg_float'] = (float)$result[$destinationId]['avg']; + } + } + } + // sort by amount_float + // sort temp array by amount. + $amounts = array_column($result, 'avg_float'); + array_multisort($amounts, SORT_ASC, $result); + + try { + $result = view('reports.budget.partials.avg-expenses', compact('result'))->render(); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); + $result = sprintf('Could not render view: %s', $e->getMessage()); + } + + return $result; + } + /** * @param Collection $accounts * @param Collection $budgets @@ -238,7 +290,6 @@ class BudgetController extends Controller return $result; } - /** * Show budget overview for a period. * @@ -304,4 +355,52 @@ class BudgetController extends Controller return $result; } + /** + * @param Collection $accounts + * @param Collection $budgets + * @param Carbon $start + * @param Carbon $end + */ + public function topExpenses(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end) + { + // get all journals. + $opsRepository = app(OperationsRepositoryInterface::class); + $spent = $opsRepository->listExpenses($start, $end, $accounts, $budgets); + $result = []; + foreach ($spent as $currency) { + $currencyId = $currency['currency_id']; + foreach ($currency['budgets'] as $budget) { + foreach ($budget['transaction_journals'] as $journal) { + $result[] = [ + 'description' => $journal['description'], + 'transaction_group_id' => $journal['transaction_group_id'], + 'amount_float' => (float)$journal['amount'], + 'amount' => $journal['amount'], + 'date' => $journal['date']->formatLocalized($this->monthAndDayFormat), + 'destination_account_name' => $journal['destination_account_name'], + 'destination_account_id' => $journal['destination_account_id'], + 'currency_id' => $currency['currency_id'], + 'currency_name' => $currency['currency_name'], + 'currency_symbol' => $currency['currency_symbol'], + 'currency_decimal_places' => $currency['currency_decimal_places'], + ]; + } + } + } + // sort by amount_float + // sort temp array by amount. + $amounts = array_column($result, 'amount_float'); + array_multisort($amounts, SORT_ASC, $result); + + try { + $result = view('reports.budget.partials.top-expenses', compact('result'))->render(); + // @codeCoverageIgnoreStart + } catch (Throwable $e) { + Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); + $result = sprintf('Could not render view: %s', $e->getMessage()); + } + + return $result; + } + } diff --git a/app/Repositories/Budget/OperationsRepository.php b/app/Repositories/Budget/OperationsRepository.php index 32d7b742fd..4ac417931a 100644 --- a/app/Repositories/Budget/OperationsRepository.php +++ b/app/Repositories/Budget/OperationsRepository.php @@ -211,7 +211,7 @@ class OperationsRepository implements OperationsRepositoryInterface if (null === $budgets || (null !== $budgets && 0 === $budgets->count())) { $collector->setBudgets($this->getBudgets()); } - $collector->withBudgetInformation(); + $collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation(); $journals = $collector->getExtractedJournals(); $array = []; @@ -248,9 +248,15 @@ class OperationsRepository implements OperationsRepositoryInterface $array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [ - 'amount' => app('steam')->negative($journal['amount']), - 'source_account_id' => $journal['source_account_id'], - 'date' => $journal['date'], + 'amount' => app('steam')->negative($journal['amount']), + 'destination_account_id' => $journal['destination_account_id'], + 'destination_account_name' => $journal['destination_account_name'], + 'source_account_id' => $journal['source_account_id'], + 'source_account_name' => $journal['source_account_name'], + 'category_name' => $journal['category_name'], + 'description' => $journal['description'], + 'transaction_group_id' => $journal['transaction_group_id'], + 'date' => $journal['date'], ]; } diff --git a/public/v1/js/ff/reports/budget/month.js b/public/v1/js/ff/reports/budget/month.js index 8586767b38..b2da1ab05e 100644 --- a/public/v1/js/ff/reports/budget/month.js +++ b/public/v1/js/ff/reports/budget/month.js @@ -18,8 +18,6 @@ * along with Firefly III. If not, see . */ -/** global: budgetExpenseUri, accountExpenseUri, mainUri */ - $(function () { "use strict"; drawChart(); @@ -28,14 +26,9 @@ $(function () { loadAjaxPartial('budgetsHolder', budgetsUri); loadAjaxPartial('accountPerbudgetHolder', accountPerBudgetUri); + loadAjaxPartial('topExpensesHolder', topExpensesUri); + loadAjaxPartial('avgExpensesHolder', avgExpensesUri); - $('#budgets-out-pie-chart-checked').on('change', function () { - redrawPieChart('budgets-out-pie-chart', budgetExpenseUri); - }); - - $('#accounts-out-pie-chart-checked').on('change', function () { - redrawPieChart('accounts-out-pie-chart', accountExpenseUri); - }); }); @@ -43,27 +36,21 @@ $(function () { function drawChart() { "use strict"; - // month view: - doubleYNonStackedChart(mainUri, 'in-out-chart'); + $.each($('.main_budget_canvas'), function (i, v) { + var canvas = $(v); + columnChart(canvas.data('url'), canvas.attr('id')); + }); // draw pie chart of income, depending on "show other transactions too": redrawPieChart('budgets-out-pie-chart', budgetExpenseUri); - redrawPieChart('accounts-out-pie-chart', accountExpenseUri); + redrawPieChart('categories-out-pie-chart', categoryExpenseUri); + redrawPieChart('source-accounts-pie-chart', sourceExpenseUri); + redrawPieChart('dest-accounts-pie-chart', destinationExpenseUri); } function redrawPieChart(container, uri) { "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); } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 5aaf2125fc..f0038e4086 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -888,6 +888,11 @@ return [ 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', 'no_budget' => '(no budget)', 'account_per_budget' => 'Account per budget', + 'all_other_budgets' => '(all other budgets)', + 'all_other_accounts' => '(all other accounts)', + 'expense_per_source_account' => 'Expenses per source account', + 'expense_per_destination_account' => 'Expenses per destination account', + 'average_spending_per_destination' => 'Average expense per destination account', 'no_budget_squared' => '(no budget)', 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', diff --git a/resources/views/v1/reports/budget/month.twig b/resources/views/v1/reports/budget/month.twig index b6d9f0c526..aed5bea2b5 100644 --- a/resources/views/v1/reports/budget/month.twig +++ b/resources/views/v1/reports/budget/month.twig @@ -8,7 +8,6 @@
- {# spent in these budgets per account, per currency.#}

{{ 'accounts'|_ }}

@@ -51,213 +50,100 @@
- {% if budgets.count > 1 %} -
-
-
-

{{ 'expense_per_budget'|_ }}

-
-
-
- -
- -
-
-
- {% endif %} -
-
+
+
-

{{ 'expense_per_account'|_ }}

+

{{ 'expense_per_budget'|_ }}

- +
-
-
- -
-
-
+
+
-

{{ 'income_and_expenses'|_ }}

+

{{ 'expense_per_category'|_ }}

- {# - Here be a chart with the budget limits as well if relevant.
- amount spent vs budget limit reps
- over the entire period the amount spent would rise and the budget limit rep would be like a heart beat jumping up and down
- needs to be two axes to work
#} - +
+ +
- {% if averageExpenses|length > 0 %} -
-
-
-

{{ 'average_spending_per_account'|_ }}

+
+
+
+

{{ 'expense_per_source_account'|_ }}

+
+
+
+
-
-
- {# loading indicator #} -
- -
- {# - - - - - - - - - - - {% set totalCount = 0 %} - {% set totalSum = 0 %} - {% for row in averageExpenses %} - {% set totalCount = totalCount+ row.count %} - {% set totalSum = totalSum + row.sum %} - {% if loop.index > listLength %} - - {% else %} - - {% endif %} - - - - - - {% endfor %} - - - {% if averageExpenses|length > listLength %} - - - - {% endif %} - - - - - - -
{{ 'account'|_ }}{{ 'spent_average'|_ }}{{ 'total'|_ }}{{ 'transaction_count'|_ }}
- {{ row.name }} - - {{ row.average|formatAmount }} - - {{ row.sum|formatAmount }} - - {{ row.count }} -
- {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} -
- {{ 'sum'|_ }} - {{ totalSum|formatAmount }}{{ totalCount }}
-
- #}
- {% endif %} - {% if topExpenses|length > 0 %} -
- -
-
-

{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})

+
+
+
+
+

{{ 'expense_per_destination_account'|_ }}

+
+
+
+
-
-
- {# loading indicator #} -
- -
- {# - - - - - - - - - - - - {% set totalSum = 0 %} - {% for row in topExpenses %} - - {% set totalSum = totalSum + row.amount %} - - {% if loop.index > listLength %} - - {% else %} - - {% endif %} - - - - - - - - - - {% endfor %} - - - {% if topExpenses|length > listLength %} - - - - {% endif %} - - - - - -
{{ 'description'|_ }}{{ 'date'|_ }}{{ 'account'|_ }}{{ 'amount'|_ }}
- - {% if row.group_title|length > 0 %} - {{ row.group_title }} ({{ row.description }}) - {% else %} - {{ row.description }} - {% endif %} - - - {{ row.date.formatLocalized(monthAndDayFormat) }} - - - {{ row.destination_account_name }} - - - {{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_decimal_places) }} -
- {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} -
- {{ 'sum'|_ }} - {{ totalSum|formatAmount }}
-
- #}
- {% endif %} +
+
+ + {% for budget in budgets %} +
+
+
+
+

{{ 'expenses'|_ }} ({{ budget.name }})

+
+
+ +
+
+
+
+ {% endfor %} +
+
+
+
+

{{ 'average_spending_per_destination'|_ }}

+
+
+
+ {# loading indicator #} +
+ +
+
+
+
+ +
+
+

{{ 'expenses'|_ }} ({{ trans('firefly.topX', {number: listLength}) }})

+
+
+
+ {# loading indicator #} +
+ +
+
+
{% endblock %} @@ -277,13 +163,16 @@ // html block URI's: var accountsUri = '{{ route('report-data.budget.accounts', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; - var budgetsUri = '{{ route('report-data.budget.budgets', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; - var accountPerBudgetUri = '{{ route('report-data.budget.account-per-budget', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var budgetsUri = '{{ route('report-data.budget.budgets', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var accountPerBudgetUri = '{{ route('report-data.budget.account-per-budget', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var avgExpensesUri = '{{ route('report-data.budget.avg-expenses', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var topExpensesUri = '{{ route('report-data.budget.top-expenses', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; // chart uri's - var budgetExpenseUri = '{{ route('chart.budget.budget-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}'; - var accountExpenseUri = '{{ route('chart.budget.account-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd'),'OTHERS']) }}'; - var mainUri = '{{ route('chart.budget.main', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var budgetExpenseUri = '{{ route('chart.budget.budget-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var categoryExpenseUri = '{{ route('chart.budget.category-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var sourceExpenseUri = '{{ route('chart.budget.source-account-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; + var destinationExpenseUri = '{{ route('chart.budget.destination-account-expense', [accountIds, budgetIds, start.format('Ymd'), end.format('Ymd')]) }}'; diff --git a/resources/views/v1/reports/budget/partials/avg-expenses.twig b/resources/views/v1/reports/budget/partials/avg-expenses.twig new file mode 100644 index 0000000000..90c28ec1c7 --- /dev/null +++ b/resources/views/v1/reports/budget/partials/avg-expenses.twig @@ -0,0 +1,46 @@ + + + + + + + + + + + {% for row in result %} + {% if loop.index > listLength %} + + {% else %} + + {% endif %} + + + + + + + + + {% endfor %} + + + {% if result|length > listLength %} + + + + {% endif %} + +
{{ 'account'|_ }}{{ 'spent_average'|_ }}{{ 'total'|_ }}{{ 'transaction_count'|_ }}
+ + {{ row.destination_account_name }} + + + {{ formatAmountBySymbol(row.avg, row.currency_symbol, row.currency_decimal_places) }} + + {{ formatAmountBySymbol(row.sum, row.currency_symbol, row.currency_decimal_places) }} + + {{ row.transactions }} +
+ {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} +
diff --git a/resources/views/v1/reports/budget/partials/top-expenses.twig b/resources/views/v1/reports/budget/partials/top-expenses.twig new file mode 100644 index 0000000000..f0c9c0e031 --- /dev/null +++ b/resources/views/v1/reports/budget/partials/top-expenses.twig @@ -0,0 +1,48 @@ + + + + + + + + + + + {% for row in result %} + {% if loop.index > listLength %} + + {% else %} + + {% endif %} + + + + + + + + + {% endfor %} + + + {% if result|length > listLength %} + + + + {% endif %} + +
{{ 'description'|_ }}{{ 'date'|_ }}{{ 'account'|_ }}{{ 'amount'|_ }}
+ + {{ row.description }} + + + {{ row.date }} + + + {{ row.destination_account_name }} + + + {{ formatAmountBySymbol(row.amount, row.currency_symbol, row.currency_decimal_places) }} +
+ {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} +
diff --git a/routes/web.php b/routes/web.php index 9a0a517aa9..8caf467ca3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -367,19 +367,11 @@ Route::group( Route::get('expense-expense/{budget}/{budgetLimit?}', ['uses' => 'BudgetController@expenseExpense', 'as' => 'expense-expense']); // these charts are used in reports (category reports): - Route::get( - 'budget/expense/{accountList}/{budgetList}/{start_date}/{end_date}/{others}', - ['uses' => 'BudgetReportController@budgetExpense', 'as' => 'budget-expense'] - ); - Route::get( - 'account/expense/{accountList}/{budgetList}/{start_date}/{end_date}/{others}', - ['uses' => 'BudgetReportController@accountExpense', 'as' => 'account-expense'] - ); - - Route::get( - 'operations/{accountList}/{budgetList}/{start_date}/{end_date}', - ['uses' => 'BudgetReportController@mainChart', 'as' => 'main'] - ); + 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']); } ); @@ -401,19 +393,19 @@ Route::group( // these charts are used in reports (category reports): Route::get( - 'category/income/{accountList}/{categoryList}/{start_date}/{end_date}/{others}', + 'category/income/{accountList}/{categoryList}/{start_date}/{end_date}', ['uses' => 'CategoryReportController@categoryIncome', 'as' => 'category-income'] ); Route::get( - 'category/expense/{accountList}/{categoryList}/{start_date}/{end_date}/{others}', + 'category/expense/{accountList}/{categoryList}/{start_date}/{end_date}', ['uses' => 'CategoryReportController@categoryExpense', 'as' => 'category-expense'] ); Route::get( - 'account/income/{accountList}/{categoryList}/{start_date}/{end_date}/{others}', + 'account/income/{accountList}/{categoryList}/{start_date}/{end_date}', ['uses' => 'CategoryReportController@accountIncome', 'as' => 'account-income'] ); Route::get( - 'account/expense/{accountList}/{categoryList}/{start_date}/{end_date}/{others}', + 'account/expense/{accountList}/{categoryList}/{start_date}/{end_date}', ['uses' => 'CategoryReportController@accountExpense', 'as' => 'account-expense'] ); @@ -433,19 +425,19 @@ Route::group( // these charts are used in reports (tag reports): Route::get( - 'tag/income/{accountList}/{tagList}/{start_date}/{end_date}/{others}', + 'tag/income/{accountList}/{tagList}/{start_date}/{end_date}', ['uses' => 'TagReportController@tagIncome', 'as' => 'tag-income'] ); Route::get( - 'tag/expense/{accountList}/{tagList}/{start_date}/{end_date}/{others}', + 'tag/expense/{accountList}/{tagList}/{start_date}/{end_date}', ['uses' => 'TagReportController@tagExpense', 'as' => 'tag-expense'] ); Route::get( - 'account/income/{accountList}/{tagList}/{start_date}/{end_date}/{others}', + 'account/income/{accountList}/{tagList}/{start_date}/{end_date}', ['uses' => 'TagReportController@accountIncome', 'as' => 'account-income'] ); Route::get( - 'account/expense/{accountList}/{tagList}/{start_date}/{end_date}/{others}', + 'account/expense/{accountList}/{tagList}/{start_date}/{end_date}', ['uses' => 'TagReportController@accountExpense', 'as' => 'account-expense'] ); @@ -815,6 +807,8 @@ Route::group( 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('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']); } );