diff --git a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php index cf3a8a1dd9..5b79fdbf47 100644 --- a/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php +++ b/app/Generator/Chart/Category/ChartJsCategoryChartGenerator.php @@ -155,6 +155,39 @@ class ChartJsCategoryChartGenerator implements CategoryChartGenerator } + /** + * @param Collection $categories + * @param Collection $entries + * + * @return array + */ + public function spentInPeriod(Collection $categories, Collection $entries) + { + + // language: + $format = trans('config.month'); + + $data = [ + 'count' => 0, + 'labels' => [], + 'datasets' => [], + ]; + + foreach ($categories as $category) { + $data['labels'][] = $category->name; + } + + foreach ($entries as $entry) { + $date = $entry[0]->formatLocalized($format); + array_shift($entry); + $data['count']++; + $data['datasets'][] = ['label' => $date, 'data' => $entry]; + } + + return $data; + + } + /** * @param Collection $entries * diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 5aa8db7ae0..7f563c7403 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -348,7 +348,7 @@ class CategoryController extends Controller } /** - * Returns a chart of what has been in this period in each category + * Returns a chart of what has been earned in this period in each category * grouped by month. * * @param CategoryRepositoryInterface $repository @@ -397,6 +397,11 @@ class CategoryController extends Controller // $categories contains all the categories the user has earned money // in in this period. $categories = $categories->unique('id'); + $categories = $categories->sortBy( + function (Category $category) { + return $category->name; + } + ); // start looping the time again, this time processing the // data for each month. @@ -446,4 +451,91 @@ class CategoryController extends Controller } + /** + * Returns a chart of what has been spent in this period in each category + * grouped by month. + * + * @param CategoryRepositoryInterface $repository + * @param $report_type + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return \Illuminate\Http\JsonResponse + */ + public function spentInPeriod(CategoryRepositoryInterface $repository, $report_type, Carbon $start, Carbon $end, Collection $accounts) + { + $original = clone $start; + $cache = new CacheProperties; // chart properties for cache: + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($report_type); + $cache->addProperty($accounts); + $cache->addProperty('category'); + $cache->addProperty('spent-in-period'); + if ($cache->has()) { + //return Response::json($cache->get()); // @codeCoverageIgnore + } + $categories = new Collection; + $sets = new Collection; + $entries = new Collection; + + // run a very special query each month: + $start = clone $original; + while ($start < $end) { + $currentEnd = clone $start; + $currentStart = clone $start; + $currentStart->startOfMonth(); + $currentEnd->endOfMonth(); + $set = $repository->spentForAccounts($accounts, $currentStart, $currentEnd); + $categories = $categories->merge($set); + $sets->push([$currentStart, $set]); + $start->addMonth(); + } + $categories = $categories->unique('id'); + $categories = $categories->sortBy( + function (Category $category) { + return $category->name; + } + ); + + $start = clone $original; + while ($start < $end) { + $currentEnd = clone $start; + $currentStart = clone $start; + $currentStart->startOfMonth(); + $currentEnd->endOfMonth(); + $currentSet = $sets->first( + function ($key, $value) use ($currentStart) { + // set for this date. + return ($value[0] == $currentStart); + } + ); + $row = [clone $currentStart]; + + /** @var Category $category */ + foreach ($categories as $category) { + /** @var Category $entry */ + $entry = $currentSet[1]->first( + function ($key, $value) use ($category) { + return $value->id == $category->id; + } + ); + if (!is_null($entry)) { + $row[] = $entry->spent; + } else { + $row[] = 0; + } + } + $entries->push($row); + $start->addMonth(); + } + + $data = $this->generator->spentInPeriod($categories, $entries); + $cache->store($data); + + return $data; + + } + } diff --git a/app/Http/routes.php b/app/Http/routes.php index 2f580fe108..fd6ac41c4c 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -415,7 +415,7 @@ Route::group( // these three charts are for reports: Route::get('/chart/category/earned-in-period/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@earnedInPeriod']); - Route::get('/chart/category/spent-in-year/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInYear']); + Route::get('/chart/category/spent-in-period/{report_type}/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\CategoryController@spentInPeriod']); Route::get( '/chart/category/multi-year/{report_type}/{start_date}/{end_date}/{accountList}/{categoryList}', ['uses' => 'Chart\CategoryController@multiYear'] ); diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 3c4a8c5569..55554da840 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -542,11 +542,57 @@ class CategoryRepository extends ComponentRepository implements CategoryReposito )// earned from these things. ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->groupBy('categories.id') + ->groupBy('categories.id') ->get(['categories.*', DB::Raw('SUM(`t_dest`.`amount`) AS `earned`')]); return $collection; } + + /** + * Returns a collection of Categories appended with the amount of money that has been spent + * in these categories, based on the $accounts involved, in period X. + * The amount earned in category X in period X is saved in field "spent". + * + * @param $accounts + * @param $start + * @param $end + * + * @return Collection + */ + public function spentForAccounts(Collection $accounts, Carbon $start, Carbon $end) + { + $accountIds = []; + foreach ($accounts as $account) { + $accountIds[] = $account->id; + } + + + $collection = Auth::user()->categories() + ->leftJoin('category_transaction_journal', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin('transaction_journals', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions AS t_src', function (JoinClause $join) { + $join->on('t_src.transaction_journal_id', '=', 'transaction_journals.id')->where('t_src.amount', '<', 0); + } + ) + ->leftJoin( + 'transactions AS t_dest', function (JoinClause $join) { + $join->on('t_dest.transaction_journal_id', '=', 'transaction_journals.id')->where('t_dest.amount', '>', 0); + } + ) + ->whereIn('t_src.account_id', $accountIds)// from these accounts (spent) + ->whereNotIn('t_dest.account_id', $accountIds)//-- but not from these accounts (spent internally) + ->whereIn( + 'transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::OPENING_BALANCE] + )// spent on these things. + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->groupBy('categories.id') + ->get(['categories.*', DB::Raw('SUM(`t_dest`.`amount`) AS `spent`')]); + + return $collection; + } } diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index ff0a072a5c..9a2116c8d7 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -47,6 +47,19 @@ interface CategoryRepositoryInterface */ public function earnedForAccounts(Collection $accounts, Carbon $start, Carbon $end); + /** + * Returns a collection of Categories appended with the amount of money that has been spent + * in these categories, based on the $accounts involved, in period X. + * The amount earned in category X in period X is saved in field "spent". + * + * @param $accounts + * @param $start + * @param $end + * + * @return Collection + */ + public function spentForAccounts(Collection $accounts, Carbon $start, Carbon $end); + /** * @return Collection */ diff --git a/public/js/reports/default/year.js b/public/js/reports/default/year.js index 44461e38ec..12fbfe7e13 100644 --- a/public/js/reports/default/year.js +++ b/public/js/reports/default/year.js @@ -18,7 +18,7 @@ function drawChart() { 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'); stackedColumnChart('chart/budget/year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budgets'); - stackedColumnChart('chart/category/spent-in-year/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-year'); + stackedColumnChart('chart/category/spent-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-spent-in-period'); stackedColumnChart('chart/category/earned-in-period/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'categories-earned-in-period'); } diff --git a/resources/twig/reports/default/year.twig b/resources/twig/reports/default/year.twig index 7754eeee1b..593fc4f2bf 100644 --- a/resources/twig/reports/default/year.twig +++ b/resources/twig/reports/default/year.twig @@ -49,12 +49,7 @@