From 1fd028dfb8b50b6bfa05091349b9e57941cfb24e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 9 Dec 2016 07:07:53 +0100 Subject: [PATCH] First code for #426 --- .../Report/Audit/MonthReportGenerator.php | 2 +- .../Report/Budget/MonthReportGenerator.php | 329 ++++++++++++++++++ .../Budget/MultiYearReportGenerator.php | 27 ++ .../Report/Budget/YearReportGenerator.php | 28 ++ app/Http/Controllers/ReportController.php | 2 +- resources/views/reports/budget/month.twig | 262 ++++++++++++++ 6 files changed, 648 insertions(+), 2 deletions(-) create mode 100644 app/Generator/Report/Budget/MonthReportGenerator.php create mode 100644 app/Generator/Report/Budget/MultiYearReportGenerator.php create mode 100644 app/Generator/Report/Budget/YearReportGenerator.php create mode 100644 resources/views/reports/budget/month.twig diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index f5ab29a5bb..cc31219d9e 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -25,7 +25,7 @@ use Steam; /** * Class MonthReportGenerator * - * @package FireflyIII\Generator\Report\Standard + * @package FireflyIII\Generator\Report\Audit */ class MonthReportGenerator implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php new file mode 100644 index 0000000000..10b5c39afe --- /dev/null +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -0,0 +1,329 @@ +income = new Collection; + $this->expenses = new Collection; + } + + /** + * @return string + */ + public function generate(): string + { + $accountIds = join(',', $this->accounts->pluck('id')->toArray()); + $categoryIds = join(',', $this->budgets->pluck('id')->toArray()); + $reportType = 'budget'; + // $expenses = $this->getExpenses(); + // $income = $this->getIncome(); + // $accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income)); + // $categorySummary = $this->getObjectSummary($this->summarizeByCategory($expenses), $this->summarizeByCategory($income)); + // $averageExpenses = $this->getAverages($expenses, SORT_ASC); + // $averageIncome = $this->getAverages($income, SORT_DESC); + // $topExpenses = $this->getTopExpenses(); + // $topIncome = $this->getTopIncome(); + + + // render! + return view( + 'reports.budget.month', + compact( + 'accountIds', 'categoryIds', 'topIncome', 'reportType', 'accountSummary', 'categorySummary', 'averageExpenses', 'averageIncome', 'topExpenses' + ) + ) + ->with('start', $this->start)->with('end', $this->end) + ->with('budgets', $this->budgets) + ->with('accounts', $this->accounts) + ->render(); + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Collection $budgets + * + * @return ReportGeneratorInterface + */ + public function setBudgets(Collection $budgets): ReportGeneratorInterface + { + $this->budgets = $budgets; + + return $this; + } + + /** + * @param Collection $categories + * + * @return ReportGeneratorInterface + */ + public function setCategories(Collection $categories): ReportGeneratorInterface + { + $this->categories = $categories; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } + + /** + * @param Collection $collection + * @param int $sortFlag + * + * @return array + */ + private function getAverages(Collection $collection, int $sortFlag): array + { + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + // opposing name and ID: + $opposingId = $transaction->opposing_account_id; + + // is not set? + if (!isset($result[$opposingId])) { + $name = $transaction->opposing_account_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, $sortFlag, $result); + + return $result; + } + + /** + * @return Collection + */ + private function getExpenses(): Collection + { + if ($this->expenses->count() > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + $collector = new JournalCollector(auth()->user()); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setCategories($this->categories)->withOpposingAccount()->disableFilter(); + + $accountIds = $this->accounts->pluck('id')->toArray(); + $transactions = $collector->getJournals(); + $transactions = self::filterExpenses($transactions, $accountIds); + $this->expenses = $transactions; + + return $transactions; + } + + /** + * @return Collection + */ + private function getIncome(): Collection + { + if ($this->income->count() > 0) { + return $this->income; + } + + $collector = new JournalCollector(auth()->user()); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->setCategories($this->categories)->withOpposingAccount(); + $accountIds = $this->accounts->pluck('id')->toArray(); + $transactions = $collector->getJournals(); + $transactions = self::filterIncome($transactions, $accountIds); + $this->income = $transactions; + + return $transactions; + } + + /** + * @param array $spent + * @param array $earned + * + * @return array + */ + private function getObjectSummary(array $spent, array $earned): array + { + $return = []; + + /** + * @var int $accountId + * @var string $entry + */ + foreach ($spent as $objectId => $entry) { + if (!isset($return[$objectId])) { + $return[$objectId] = ['spent' => 0, 'earned' => 0]; + } + + $return[$objectId]['spent'] = $entry; + } + unset($entry); + + /** + * @var int $accountId + * @var string $entry + */ + foreach ($earned as $objectId => $entry) { + if (!isset($return[$objectId])) { + $return[$objectId] = ['spent' => 0, 'earned' => 0]; + } + + $return[$objectId]['earned'] = $entry; + } + + + return $return; + } + + + /** + * @return Collection + */ + private function getTopExpenses(): Collection + { + $transactions = $this->getExpenses()->sortBy('transaction_amount'); + + return $transactions; + } + + /** + * @return Collection + */ + private function getTopIncome(): Collection + { + $transactions = $this->getIncome()->sortByDesc('transaction_amount'); + + return $transactions; + } + + /** + * @param Collection $collection + * + * @return array + */ + private function summarizeByAccount(Collection $collection): array + { + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + $accountId = $transaction->account_id; + $result[$accountId] = $result[$accountId] ?? '0'; + $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); + } + + return $result; + } + + /** + * @param Collection $collection + * + * @return array + */ + private function summarizeByCategory(Collection $collection): array + { + $result = []; + /** @var Transaction $transaction */ + foreach ($collection as $transaction) { + $jrnlCatId = intval($transaction->transaction_journal_category_id); + $transCatId = intval($transaction->transaction_category_id); + $categoryId = max($jrnlCatId, $transCatId); + $result[$categoryId] = $result[$categoryId] ?? '0'; + $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); + } + + return $result; + } +} \ No newline at end of file diff --git a/app/Generator/Report/Budget/MultiYearReportGenerator.php b/app/Generator/Report/Budget/MultiYearReportGenerator.php new file mode 100644 index 0000000000..3d92def9f3 --- /dev/null +++ b/app/Generator/Report/Budget/MultiYearReportGenerator.php @@ -0,0 +1,27 @@ + +
+
+
+

