diff --git a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php index fc466aa418..4983d9635a 100644 --- a/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php +++ b/app/Generator/Chart/Budget/BudgetChartGeneratorInterface.php @@ -38,13 +38,6 @@ interface BudgetChartGeneratorInterface */ public function frontpage(Collection $entries): array; - /** - * @param Collection $entries - * - * @return array - */ - public function multiYear(Collection $entries): array; - /** * @param Collection $entries * @param string $viewRange diff --git a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php index f82fb807c5..cf78936c4d 100644 --- a/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php +++ b/app/Generator/Chart/Budget/ChartJsBudgetChartGenerator.php @@ -100,40 +100,6 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface return $data; } - /** - * @param Collection $entries - * - * @return array - */ - public function multiYear(Collection $entries): array - { - // dataset: - $data = [ - 'count' => 0, - 'labels' => [], - 'datasets' => [], - ]; - // get labels from one of the budgets (assuming there's at least one): - $first = $entries->first(); - $keys = array_keys($first['budgeted']); - foreach ($keys as $year) { - $data['labels'][] = strval($year); - } - - // then, loop all entries and create datasets: - foreach ($entries as $entry) { - $name = $entry['name']; - $spent = $entry['spent']; - $budgeted = $entry['budgeted']; - $data['datasets'][] = ['label' => 'Spent on ' . $name, 'data' => array_values($spent)]; - $data['datasets'][] = ['label' => 'Budgeted for ' . $name, 'data' => array_values($budgeted)]; - } - $data['count'] = count($data['datasets']); - - return $data; - - } - /** * @param Collection $entries * @param string $viewRange diff --git a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php index 56d57f6966..e17022d9b6 100644 --- a/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php +++ b/app/Generator/Chart/Category/CategoryChartGeneratorInterface.php @@ -45,13 +45,6 @@ interface CategoryChartGeneratorInterface */ public function frontpage(Collection $entries): array; - /** - * @param Collection $entries - * - * @return array - */ - public function multiYear(Collection $entries): array; - /** * @param Collection $entries * diff --git a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php index 631b867cd1..560b4f95bb 100644 --- a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php +++ b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php @@ -117,34 +117,6 @@ class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface return $data; } - /** - * @param Collection $entries - * - * @return array - */ - public function multiYear(Collection $entries): array - { - // get labels from one of the categories (assuming there's at least one): - $first = $entries->first(); - $data = ['count' => 0, 'labels' => array_keys($first['spent']), 'datasets' => [],]; - - // then, loop all entries and create datasets: - foreach ($entries as $entry) { - $name = $entry['name']; - $spent = $entry['spent']; - $earned = $entry['earned']; - if (array_sum(array_values($spent)) != 0) { - $data['datasets'][] = ['label' => 'Spent in category ' . $name, 'data' => array_values($spent)]; - } - if (array_sum(array_values($earned)) != 0) { - $data['datasets'][] = ['label' => 'Earned in category ' . $name, 'data' => array_values($earned)]; - } - } - $data['count'] = count($data['datasets']); - - return $data; - } - /** * * @param Collection $entries diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index f9df70c397..cd8520e29d 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -14,6 +14,7 @@ declare(strict_types = 1); namespace FireflyIII\Helpers\Report; use Carbon\Carbon; +use DB; use FireflyIII\Helpers\Collection\Bill as BillCollection; use FireflyIII\Helpers\Collection\BillLine; use FireflyIII\Helpers\Collection\Category as CategoryCollection; @@ -21,6 +22,7 @@ use FireflyIII\Helpers\Collection\Expense; use FireflyIII\Helpers\Collection\Income; use FireflyIII\Helpers\FiscalHelperInterface; use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionJournal; @@ -111,6 +113,67 @@ class ReportHelper implements ReportHelperInterface return $collection; } + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getBudgetMultiYear(Carbon $start, Carbon $end, Collection $accounts): array + { + $accountIds = $accounts->pluck('id')->toArray(); + $query = TransactionJournal + ::leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->whereNull('transaction_journals.deleted_at') + ->whereNull('transactions.deleted_at') + ->where('transaction_types.type', 'Withdrawal') + ->where('transaction_journals.user_id', auth()->user()->id); + + if (count($accountIds) > 0) { + $query->whereIn('transactions.account_id', $accountIds); + } + $query->groupBy(['budget_transaction_journal.budget_id', 'the_year']); + $queryResult = $query->get( + [ + 'budget_transaction_journal.budget_id', + DB::raw('DATE_FORMAT(transaction_journals.date,"%Y") AS the_year'), + DB::raw('SUM(transactions.amount) as sum_of_period'), + ] + ); + + $data = []; + $budgets = $this->budgetRepository->getBudgets(); + $years = $this->listOfYears($start, $end); + + // do budget "zero" + $emptyBudget = new Budget; + $emptyBudget->id = 0; + $emptyBudget->name = strval(trans('firefly.no_budget')); + $budgets->push($emptyBudget); + + + // get all budgets and years. + foreach ($budgets as $budget) { + $data[$budget->id] = [ + 'name' => $budget->name, + 'entries' => [], + ]; + foreach ($years as $year) { + // filter query result here! + $data[$budget->id]['entries'][$year] = $this->filterAmount($queryResult, $budget->id, $year); + } + } + + return $data; + } + /** * @param Carbon $start * @param Carbon $end @@ -231,6 +294,24 @@ class ReportHelper implements ReportHelperInterface return $months; } + /** + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function listOfYears(Carbon $start, Carbon $end): array + { + $begin = clone $start; + $years = []; + while ($begin < $end) { + $years[] = $begin->year; + $begin->addYear(); + } + + return $years; + } + /** * Returns an array of tags and their comparitive size with amounts bla bla. * @@ -305,6 +386,30 @@ class ReportHelper implements ReportHelperInterface return $collection; } + /** + * @param Collection $set + * @param int $budgetId + * @param int $year + * + * @return string + */ + protected function filterAmount(Collection $set, int $budgetId, int $year): string + { + /** @var stdClass $object */ + $result = $set->filter( + function (TransactionJournal $object) use ($budgetId, $year) { + return intval($object->the_year) === $year && $budgetId === intval($object->budget_id); + } + ); + $amount = '0'; + if (!is_null($result->first())) { + $amount = $result->first()->sum_of_period; + } + + return $amount; + + } + /** * Take the array as returned by CategoryRepositoryInterface::spentPerDay and CategoryRepositoryInterface::earnedByDay * and sum up everything in the array in the given range. @@ -331,4 +436,6 @@ class ReportHelper implements ReportHelperInterface return $sum; } + + } diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php index 34ffcc9e7b..6dec1b9c0e 100644 --- a/app/Helpers/Report/ReportHelperInterface.php +++ b/app/Helpers/Report/ReportHelperInterface.php @@ -28,6 +28,16 @@ use Illuminate\Support\Collection; interface ReportHelperInterface { + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return array + */ + public function getBudgetMultiYear(Carbon $start, Carbon $end, Collection $accounts): array; + + /** * This method generates a full report for the given period on all * the users bills and their payments. @@ -80,6 +90,14 @@ interface ReportHelperInterface */ public function listOfMonths(Carbon $date): array; + /** + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function listOfYears(Carbon $start, Carbon $end): array; + /** * Returns an array of tags and their comparitive size with amounts bla bla. * diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 56eeb966ef..3e6c5f1c83 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -183,80 +183,6 @@ class BudgetController extends Controller return Response::json($data); } - /** - * - * @param BudgetRepositoryInterface $repository - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * @param Collection $budgets - * - * - * @return \Illuminate\Http\JsonResponse - */ - public function multiYear(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts, Collection $budgets) - { - - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($accounts); - $cache->addProperty($budgets); - $cache->addProperty('multiYearBudget'); - - if ($cache->has()) { - return Response::json($cache->get()); - } - $budgetIds = $budgets->pluck('id')->toArray(); - $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); - $budgeted = []; - $entries = new Collection; - // filter budgets once: - $repetitions = $repetitions->filter( - function (LimitRepetition $repetition) use ($budgetIds) { - if (in_array(strval($repetition->budget_id), $budgetIds)) { - return true; - } - - return false; - } - ); - /** @var LimitRepetition $repetition */ - foreach ($repetitions as $repetition) { - $year = $repetition->startdate->year; - if (isset($budgeted[$repetition->budget_id][$year])) { - $budgeted[$repetition->budget_id][$year] = bcadd($budgeted[$repetition->budget_id][$year], $repetition->amount); - continue; - } - $budgeted[$repetition->budget_id][$year] = $repetition->amount; - } - - foreach ($budgets as $budget) { - $currentStart = clone $start; - $entry = ['name' => $budget->name, 'spent' => [], 'budgeted' => []]; - while ($currentStart < $end) { - // fix the date: - $currentEnd = clone $currentStart; - $year = $currentStart->year; - $currentEnd->endOfYear(); - - $spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $currentStart, $currentEnd); - - // jump to next year. - $currentStart = clone $currentEnd; - $currentStart->addDay(); - - $entry['spent'][$year] = round($spent * -1, 2); - $entry['budgeted'][$year] = isset($budgeted[$budget->id][$year]) ? round($budgeted[$budget->id][$year], 2) : 0; - } - $entries->push($entry); - } - $data = $this->generator->multiYear($entries); - $cache->store($data); - - return Response::json($data); - } - /** * @param BudgetRepositoryInterface $repository * @param Budget $budget diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 98a8392ca4..f8d9882e28 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -153,78 +153,6 @@ class CategoryController extends Controller } - /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * @param Collection $categories - * - * @return \Illuminate\Http\JsonResponse - */ - public function multiYear(Carbon $start, Carbon $end, Collection $accounts, Collection $categories) - { - - /** @var CRI $repository */ - $repository = app(CRI::class); - - // chart properties for cache: - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($accounts); - $cache->addProperty($categories); - $cache->addProperty('multiYearCategory'); - - if ($cache->has()) { - return Response::json($cache->get()); - } - - $entries = new Collection; - - /** @var Category $category */ - foreach ($categories as $category) { - $entry = ['name' => '', 'spent' => [], 'earned' => []]; - - $currentStart = clone $start; - while ($currentStart < $end) { - // fix the date: - $year = $currentStart->year; - $currentEnd = clone $currentStart; - $currentEnd->endOfYear(); - - // get data: - if (is_null($category->id)) { - $entry['name'] = trans('firefly.noCategory'); - $entry['spent'][$year] = ($repository->spentInPeriodWithoutCategory($accounts, $currentStart, $currentEnd) * -1); - $entry['earned'][$year] = $repository->earnedInPeriodWithoutCategory($accounts, $currentStart, $currentEnd); - - // jump to next year. - $currentStart = clone $currentEnd; - $currentStart->addDay(); - continue; - - } - // alternative is a normal category: - $entry['name'] = $category->name; - $entry['spent'][$year] = ($repository->spentInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd) * -1); - $entry['earned'][$year] = $repository->earnedInPeriod(new Collection([$category]), $accounts, $currentStart, $currentEnd); - - // jump to next year. - $currentStart = clone $currentEnd; - $currentStart->addDay(); - } - $entries->push($entry); - } - - // generate chart with data: - $data = $this->generator->multiYear($entries); - $cache->store($data); - - return Response::json($data); - - - } - /** * @param CRI $repository * @param Category $category diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 77a50d7218..7878830f15 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -21,8 +21,6 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use Illuminate\Support\Collection; use Preferences; use Session; @@ -244,10 +242,11 @@ class ReportController extends Controller */ private function defaultMultiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) { + // need all budgets + // need all years. + $years = $this->helper->listOfYears($start, $end); + $budgetMultiYear = $this->helper->getBudgetMultiYear($start, $end, $accounts); - $budgets = app(BudgetRepositoryInterface::class)->getActiveBudgets(); - $categories = app(CategoryRepositoryInterface::class)->getCategories(); - $tags = $this->helper->tagReport($start, $end, $accounts); // and some id's, joined: $accountIds = []; @@ -260,7 +259,8 @@ class ReportController extends Controller return view( 'reports.default.multi-year', compact( - 'budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'reportType', 'tags' + 'accounts', 'start', 'end', 'accountIds', 'reportType', + 'years', 'budgetMultiYear' ) ); } @@ -275,8 +275,6 @@ class ReportController extends Controller */ private function defaultYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) { - $tags = $this->helper->tagReport($start, $end, $accounts); - Session::flash('gaEventCategory', 'report'); Session::flash('gaEventAction', 'year'); Session::flash('gaEventLabel', $start->format('Y')); @@ -293,7 +291,7 @@ class ReportController extends Controller 'reports.default.year', compact( 'start', 'reportType', - 'accountIds', 'end', 'tags' + 'accountIds', 'end' ) ); } diff --git a/public/js/ff/reports/default/multi-year.js b/public/js/ff/reports/default/multi-year.js index 78845f5100..04b9ab3ddb 100644 --- a/public/js/ff/reports/default/multi-year.js +++ b/public/js/ff/reports/default/multi-year.js @@ -15,142 +15,4 @@ function drawChart() { lineChart('chart/report/net-worth/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'net-worth'); columnChart('chart/report/in-out/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-chart'); columnChart('chart/report/in-out-sum/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'income-expenses-sum-chart'); - - - $.each($('.account-chart'), function (i, v) { - var holder = $(v); - console.log('Will draw chart for account #' + holder.data('id')); - }); - - // draw budget chart based on selected budgets: - $('.budget-checkbox').on('change', updateBudgetChart); - selectBudgetsByCookie(); - updateBudgetChart(); - - // draw category chart based on selected budgets: - $('.category-checkbox').on('change', updateCategoryChart); - selectCategoriesByCookie(); - updateCategoryChart(); } - -function selectBudgetsByCookie() { - "use strict"; - var cookie = readCookie('multi-year-budgets'); - if (cookie !== null) { - var cookieArray = cookie.split(','); - for (var x in cookieArray) { - var budgetId = cookieArray[x]; - $('.budget-checkbox[value="' + budgetId + '"').prop('checked', true); - } - } -} - -function selectCategoriesByCookie() { - "use strict"; - var cookie = readCookie('multi-year-categories'); - if (cookie !== null) { - var cookieArray = cookie.split(','); - for (var x in cookieArray) { - var categoryId = cookieArray[x]; - $('.category-checkbox[value="' + categoryId + '"').prop('checked', true); - } - } -} - -function updateBudgetChart() { - "use strict"; - console.log('will update budget chart.'); - // get all budget ids: - var budgets = []; - $.each($('.budget-checkbox'), function (i, v) { - var current = $(v); - if (current.prop('checked')) { - budgets.push(current.val()); - } - }); - - if (budgets.length > 0) { - - var budgetIds = budgets.join(','); - - // remove old chart: - $('#budgets-chart').replaceWith(''); - - // hide message: - $('#budgets-chart-message').hide(); - - // draw chart. Redraw when exists? Not sure if we support that. - columnChart('chart/budget/multi-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds + '/' + budgetIds, 'budgets-chart'); - createCookie('multi-year-budgets', budgets, 365); - } else { - // hide canvas, show message: - $('#budgets-chart-message').show(); - $('#budgets-chart').hide(); - - } - -} - -function updateCategoryChart() { - "use strict"; - console.log('will update category chart.'); - // get all category ids: - var categories = []; - $.each($('.category-checkbox'), function (i, v) { - var current = $(v); - if (current.prop('checked')) { - categories.push(current.val()); - } - }); - - if (categories.length > 0) { - - var categoryIds = categories.join(','); - - // remove old chart: - $('#categories-chart').replaceWith(''); - - // hide message: - $('#categories-chart-message').hide(); - - // draw chart. Redraw when exists? Not sure if we support that. - columnChart('chart/category/multi-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds + '/' + categoryIds, 'categories-chart'); - createCookie('multi-year-categories', categories, 365); - } else { - // hide canvas, show message: - $('#categories-chart-message').show(); - $('#categories-chart').hide(); - - } -} - - -function createCookie(name, value, days) { - "use strict"; - var expires; - - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toGMTString(); - } else { - expires = ""; - } - document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; -} - -function readCookie(name) { - "use strict"; - var nameEQ = encodeURIComponent(name) + "="; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); - } - return null; -} - -function eraseCookie(name) { - createCookie(name, "", -1); -} \ No newline at end of file diff --git a/resources/views/reports/default/multi-year.twig b/resources/views/reports/default/multi-year.twig index 9eeff7cafe..314415d2f8 100644 --- a/resources/views/reports/default/multi-year.twig +++ b/resources/views/reports/default/multi-year.twig @@ -69,28 +69,8 @@ -
- {% include 'reports/partials/tags.twig' %} -
- - {% for account in accounts %} - - {% endfor %} -
@@ -104,71 +84,44 @@
-
-
-
-
-

Selected budgets

-
-
-
-
- - -
-
-
-
- - {% for budget in budgets %} - - {% endfor %} -
-
-
-
-
-
-

Selected categories

+

{{ 'budgets'|_ }}

-
-
-
- - -
-
-
-
- - {% for category in categories %} - +
+ + + + + {% for year in years %} + {% endfor %} - - + + + + + + {% for id, info in budgetMultiYear %} + + + {% for amount in info.entries %} + + {% endfor %} + + + {% endfor %} + +
Budget{{ year }}
{{ info.name }} + {{ amount|formatAmount }} +
- {% endblock %} {% block styles %} diff --git a/routes/web.php b/routes/web.php index 69fd01df55..7a5072cf29 100755 --- a/routes/web.php +++ b/routes/web.php @@ -198,17 +198,12 @@ Route::group( Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']); // this chart is used in reports: - Route::get('/chart/budget/multi-year/default/{start_date}/{end_date}/{accountList}/{budgetList}', ['uses' => 'Chart\BudgetController@multiYear']); - Route::get('/chart/budget/period/{budget}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']); Route::get('/chart/budget/{budget}/{limitrepetition}', ['uses' => 'Chart\BudgetController@budgetLimit']); Route::get('/chart/budget/{budget}', ['uses' => 'Chart\BudgetController@budget']); // categories: Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']); - // these three charts are for reports: - Route::get('/chart/category/multi-year/default/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear']); - Route::get('/chart/category/{category}/period', ['uses' => 'Chart\CategoryController@currentPeriod']); Route::get('/chart/category/{category}/period/{date}', ['uses' => 'Chart\CategoryController@specificPeriod']); Route::get('/chart/category/{category}/all', ['uses' => 'Chart\CategoryController@all']);