From 2c2410e71021f9d18b846388000e3b91cf3c899d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 26 Jan 2019 20:18:42 +0100 Subject: [PATCH] Category overview chart in API and support methods. --- .../Controllers/Chart/CategoryController.php | 141 ++++++++++++++++++ .../Category/CategoryRepository.php | 111 ++++++++++++-- .../Category/CategoryRepositoryInterface.php | 10 ++ 3 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 app/Api/V1/Controllers/Chart/CategoryController.php diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php new file mode 100644 index 0000000000..fc06eeb5d2 --- /dev/null +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -0,0 +1,141 @@ +middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->categoryRepository = app(CategoryRepositoryInterface::class); + $this->categoryRepository->setUser($user); + + return $next($request); + } + ); + } + + + /** + * @param Request $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function overview(Request $request): JsonResponse + { + // parameters for chart: + $start = (string)$request->get('start'); + $end = (string)$request->get('end'); + if ('' === $start || '' === $end) { + throw new FireflyException('Start and end are mandatory parameters.'); + } + $start = Carbon::createFromFormat('Y-m-d', $start); + $end = Carbon::createFromFormat('Y-m-d', $end); + $tempData = []; + $spent = $this->categoryRepository->spentInPeriodPerCurrency(new Collection, new Collection, $start, $end); + $earned = $this->categoryRepository->earnedInPeriodPerCurrency(new Collection, new Collection, $start, $end); + $categories = []; + + foreach ($earned as $categoryId => $row) { + $categoryName = $row['name']; + + // create a new set if necessary, "spent (EUR)": + foreach ($row['earned'] as $currencyId => $expense) { + // find or make set for currency: + $key = sprintf('%s-e', $currencyId); + $decimalPlaces = $expense['currency_decimal_places']; + if (!isset($tempData[$key])) { + $tempData[$key] = [ + 'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $expense['currency_symbol']]), + 'currency_id' => $expense['currency_id'], + 'currency_code' => $expense['currency_code'], + 'currency_symbol' => $expense['currency_symbol'], + 'currency_decimal_places' => $decimalPlaces, + 'type' => 'bar', // line, area or bar + 'yAxisID' => 0, // 0, 1, 2 + 'fill' => null, // true, false, null + 'backgroundColor' => null, // null or hex + 'entries' => [], + ]; + } + $amount = round($expense['earned'], $decimalPlaces); + $categories[$categoryName] = isset($categories[$categoryName]) ? $categories[$categoryName] + $amount : $amount; + $tempData[$key]['entries'][$categoryName] + = $amount; + + } + } + + foreach ($spent as $categoryId => $row) { + $categoryName = $row['name']; + // create a new set if necessary, "spent (EUR)": + foreach ($row['spent'] as $currencyId => $expense) { + // find or make set for currency: + $key = sprintf('%s-s', $currencyId); + $decimalPlaces = $expense['currency_decimal_places']; + if (!isset($tempData[$key])) { + $tempData[$key] = [ + 'label' => (string)trans('firefly.box_spent_in_currency', ['currency' => $expense['currency_symbol']]), + 'currency_id' => $expense['currency_id'], + 'currency_code' => $expense['currency_code'], + 'currency_symbol' => $expense['currency_symbol'], + 'currency_decimal_places' => $decimalPlaces, + 'type' => 'bar', // line, area or bar + 'yAxisID' => 0, // 0, 1, 2 + 'fill' => null, // true, false, null + 'backgroundColor' => null, // null or hex + 'entries' => [], + ]; + } + $amount = round($expense['spent'], $decimalPlaces); + $categories[$categoryName] = isset($categories[$categoryName]) ? $categories[$categoryName] + $amount : $amount; + $tempData[$key]['entries'][$categoryName] + = $amount; + + } + } + + + asort($categories); + $keys = array_keys($categories); + + // re-sort every spent array and add 0 for missing entries. + foreach ($tempData as $index => $set) { + $oldSet = $set['entries']; + $newSet = []; + foreach ($keys as $key) { + $value = $oldSet[$key] ?? 0; + $value = $value < 0 ? $value * -1 : $value; + $newSet[$key] = $value; + } + $tempData[$index]['entries'] = $newSet; + } + $chartData = array_values($tempData); + + return response()->json($chartData); + } +} \ No newline at end of file diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 70b63ca744..6426e9ce3a 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -116,6 +116,68 @@ class CategoryRepository implements CategoryRepositoryInterface return $collector->getTransactions(); } + /** + * @param Collection $categories + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function earnedInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array + { + /** @var TransactionCollectorInterface $collector */ + $collector = app(TransactionCollectorInterface::class); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); + + if ($categories->count() > 0) { + $collector->setCategories($categories); + } + if ($categories->count() === 0) { + $collector->setCategories($this->getCategories()); + } + + if ($accounts->count() > 0) { + $collector->setAccounts($accounts); + } + if (0 === $accounts->count()) { + $collector->setAllAssetAccounts(); + } + + $set = $collector->getTransactions(); + $return = []; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + $jrnlCatId = (int)$transaction->transaction_journal_category_id; + $transCatId = (int)$transaction->transaction_category_id; + $categoryId = max($jrnlCatId, $transCatId); + $currencyId = (int)$transaction->transaction_currency_id; + $name = $transaction->transaction_category_name; + $name = '' === (string)$name ? $transaction->transaction_journal_category_name : $name; + // make array for category: + if (!isset($return[$categoryId])) { + $return[$categoryId] = [ + 'name' => $name, + 'earned' => [], + ]; + } + if (!isset($return[$categoryId]['earned'][$currencyId])) { + $return[$categoryId]['earned'][$currencyId] = [ + 'earned' => '0', + 'currency_id' => $currencyId, + 'currency_symbol' => $transaction->transaction_currency_symbol, + 'currency_code' => $transaction->transaction_currency_code, + 'currency_decimal_places' => $transaction->transaction_currency_dp, + ]; + } + $return[$categoryId]['earned'][$currencyId]['earned'] + = bcadd($return[$categoryId]['earned'][$currencyId]['earned'], $transaction->transaction_amount); + } + + return $return; + } + /** * Find a category. * @@ -187,6 +249,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $this->user->categories()->whereIn('id', $categoryIds)->get(); } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Returns a list of all the categories belonging to a user. * @@ -205,8 +269,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $set; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param Category $category * @param Collection $accounts @@ -237,6 +299,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $lastJournalDate; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $categories * @param Collection $accounts @@ -282,8 +346,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $data; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param Collection $accounts * @param Carbon $start @@ -367,6 +429,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $data; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $accounts * @param Carbon $start @@ -419,6 +483,7 @@ class CategoryRepository implements CategoryRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $categories * @param Collection $accounts @@ -435,7 +500,6 @@ class CategoryRepository implements CategoryRepositoryInterface return (string)$set->sum('transaction_amount'); } - /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param Collection $categories * @param Collection $accounts @@ -521,7 +585,14 @@ class CategoryRepository implements CategoryRepositoryInterface /** @var TransactionCollectorInterface $collector */ $collector = app(TransactionCollectorInterface::class); $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setCategories($categories); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); + + if ($categories->count() > 0) { + $collector->setCategories($categories); + } + if ($categories->count() === 0) { + $collector->setCategories($this->getCategories()); + } if ($accounts->count() > 0) { $collector->setAccounts($accounts); @@ -534,9 +605,31 @@ class CategoryRepository implements CategoryRepositoryInterface $return = []; /** @var Transaction $transaction */ foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $return[$currencyId] = $return[$currencyId] ?? '0'; - $return[$currencyId] = bcadd($return[$currencyId], $transaction->transaction_amount); + $jrnlCatId = (int)$transaction->transaction_journal_category_id; + $transCatId = (int)$transaction->transaction_category_id; + $categoryId = max($jrnlCatId, $transCatId); + $currencyId = (int)$transaction->transaction_currency_id; + $name = $transaction->transaction_category_name; + $name = '' === (string)$name ? $transaction->transaction_journal_category_name : $name; + + // make array for category: + if (!isset($return[$categoryId])) { + $return[$categoryId] = [ + 'name' => $name, + 'spent' => [], + ]; + } + if (!isset($return[$categoryId]['spent'][$currencyId])) { + $return[$categoryId]['spent'][$currencyId] = [ + 'spent' => '0', + 'currency_id' => $currencyId, + 'currency_symbol' => $transaction->transaction_currency_symbol, + 'currency_code' => $transaction->transaction_currency_code, + 'currency_decimal_places' => $transaction->transaction_currency_dp, + ]; + } + $return[$categoryId]['spent'][$currencyId]['spent'] + = bcadd($return[$categoryId]['spent'][$currencyId]['spent'], $transaction->transaction_amount); } return $return; diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index c4944fe8ba..d1b1ef42ee 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -205,6 +205,16 @@ interface CategoryRepositoryInterface */ public function spentInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array; + /** + * @param Collection $categories + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function earnedInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array; + /** * @param Collection $accounts * @param Carbon $start