{{ 'accounts'|_ }}

+
+
+ + + + + + + + + + {% for account in accounts %} + + + {% if accountSummary[account.id] %} + + {% else %} + + {% endif %} + {% if accountSummary[account.id] %} + + {% else %} + + {% endif %} + + {% endfor %} + +
{{ 'name'|_ }}{{ 'earned'|_ }}{{ 'spent'|_ }}
+ {{ account.name }} + {{ accountSummary[account.id].earned|formatAmount }}{{ 0|formatAmount }}{{ accountSummary[account.id].spent|formatAmount }}{{ 0|formatAmount }}
+
+
+ +
+
+

{{ 'budgets'|_ }}

+
+
+ + + + + + + + + {% for budget in budgets %} + + + {% if budgetSummary[budget.id] %} + + {% else %} + + {% endif %} + {% if budgetSummary[budget.id] %} + + {% else %} + + {% endif %} + + {% endfor %} + +
{{ 'name'|_ }}{{ 'spent'|_ }}
+ {{ budget.name }} + {{ budgetSummary[budget.id].earned|formatAmount }}{{ 0|formatAmount }}{{ budgetSummary[budget.id].spent|formatAmount }}{{ 0|formatAmount }}
+
+
+
+ {% if budgets.count > 1 %} +
+
+
+

{{ 'expense_per_budget'|_ }}

+
+
+ + +
+
+
+ {% endif %} + {% if accounts.count > 1 %} +
+
+
+

{{ 'expense_per_budget'|_ }}

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

{{ 'income_and_expenses'|_ }}

+
+
+ +
+
+
+
+
+ {% if averageExpenses|length > 0 %} +
+
+
+

{{ 'average_spending_per_account'|_ }}

+
+
+ + + + + + + + + + + {% for row in averageExpenses %} + + + + + + + {% endfor %} + +
{{ 'account'|_ }}{{ 'spent_average'|_ }}{{ 'total'|_ }}{{ 'transaction_count'|_ }}
+ {{ row.name }} + + {{ row.average|formatAmount }} + + {{ row.sum|formatAmount }} + + {{ row.count }} +
+
+
+
+ {% endif %} + {% if topExpenses.count > 0 %} +
+ +
+
+

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

+
+
+ + + + + + + + + + + {% 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 %} +
+ +{% endblock %} + +{% block scripts %} + + + + + + + + + + + +{% endblock %} + +{% block styles %} + +{% endblock %} \ No newline at end of file