diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index 8d2b535e67..3e0a82ef8c 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -63,10 +63,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $accountSummary = $this->getAccountSummary(); $categorySummary = $this->getCategorySummary(); $averageExpenses = $this->getAverageExpenses(); + $averageIncome = $this->getAverageIncome(); + $topExpenses = $this->getTopExpenses(); + $topIncome = $this->getTopIncome(); // render! - return view('reports.category.month', compact('accountIds', 'categoryIds', 'reportType', 'accountSummary', 'categorySummary','averageExpenses')) + return view( + 'reports.category.month', + compact( + 'accountIds', 'categoryIds', 'topIncome', 'reportType', 'accountSummary', 'categorySummary', 'averageExpenses', 'averageIncome', 'topExpenses' + ) + ) ->with('start', $this->start)->with('end', $this->end) ->with('categories', $this->categories) ->with('accounts', $this->accounts) @@ -203,6 +211,45 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface } + private function getAverageIncome(): array + { + $expenses = $this->getIncome(); + $result = []; + /** @var Transaction $transaction */ + foreach ($expenses as $transaction) { + // opposing name and ID: + $opposingId = $transaction->opposing_account_id; + + // is not set? + if (!isset($result[$opposingId])) { + $name = $transaction->opposing_account_name; + $encrypted = intval($transaction->opposing_account_encrypted); + $name = $encrypted === 1 ? Crypt::decrypt($name) : $name; + $result[$opposingId] = [ + 'name' => $name, + 'count' => 1, + 'id' => $opposingId, + 'average' => $transaction->transaction_amount, + 'sum' => $transaction->transaction_amount, + ]; + continue; + } + $result[$opposingId]['count']++; + $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); + $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], strval($result[$opposingId]['count'])); + } + + // sort result by average: + $average = []; + foreach ($result as $key => $row) { + $average[$key] = floatval($row['average']); + } + + array_multisort($average, SORT_DESC, $result); + + return $result; + } + /** * @return array */ @@ -360,4 +407,40 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface } + + /** + * @return Collection + */ + private function getTopExpenses(): Collection + { + $transactions = $this->getExpenses()->sortBy('transaction_amount'); + + $transactions = $transactions->each( + function (Transaction $transaction) { + if (intval($transaction->opposing_account_encrypted) === 1) { + $transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name); + } + } + ); + + return $transactions; + } + + /** + * @return Collection + */ + private function getTopIncome(): Collection + { + $transactions = $this->getIncome()->sortByDesc('transaction_amount'); + + $transactions = $transactions->each( + function (Transaction $transaction) { + if (intval($transaction->opposing_account_encrypted) === 1) { + $transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name); + } + } + ); + + return $transactions; + } } \ No newline at end of file diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index 98d1ad463e..ad0576fa1a 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -66,57 +66,6 @@ function colorizeData(data) { return newData; } -/** - * @param URI - * @param container - * @param chartType - * @param options - * @param colorData - */ -function drawAChart(URI, container, chartType, options, colorData) { - if ($('#' + container).length === 0) { - console.log('No container called ' + container + ' was found.'); - return; - } - - // var result = true; - // if (options.beforeDraw) { - // result = options.beforeDraw(data, {url: URL, container: container}); - // } - // if (result === false) { - // return; - // } - - $.getJSON(URI).done(function (data) { - - if (colorData) { - data = colorizeData(data); - } - - if (allCharts.hasOwnProperty(container)) { - console.log('Will draw updated ' + chartType + ' chart'); - allCharts[container].data.datasets = data.datasets; - allCharts[container].data.labels = data.labels; - allCharts[container].update(); - } else { - // new chart! - console.log('Will draw new ' + chartType + 'chart'); - var ctx = document.getElementById(container).getContext("2d"); - allCharts[container] = new Chart(ctx, { - type: chartType, - data: data, - options: options - }); - } - - }).fail(function () { - console.log('Failed to draw ' + chartType + ' in container ' + container); - $('#' + container).addClass('general-chart-error'); - }); - console.log('URL for ' + chartType + ' chart : ' + URL); -} - - /** * Function to draw a line chart: * @param URI @@ -182,3 +131,60 @@ function pieChart(URI, container) { drawAChart(URI, container, chartType, options, colorData); } + + +/** + * @param URI + * @param container + * @param chartType + * @param options + * @param colorData + */ +function drawAChart(URI, container, chartType, options, colorData) { + if ($('#' + container).length === 0) { + console.log('No container called ' + container + ' was found.'); + return; + } + + + $.getJSON(URI).done(function (data) { + + + if (data.labels.length === 0) { + console.log(chartType + " chart in " + container + " has no data."); + // remove the chart container + parent + var holder = $('#' + container).parent().parent(); + if (holder.hasClass('box')) { + // remove box + holder.remove(); + } + return; + } + + + if (colorData) { + data = colorizeData(data); + } + + if (allCharts.hasOwnProperty(container)) { + console.log('Will draw updated ' + chartType + ' chart'); + allCharts[container].data.datasets = data.datasets; + allCharts[container].data.labels = data.labels; + allCharts[container].update(); + } else { + // new chart! + console.log('Will draw new ' + chartType + 'chart'); + var ctx = document.getElementById(container).getContext("2d"); + allCharts[container] = new Chart(ctx, { + type: chartType, + data: data, + options: options + }); + } + + }).fail(function () { + console.log('Failed to draw ' + chartType + ' in container ' + container); + $('#' + container).addClass('general-chart-error'); + }); + console.log('URL for ' + chartType + ' chart : ' + URL); +} diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index d63eddbcc3..e57052381a 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -55,6 +55,10 @@ $(function () { } ); + + // trigger list thing + listLengthInitial(); + }); function currencySelect(e) { @@ -108,3 +112,30 @@ accounting.settings = { decimal: "." } }; + + +function listLengthInitial() { + "use strict"; + $('.overListLength').hide(); + $('.listLengthTrigger').unbind('click').click(triggerList) +} + +function triggerList(e) { + "use strict"; + var link = $(e.target); + var table = link.parent().parent().parent().parent(); + console.log('data-hidden = ' + table.attr('data-hidden')); + if (table.attr('data-hidden') === 'no') { + // hide all elements, return false. + table.find('.overListLength').hide(); + table.attr('data-hidden', 'yes'); + link.text(showFullList); + return false; + } + // show all, return false + table.find('.overListLength').show(); + table.attr('data-hidden', 'no'); + link.text(showOnlyTop); + + return false; +} \ No newline at end of file diff --git a/public/js/ff/index.js b/public/js/ff/index.js index 3621132011..f03c97445f 100644 --- a/public/js/ff/index.js +++ b/public/js/ff/index.js @@ -43,29 +43,6 @@ function drawChart() { getBoxAmounts(); } -// /** -// * Removes a chart box if there is nothing for the chart to draw. -// * -// * @param data -// * @param options -// * @returns {boolean} -// */ -// function beforeDrawIsEmpty(data, options) { -// "use strict"; -// -// // check if chart holds data. -// if (data.labels.length === 0) { -// // remove the chart container + parent -// console.log(options.container + ' appears empty. Removed.'); -// $('#' + options.container).parent().parent().remove(); -// -// // return false so script stops. -// return false; -// } -// return true; -// } - - function getBoxAmounts() { "use strict"; var boxes = ['in', 'out', 'bills-unpaid', 'bills-paid']; diff --git a/public/js/ff/reports/default/all.js b/public/js/ff/reports/default/all.js index 3b47c14cf9..ec3fb8c1e6 100644 --- a/public/js/ff/reports/default/all.js +++ b/public/js/ff/reports/default/all.js @@ -27,32 +27,6 @@ function triggerInfoClick() { $('.firefly-info-button').unbind('click').click(clickInfoButton); } -function listLengthInitial() { - "use strict"; - $('.overListLength').hide(); - $('.listLengthTrigger').unbind('click').click(triggerList) -} - -function triggerList(e) { - "use strict"; - var link = $(e.target); - var table = link.parent().parent().parent().parent(); - console.log('data-hidden = ' + table.attr('data-hidden')); - if (table.attr('data-hidden') === 'no') { - // hide all elements, return false. - table.find('.overListLength').hide(); - table.attr('data-hidden', 'yes'); - link.text(showFullList); - return false; - } - // show all, return false - table.find('.overListLength').show(); - table.attr('data-hidden', 'no'); - link.text(showOnlyTop); - - return false; -} - function clickInfoButton(e) { "use strict"; // find all data tags, regardless of what they are: diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index a57a2b43ff..db1eaa3010 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -698,8 +698,10 @@ return [ 'everything_else' => 'Everything else', 'income_and_expenses' => 'Income and expenses', 'spent_average' => 'Spent (average)', + 'income_average' => 'Income (average)', 'transaction_count' => 'Transaction count', - + 'average_spending_per_account' => 'Average spending per account', + 'average_income_per_account' => 'Average income per account', // charts: 'chart' => 'Chart', 'dayOfMonth' => 'Day of the month', diff --git a/resources/views/reports/category/month.twig b/resources/views/reports/category/month.twig index 2048b765b3..7e860d70c5 100644 --- a/resources/views/reports/category/month.twig +++ b/resources/views/reports/category/month.twig @@ -16,31 +16,27 @@ - - - + + + {% for account in accounts %} - - - + {% if accountSummary[account.id] %} + + {% else %} + + {% endif %} + {% if accountSummary[account.id] %} + + {% else %} + + {% endif %} {% endfor %} @@ -56,31 +52,27 @@
{{ 'name'|_ }}{{ 'earned'|_ }}{{ 'spent'|_ }}{{ 'name'|_ }}{{ 'earned'|_ }}{{ 'spent'|_ }}
+ {{ account.name }} - {% if accountSummary[account.id] %} - {{ accountSummary[account.id].earned|formatAmount }} - {% else %} - {{ 0|formatAmount }} - {% endif %} - - {% if accountSummary[account.id] %} - {{ accountSummary[account.id].spent|formatAmount }} - {% else %} - {{ 0|formatAmount }} - {% endif %} - {{ accountSummary[account.id].earned|formatAmount }}{{ 0|formatAmount }}{{ accountSummary[account.id].spent|formatAmount }}{{ 0|formatAmount }}
- - - + + + {% for category in categories %} - - - + {% if categorySummary[category.id] %} + + {% else %} + + {% endif %} + {% if categorySummary[category.id] %} + + {% else %} + + {% endif %} {% endfor %} @@ -162,12 +154,6 @@ - {# - big chart here - Show income / expenses per period. Differs per report: month = per day, year = per month, multi-year = per year. - In a bar chart, possibly grouped by expense/revenue account. - #} -
@@ -178,9 +164,10 @@
{{ 'name'|_ }}{{ 'earned'|_ }}{{ 'spent'|_ }}{{ 'name'|_ }}{{ 'earned'|_ }}{{ 'spent'|_ }}
+ {{ category.name }} - {% if categorySummary[category.id] %} - {{ categorySummary[category.id].earned|formatAmount }} - {% else %} - {{ 0|formatAmount }} - {% endif %} - - {% if categorySummary[category.id] %} - {{ categorySummary[category.id].spent|formatAmount }} - {% else %} - {{ 0|formatAmount }} - {% endif %} - {{ categorySummary[category.id].earned|formatAmount }}{{ 0|formatAmount }}{{ categorySummary[category.id].spent|formatAmount }}{{ 0|formatAmount }}
- - - + + + + @@ -189,10 +176,13 @@ - - + @@ -203,69 +193,175 @@
-
-
-

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

+ {% if topExpenses.count > 0 %} +
+
+

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

+
+
+
{{ 'account'|_ }}{{ 'spent_average'|_ }}{{ 'transaction_count'|_ }}{{ 'account'|_ }}{{ 'spent_average'|_ }}{{ 'total'|_ }}{{ 'transaction_count'|_ }}
{{ row.name }} + {{ row.average|formatAmount }} + + {{ row.sum|formatAmount }} + {{ row.count }}
+ + + + + + + + + + {% for row in topExpenses %} + {% if loop.index > listLength %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + + + {% if topExpenses.count > listLength %} + + + + {% endif %} + +
{{ 'description'|_ }}{{ 'date'|_ }}{{ 'account'|_ }}{{ 'amount'|_ }}
+ + {% if row.transaction_description|length > 0 %} + {{ row.transaction_description }} ({{ row.description }}) + {% else %} + {{ row.description }} + {% endif %} + + + {{ row.date.formatLocalized(monthAndDayFormat) }} + + + {{ row.opposing_account_name }} + + + {{ row.transaction_amount|formatAmount }} +
+ {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} +
+ -
-
- + {% endif %}
- -
-
-
-

{{ 'average_income_per_account'|_ }}

-
-
+ {% if averageIncome|length > 0 %} +
+
+
+

{{ 'average_income_per_account'|_ }}

+
+
+ + + + + + + + + + + {% for row in averageIncome %} + + + + + + + {% endfor %} + +
{{ 'account'|_ }}{{ 'income_average'|_ }}{{ 'total'|_ }}{{ 'transaction_count'|_ }}
+ {{ row.name }} + + {{ row.average|formatAmount }} + + {{ row.sum|formatAmount }} + + {{ row.count }} +
+
-
+ {% endif %}
-
-
-

{{ 'income'|_ }} ({{ trans('firefly.topX', {number: 10}) }})

+ {% if topIncome.count > 0 %} +
+
+

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

+
+
+ + + + + + + + + + + {% for row in topIncome %} + {% if loop.index > listLength %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + + + {% if topIncome.count > listLength %} + + + + {% endif %} + +
{{ 'description'|_ }}{{ 'date'|_ }}{{ 'account'|_ }}{{ 'amount'|_ }}
+ + {% if row.transaction_description|length > 0 %} + {{ row.transaction_description }} ({{ row.description }}) + {% else %} + {{ row.description }} + {% endif %} + + + {{ row.date.formatLocalized(monthAndDayFormat) }} + + + {{ row.opposing_account_name }} + + + {{ row.transaction_amount|formatAmount }} +
+ {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} +
+
-
-
-
+ {% endif %}
- -
-
- List of spending (withdrawals) by account, if relevant. Grouped:
- - BC: 456 - AH: 123 - - order by size - -
-
- List of spending (withdrawals) by transaction, if relevant. Not grouped
- - more groceries: 456 - groceries: 123 - - ordered by size. top x list? -
- -
- Same but for income -
- -
- {% endblock %} {% block scripts %} + @@ -284,4 +378,5 @@ {% endblock %} {% block styles %} + {% endblock %} \ No newline at end of file diff --git a/resources/views/reports/partials/categories.twig b/resources/views/reports/partials/categories.twig index a09cbe2c7c..281fd6ca5f 100644 --- a/resources/views/reports/partials/categories.twig +++ b/resources/views/reports/partials/categories.twig @@ -25,7 +25,7 @@ {% endfor %} - {% if categories.getCategories.count > expenseTopLength %} + {% if categories.getCategories.count > listLength %} {{ trans('firefly.show_full_list',{number:incomeTopLength}) }} diff --git a/resources/views/reports/partials/expenses.twig b/resources/views/reports/partials/expenses.twig index 3491b33564..f3cf12e4aa 100644 --- a/resources/views/reports/partials/expenses.twig +++ b/resources/views/reports/partials/expenses.twig @@ -24,7 +24,7 @@ {% endfor %} - {% if expenses.getExpenses|length > expenseTopLength %} + {% if expenses.getExpenses|length > listLength %} {{ trans('firefly.show_full_list',{number:incomeTopLength}) }}