diff --git a/app/Generator/Chart/Category/CategoryChartGenerator.php b/app/Generator/Chart/Category/CategoryChartGenerator.php index 60f8c42f01..6b52518a34 100644 --- a/app/Generator/Chart/Category/CategoryChartGenerator.php +++ b/app/Generator/Chart/Category/CategoryChartGenerator.php @@ -19,6 +19,13 @@ interface CategoryChartGenerator */ public function all(Collection $entries); + /** + * @param Collection $entries + * + * @return array + */ + public function multiYear(Collection $entries); + /** * @param Collection $entries * diff --git a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php index aa83c15103..4ea5a119cc 100644 --- a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php +++ b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php @@ -158,4 +158,37 @@ class ChartJsCategoryChartGenerator implements CategoryChartGenerator return $data; } + + /** + * @param Collection $entries + * + * @return array + */ + public function multiYear(Collection $entries) + { + // dataset: + $data = [ + 'count' => 0, + 'labels' => [], + 'datasets' => [], + ]; + // get labels from one of the categories (assuming there's at least one): + $first = $entries->first(); + foreach ($first['spent'] as $year => $noInterest) { + $data['labels'][] = strval($year); + } + + // then, loop all entries and create datasets: + foreach ($entries as $entry) { + $name = $entry['name']; + $spent = $entry['spent']; + $earned = $entry['earned']; + $data['datasets'][] = ['label' => 'Spent in category ' . $name, 'data' => array_values($spent)]; + $data['datasets'][] = ['label' => 'Earned in category ' . $name, 'data' => array_values($earned)]; + } + $data['count'] = count($data['datasets']); + + return $data; + + } } diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index c2279c923a..daa0fdf128 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -56,7 +56,7 @@ class BudgetController extends Controller $cache->addProperty('multiYearBudget'); if ($cache->has()) { - // return Response::json($cache->get()); // @codeCoverageIgnore + return Response::json($cache->get()); // @codeCoverageIgnore } /** diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 7bb2699fb0..ec7dafbef5 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -121,6 +121,84 @@ class CategoryController extends Controller ); $set = new Collection($array); $data = $this->generator->frontpage($set); + $cache->store($data); + + + return Response::json($data); + + } + + /** + * @param CategoryRepositoryInterface $repository + * @param $report_type + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * @param Collection $categories + */ + public function multiYear(CategoryRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts, Collection $categories) + { + // chart properties for cache: + $cache = new CacheProperties(); + $cache->addProperty($report_type); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($accounts); + $cache->addProperty($categories); + $cache->addProperty('multiYearCategory'); + + if ($cache->has()) { + return Response::json($cache->get()); // @codeCoverageIgnore + } + + /** + * category + * year: + * spent: x + * earned: x + * year + * spent: x + * earned: x + */ + $entries = new Collection; + // go by budget, not by year. + /** @var Category $category */ + foreach ($categories as $category) { + $entry = ['name' => '', 'spent' => [], 'earned' => []]; + + $currentStart = clone $start; + while ($currentStart < $end) { + // fix the date: + $currentEnd = clone $currentStart; + $currentEnd->endOfYear(); + + // get data: + if (is_null($category->id)) { + $name = trans('firefly.noCategory'); + $spent = $repository->spentNoCategoryForAccounts($accounts, $currentStart, $currentEnd); + $earned = $repository->earnedNoCategoryForAccounts($accounts, $currentStart, $currentEnd); + } else { + $name = $category->name; + $spent = $repository->spentInPeriodForAccounts($category, $accounts, $currentStart, $currentEnd); + $earned = $repository->earnedInPeriodForAccounts($category, $accounts, $currentStart, $currentEnd); + } + + // save to array: + $year = $currentStart->year; + $entry['name'] = $name; + $entry['spent'][$year] = ($spent * -1); + $entry['earned'][$year] = $earned; + + // 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); diff --git a/app/Http/routes.php b/app/Http/routes.php index 1e3f4c109a..c5b96ed926 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -56,13 +56,13 @@ Route::bind( if (Auth::check()) { $ids = explode(',', $value); /** @var \Illuminate\Support\Collection $object */ - $object = Budget::where('budgets.active', 1) - ->whereIn('budgets.id', $ids) - ->where('budgets.user_id', Auth::user()->id) - ->get(['budgets.*']); + $object = Budget::where('active', 1) + ->whereIn('id', $ids) + ->where('user_id', Auth::user()->id) + ->get(); // add empty budget if applicable. - if(in_array('0', $ids)) { + if (in_array('0', $ids)) { $object->push(new Budget); } @@ -74,6 +74,30 @@ Route::bind( } ); +// category list +Route::bind( + 'categoryList', + function ($value) { + if (Auth::check()) { + $ids = explode(',', $value); + /** @var \Illuminate\Support\Collection $object */ + $object = Category::whereIn('id', $ids) + ->where('user_id', Auth::user()->id) + ->get(); + + // add empty budget if applicable. + if (in_array('0', $ids)) { + $object->push(new Category); + } + + if ($object->count() > 0) { + return $object; + } + } + throw new NotFoundHttpException; + } +); + // Date Route::bind( 'start_date', @@ -244,7 +268,7 @@ Route::get('/cron/sendgrid', ['uses' => 'CronController@sendgrid']); Route::controllers( [ - 'auth' => 'Auth\AuthController', + 'auth' => 'Auth\AuthController', 'password' => 'Auth\PasswordController', ] ); @@ -390,10 +414,12 @@ Route::group( // categories: Route::get('/chart/category/frontpage', ['uses' => 'Chart\CategoryController@frontpage']); - // both charts are for reports: + // these three charts are for reports: Route::get('/chart/category/spent-in-year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInYear']); Route::get('/chart/category/earned-in-year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInYear']); - + Route::get( + '/chart/category/multi-year/{report_type}/{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']); @@ -403,7 +429,9 @@ Route::group( Route::get('/chart/piggyBank/{piggyBank}', ['uses' => 'Chart\PiggyBankController@history']); // reports: - Route::get('/chart/report/in-out/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOut'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']); + Route::get('/chart/report/in-out/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOut'])->where( + ['year' => '[0-9]{4}', 'shared' => 'shared'] + ); Route::get('/chart/report/in-out-sum/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\ReportController@yearInOutSummarized'])->where( ['year' => '[0-9]{4}', 'shared' => 'shared'] ); @@ -472,10 +500,10 @@ Route::group( * Report Controller */ Route::get('/reports', ['uses' => 'ReportController@index', 'as' => 'reports.index']); -// Route::post('/reports/select', ['uses' => 'ReportController@select', 'as' => 'reports.select']); + // Route::post('/reports/select', ['uses' => 'ReportController@select', 'as' => 'reports.select']); Route::get('/reports/report/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'ReportController@report', 'as' => 'reports.report']); -// Route::get('/reports/{year}/{shared?}', ['uses' => 'ReportController@year', 'as' => 'reports.year'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']); -// Route::get('/reports/{year}/{month}/{shared?}', ['uses' => 'ReportController@month', 'as' => 'reports.month'])->where(['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared']); + // Route::get('/reports/{year}/{shared?}', ['uses' => 'ReportController@year', 'as' => 'reports.year'])->where(['year' => '[0-9]{4}', 'shared' => 'shared']); + // Route::get('/reports/{year}/{month}/{shared?}', ['uses' => 'ReportController@month', 'as' => 'reports.month'])->where(['year' => '[0-9]{4}', 'month' => '[0-9]{1,2}', 'shared' => 'shared']); // pop ups for budget report: diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 924104fcba..859ac36106 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -59,6 +59,71 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito return $set; } + /** + * Returns the amount earned without category by accounts in period. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end) + { + + $accountIds = []; + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + + // is deposit AND account_from is in the list of $accounts + // not from any of the accounts in the list? + + return Auth::user() + ->transactionjournals() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start) + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereIn('transactions.account_id', $accountIds) + ->transactionTypes([TransactionType::DEPOSIT]) + ->get(['transaction_journals.*'])->sum('amount'); + } + + + /** + * Returns the amount spent without category by accounts in period. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end) + { + + $accountIds = []; + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + + // is withdrawal or transfer AND account_from is in the list of $accounts + + + return Auth::user() + ->transactionjournals() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('category_transaction_journal.id') + ->before($end) + ->after($start) + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereIn('transactions.account_id', $accountIds) + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->get(['transaction_journals.*'])->sum('amount'); + } + /** * @@ -385,4 +450,65 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito { return $category->transactionjournals()->transactionTypes([TransactionType::DEPOSIT])->onDate($date)->get(['transaction_journals.*'])->sum('amount'); } + + /** + * Calculates how much is spent in this period. + * + * @param Category $category + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end) + { + $accountIds = []; + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + + $sum + = $category + ->transactionjournals() + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->after($start) + ->before($end) + ->whereIn('transactions.account_id', $accountIds) + ->transactionTypes([TransactionType::WITHDRAWAL]) + ->get(['transaction_journals.*']) + ->sum('amount'); + return $sum; + + } + + /** + * Calculate how much is earned in this period. + * + * @param Category $category + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end) + { + $accountIds = []; + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + $sum + = $category + ->transactionjournals() + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->before($end) + ->whereIn('transactions.account_id', $accountIds) + ->transactionTypes([TransactionType::DEPOSIT]) + ->after($start) + ->get(['transaction_journals.*']) + ->sum('amount'); + return $sum; + + } } diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 81719876f8..d54cdb5f55 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -39,6 +39,53 @@ interface CategoryRepositoryInterface */ public function getCategories(); + + /** + * Calculates how much is spent in this period. + * + * @param Category $category + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end); + + /** + * Calculate how much is earned in this period. + * + * @param Category $category + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedInPeriodForAccounts(Category $category, Collection $accounts, Carbon $start, Carbon $end); + + /** + * Returns the amount spent without category by accounts in period. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end); + + /** + * Returns the amount earned without category by accounts in period. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function earnedNoCategoryForAccounts(Collection $accounts, Carbon $start, Carbon $end); + /** * Corrected for tags. * diff --git a/config/app.php b/config/app.php index 1ab4e1f26d..a7b86ca2c5 100644 --- a/config/app.php +++ b/config/app.php @@ -139,8 +139,8 @@ return [ 'TwigBridge\ServiceProvider', 'DaveJamesMiller\Breadcrumbs\ServiceProvider', - //'Barryvdh\Debugbar\ServiceProvider', - //'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', + 'Barryvdh\Debugbar\ServiceProvider', + 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider', 'Zizaco\Entrust\EntrustServiceProvider', /* diff --git a/public/js/reports/default/multi-year.js b/public/js/reports/default/multi-year.js index bf18c64a91..c7fed264f2 100644 --- a/public/js/reports/default/multi-year.js +++ b/public/js/reports/default/multi-year.js @@ -25,10 +25,13 @@ function drawChart() { $('.budget-checkbox').on('change', updateBudgetChart); updateBudgetChart(); - + // draw category chart based on selected budgets: + $('.category-checkbox').on('change', updateCategoryChart); + updateCategoryChart(); } -function updateBudgetChart(e) { +function updateBudgetChart() { + "use strict"; console.log('will update budget chart.'); // get all budget ids: var budgets = []; @@ -58,4 +61,36 @@ function updateBudgetChart(e) { } +} + +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'); + } else { + // hide canvas, show message: + $('#categories-chart-message').show(); + $('#categories-chart').hide(); + + } } \ No newline at end of file diff --git a/resources/lang/en/firefly.php b/resources/lang/en/firefly.php index 2382b55a20..a35a615e00 100644 --- a/resources/lang/en/firefly.php +++ b/resources/lang/en/firefly.php @@ -374,16 +374,16 @@ return [ // 'reportForMonth' => 'Montly report for :month', // 'reportForMonthShared' => 'Montly report for :month (including shared accounts)', - 'report_default' => 'Default financial report for :start until :end', - 'quick_link_reports' => 'Quick links', - 'quick_link_default_report' => 'Default financial report', - 'report_this_month_shared' => 'Current month, all shared accounts', - 'report_this_month_non_shared' => 'Current month, all not-shared accounts', - 'report_this_year_shared' => 'Current year, all shared accounts', - 'report_this_year_non_shared' => 'Current year, all not-shared accounts', - 'report_all_time_shared' => 'All-time, all shared accounts', - 'report_all_time_non_shared' => 'All-time, all not-shared accounts', - 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', + 'report_default' => 'Default financial report for :start until :end', + 'quick_link_reports' => 'Quick links', + 'quick_link_default_report' => 'Default financial report', + 'report_this_month_shared' => 'Current month, all shared accounts', + 'report_this_month_non_shared' => 'Current month, all not-shared accounts', + 'report_this_year_shared' => 'Current year, all shared accounts', + 'report_this_year_non_shared' => 'Current year, all not-shared accounts', + 'report_all_time_shared' => 'All-time, all shared accounts', + 'report_all_time_non_shared' => 'All-time, all not-shared accounts', + 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', 'incomeVsExpenses' => 'Income vs. expenses', 'accountBalances' => 'Account balances', 'balanceStartOfYear' => 'Balance at start of year', @@ -402,6 +402,7 @@ return [ 'outsideOfBudgets' => 'Outside of budgets', 'leftInBudget' => 'Left in budget', 'sumOfSums' => 'Sum of sums', + 'noCategory' => '(no category)', 'notCharged' => 'Not charged (yet)', 'inactive' => 'Inactive', 'difference' => 'Difference', diff --git a/resources/lang/nl/firefly.php b/resources/lang/nl/firefly.php index 6df130e1ec..f7f9e42c55 100644 --- a/resources/lang/nl/firefly.php +++ b/resources/lang/nl/firefly.php @@ -417,6 +417,7 @@ return [ 'outsideOfBudgets' => 'Buiten budgetten', 'leftInBudget' => 'Over van budget', 'sumOfSums' => 'Alles bij elkaar', + 'noCategory' => '(zonder categorie)', 'notCharged' => '(Nog) niet betaald', 'inactive' => 'Niet actief', 'difference' => 'Verschil', diff --git a/resources/twig/reports/default/multi-year.twig b/resources/twig/reports/default/multi-year.twig index 5ab297f952..abd50a687d 100644 --- a/resources/twig/reports/default/multi-year.twig +++ b/resources/twig/reports/default/multi-year.twig @@ -85,14 +85,20 @@