From c424bb097d5488062458ae025228e343f0dfd49a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 14 Jul 2018 22:48:22 +0200 Subject: [PATCH] Improve category code quality. --- .../V1/Controllers/BudgetLimitController.php | 3 + .../Controllers/Budget/ShowController.php | 2 + .../Category/NoCategoryController.php | 225 ++++++++++++ .../Controllers/Category/ShowController.php | 217 ++++++++++++ app/Http/Controllers/CategoryController.php | 295 +--------------- resources/views/categories/no-category.twig | 6 +- resources/views/categories/show.twig | 8 +- routes/breadcrumbs.php | 55 +-- routes/web.php | 25 +- .../Category/NoCategoryControllerTest.php | 175 ++++++++++ .../Category/ShowControllerTest.php | 236 +++++++++++++ .../Controllers/CategoryControllerTest.php | 327 +----------------- 12 files changed, 922 insertions(+), 652 deletions(-) create mode 100644 app/Http/Controllers/Category/NoCategoryController.php create mode 100644 app/Http/Controllers/Category/ShowController.php create mode 100644 tests/Feature/Controllers/Category/NoCategoryControllerTest.php create mode 100644 tests/Feature/Controllers/Category/ShowControllerTest.php diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index d4cf2dd75c..1acbae59d5 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -97,8 +97,11 @@ class BudgetLimitController extends Controller $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $budgetId = (int)($request->get('budget_id') ?? 0); $budget = $this->repository->findNull($budgetId); + $start = null; + $end = null; $this->parameters->set('budget_id', $budgetId); + try { $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); $this->parameters->set('start', $start->format('Y-m-d')); diff --git a/app/Http/Controllers/Budget/ShowController.php b/app/Http/Controllers/Budget/ShowController.php index bbb9939df2..1d815d8f5f 100644 --- a/app/Http/Controllers/Budget/ShowController.php +++ b/app/Http/Controllers/Budget/ShowController.php @@ -228,6 +228,8 @@ class ShowController extends Controller /** * @return Collection + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ private function getPeriodOverview(): Collection { diff --git a/app/Http/Controllers/Category/NoCategoryController.php b/app/Http/Controllers/Category/NoCategoryController.php new file mode 100644 index 0000000000..d98a546e75 --- /dev/null +++ b/app/Http/Controllers/Category/NoCategoryController.php @@ -0,0 +1,225 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Category; + + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Support\CacheProperties; +use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use Log; + +/** + * + * Class NoCategoryController + */ +class NoCategoryController extends Controller +{ + + /** @var JournalRepositoryInterface */ + private $journalRepos; + /** @var CategoryRepositoryInterface */ + private $repository; + + /** + * CategoryController constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->journalRepos = app(JournalRepositoryInterface::class); + $this->repository = app(CategoryRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Request $request + * @param string|null $moment + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function show(Request $request, Carbon $start = null, Carbon $end = null) + { + Log::debug('Start of noCategory()'); + /** @var Carbon $start */ + $start = $start ?? session('start'); + /** @var Carbon $end */ + $end = $end ?? session('end'); + $moment = ''; + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getNoCategoryPeriodOverview($start); + + Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d'))); + Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d'))); + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transactions = $collector->getPaginatedJournals(); + $transactions->setPath(route('categories.no-category')); + + return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end')); + } + + + /** + * @param Request $request + * @param string|null $moment + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showAll(Request $request, string $moment = null) + { + // default values: + $moment = $moment ?? ''; + $start = null; + $end = null; + $periods = new Collection; + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + Log::debug('Start of noCategory()'); + $subTitle = trans('firefly.all_journals_without_category'); + $first = $this->journalRepos->firstNull(); + $start = null === $first ? new Carbon : $first->date; + $end = new Carbon; + Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d'))); + Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d'))); + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transactions = $collector->getPaginatedJournals(); + $transactions->setPath(route('categories.no-category')); + + return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end')); + } + + + /** + * @param Carbon $theDate + * + * @return Collection + */ + private function getNoCategoryPeriodOverview(Carbon $theDate): Collection + { + Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d'))); + $range = app('preferences')->get('viewRange', '1M')->data; + $first = $this->journalRepos->firstNull(); + $start = null === $first ? new Carbon : $first->date; + $end = $theDate ?? new Carbon; + + Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d'))); + Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d'))); + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('no-category-period-entries'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $dates = app('navigation')->blockPeriods($start, $end, $range); + $entries = new Collection; + + foreach ($dates as $date) { + + // count journals without category in this period: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() + ->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $count = $collector->getJournals()->count(); + + // amount transferred + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() + ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount')); + + // amount spent + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( + [TransactionType::WITHDRAWAL] + ); + $spent = $collector->getJournals()->sum('transaction_amount'); + + // amount earned + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( + [TransactionType::DEPOSIT] + ); + $earned = $collector->getJournals()->sum('transaction_amount'); + /** @noinspection PhpUndefinedMethodInspection */ + $dateStr = $date['end']->format('Y-m-d'); + $dateName = app('navigation')->periodShow($date['end'], $date['period']); + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $count, + 'spent' => $spent, + 'earned' => $earned, + 'transferred' => $transferred, + 'start' => clone $date['start'], + 'end' => clone $date['end'], + ] + ); + } + Log::debug('End of loops'); + $cache->store($entries); + + return $entries; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Category/ShowController.php b/app/Http/Controllers/Category/ShowController.php new file mode 100644 index 0000000000..824600dc2e --- /dev/null +++ b/app/Http/Controllers/Category/ShowController.php @@ -0,0 +1,217 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Category; + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Category; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Support\CacheProperties; +use Illuminate\Http\Request; +use Illuminate\Support\Collection; + + +/** + * + * Class ShowController + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ShowController extends Controller +{ + + /** @var AccountRepositoryInterface */ + private $accountRepos; + /** @var JournalRepositoryInterface */ + private $journalRepos; + /** @var CategoryRepositoryInterface */ + private $repository; + + /** + * CategoryController constructor. + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->journalRepos = app(JournalRepositoryInterface::class); + $this->repository = app(CategoryRepositoryInterface::class); + $this->accountRepos = app(AccountRepositoryInterface::class); + + return $next($request); + } + ); + } + + + /** @noinspection MoreThanThreeArgumentsInspection */ + /** + * @param Request $request + * @param Category $category + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null) + { + /** @var Carbon $start */ + $start = $start ?? session('start'); + /** @var Carbon $end */ + $end = $end ?? session('end'); + $subTitleIcon = 'fa-bar-chart'; + $moment = ''; + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $periods = $this->getPeriodOverview($category, $start); + $path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $subTitle = trans( + 'firefly.journals_in_period_for_category', + ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), + 'end' => $end->formatLocalized($this->monthAndDayFormat),] + ); + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); + $collector->removeFilter(InternalTransferFilter::class); + $transactions = $collector->getPaginatedJournals(); + $transactions->setPath($path); + + return view('categories.show', compact('category', 'transactions', 'moment', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); + } + + /** + * @param Request $request + * @param Category $category + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showAll(Request $request, Category $category) + { + // default values: + $subTitleIcon = 'fa-bar-chart'; + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $start = null; + $end = null; + $periods = new Collection; + $moment = 'all'; + + $subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]); + $first = $this->repository->firstUseDate($category); + /** @var Carbon $start */ + $start = $first ?? new Carbon; + $end = new Carbon; + $path = route('categories.show-all', [$category->id]); + + + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); + $collector->removeFilter(InternalTransferFilter::class); + $transactions = $collector->getPaginatedJournals(); + $transactions->setPath($path); + + return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); + } + + /** + * @param Category $category + * + * @param Carbon $date + * + * @return Collection + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function getPeriodOverview(Category $category, Carbon $date): Collection + { + $range = app('preferences')->get('viewRange', '1M')->data; + $first = $this->journalRepos->firstNull(); + $start = null === $first ? new Carbon : $first->date; + $end = $date ?? new Carbon; + $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + + // properties for entries with their amounts. + $cache = new CacheProperties(); + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($range); + $cache->addProperty('categories.entries'); + $cache->addProperty($category->id); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + /** @var array $dates */ + $dates = app('navigation')->blockPeriods($start, $end, $range); + $entries = new Collection; + + foreach ($dates as $currentDate) { + $spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); + $earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); + /** @noinspection PhpUndefinedMethodInspection */ + $dateStr = $currentDate['end']->format('Y-m-d'); + $dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); + + // amount transferred + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category) + ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); + $collector->removeFilter(InternalTransferFilter::class); + $transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount')); + + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'spent' => $spent, + 'earned' => $earned, + 'sum' => bcadd($earned, $spent), + 'transferred' => $transferred, + 'start' => clone $currentDate['start'], + 'end' => clone $currentDate['end'], + ] + ); + } + $cache->store($entries); + + return $entries; + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 184a04e6bd..233cc93f34 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -22,31 +22,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; -use Carbon\Carbon; -use FireflyIII\Helpers\Collector\JournalCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Http\Requests\CategoryFormRequest; -use FireflyIII\Models\AccountType; use FireflyIII\Models\Category; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; -use Log; /** * Class CategoryController. */ class CategoryController extends Controller { - /** @var AccountRepositoryInterface */ - private $accountRepos; - /** @var JournalRepositoryInterface */ - private $journalRepos; /** @var CategoryRepositoryInterface */ private $repository; @@ -61,9 +48,7 @@ class CategoryController extends Controller function ($request, $next) { app('view')->share('title', trans('firefly.categories')); app('view')->share('mainTitleIcon', 'fa-bar-chart'); - $this->journalRepos = app(JournalRepositoryInterface::class); - $this->repository = app(CategoryRepositoryInterface::class); - $this->accountRepos = app(AccountRepositoryInterface::class); + $this->repository = app(CategoryRepositoryInterface::class); return $next($request); } @@ -163,139 +148,6 @@ class CategoryController extends Controller return view('categories.index', compact('categories')); } - /** - * @param Request $request - * @param string|null $moment - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function noCategory(Request $request, string $moment = null) - { - // default values: - $moment = $moment ?? ''; - $range = app('preferences')->get('viewRange', '1M')->data; - $start = null; - $end = null; - $periods = new Collection; - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - Log::debug('Start of noCategory()'); - // prep for "all" view. - if ('all' === $moment) { - $subTitle = trans('firefly.all_journals_without_category'); - $first = $this->journalRepos->firstNull(); - $start = null === $first ? new Carbon : $first->date; - $end = new Carbon; - Log::debug('Moment is all()'); - } - - // prep for "specific date" view. - if ('all' !== $moment && \strlen($moment) > 0) { - /** @var Carbon $start */ - $start = app('navigation')->startOfPeriod(new Carbon($moment), $range); - /** @var Carbon $end */ - $end = app('navigation')->endOfPeriod($start, $range); - $subTitle = trans( - 'firefly.without_category_between', - ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] - ); - $periods = $this->getNoCategoryPeriodOverview($start); - - } - - // prep for current period - if ('' === $moment) { - $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); - $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); - $periods = $this->getNoCategoryPeriodOverview($start); - $subTitle = trans( - 'firefly.without_category_between', - ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] - ); - } - Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d'))); - Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d'))); - - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() - ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedJournals(); - $transactions->setPath(route('categories.no-category')); - - return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end')); - } - - /** - * @param Request $request - * @param Category $category - * @param string|null $moment - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function show(Request $request, Category $category, string $moment = null) - { - // default values: - $moment = $moment ?? ''; - $subTitle = $category->name; - $subTitleIcon = 'fa-bar-chart'; - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $range = app('preferences')->get('viewRange', '1M')->data; - $start = null; - $end = null; - $periods = new Collection; - $path = route('categories.show', [$category->id]); - - // prep for "all" view. - if ('all' === $moment) { - $subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]); - $first = $this->repository->firstUseDate($category); - /** @var Carbon $start */ - $start = $first ?? new Carbon; - $end = new Carbon; - $path = route('categories.show', [$category->id, 'all']); - } - - // prep for "specific date" view. - if ('all' !== $moment && \strlen($moment) > 0) { - $start = app('navigation')->startOfPeriod(new Carbon($moment), $range); - /** @var Carbon $end */ - $end = app('navigation')->endOfPeriod($start, $range); - $subTitle = trans( - 'firefly.journals_in_period_for_category', - ['name' => $category->name, - 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),] - ); - $periods = $this->getPeriodOverview($category, $start); - $path = route('categories.show', [$category->id, $moment]); - } - - // prep for current period - if ('' === $moment) { - /** @var Carbon $start */ - $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); - /** @var Carbon $end */ - $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); - $periods = $this->getPeriodOverview($category, $start); - $subTitle = trans( - 'firefly.journals_in_period_for_category', - ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), - 'end' => $end->formatLocalized($this->monthAndDayFormat),] - ); - } - - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedJournals(); - $transactions->setPath($path); - - return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); - } /** * @param CategoryFormRequest $request @@ -353,149 +205,4 @@ class CategoryController extends Controller } - /** - * @param Carbon $theDate - * - * @return Collection - */ - private function getNoCategoryPeriodOverview(Carbon $theDate): Collection - { - Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d'))); - $range = app('preferences')->get('viewRange', '1M')->data; - $first = $this->journalRepos->firstNull(); - $start = null === $first ? new Carbon : $first->date; - $end = $theDate ?? new Carbon; - - Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d'))); - Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d'))); - - // properties for cache - $cache = new CacheProperties; - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('no-category-period-entries'); - - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; - - foreach ($dates as $date) { - - // count journals without category in this period: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() - ->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $count = $collector->getJournals()->count(); - - // amount transferred - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() - ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount')); - - // amount spent - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( - [TransactionType::WITHDRAWAL] - ); - $spent = $collector->getJournals()->sum('transaction_amount'); - - // amount earned - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( - [TransactionType::DEPOSIT] - ); - $earned = $collector->getJournals()->sum('transaction_amount'); - /** @noinspection PhpUndefinedMethodInspection */ - $dateStr = $date['end']->format('Y-m-d'); - $dateName = app('navigation')->periodShow($date['end'], $date['period']); - $entries->push( - [ - 'string' => $dateStr, - 'name' => $dateName, - 'count' => $count, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'date' => clone $date['end'], - ] - ); - } - Log::debug('End of loops'); - $cache->store($entries); - - return $entries; - } - - - /** - * @param Category $category - * - * @param Carbon $date - * - * @return Collection - */ - private function getPeriodOverview(Category $category, Carbon $date): Collection - { - $range = app('preferences')->get('viewRange', '1M')->data; - $first = $this->journalRepos->firstNull(); - $start = null === $first ? new Carbon : $first->date; - $end = $date ?? new Carbon; - $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); - - // properties for entries with their amounts. - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($range); - $cache->addProperty('categories.entries'); - $cache->addProperty($category->id); - - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - /** @var array $dates */ - $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; - - foreach ($dates as $currentDate) { - $spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); - $earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); - /** @noinspection PhpUndefinedMethodInspection */ - $dateStr = $currentDate['end']->format('Y-m-d'); - $dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - - // amount transferred - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category) - ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transferred = app('steam')->positive((string)$collector->getJournals()->sum('transaction_amount')); - - $entries->push( - [ - 'string' => $dateStr, - 'name' => $dateName, - 'spent' => $spent, - 'earned' => $earned, - 'sum' => bcadd($earned, $spent), - 'transferred' => $transferred, - 'date' => clone $currentDate['end'], - ] - ); - } - $cache->store($entries); - - return $entries; - } } diff --git a/resources/views/categories/no-category.twig b/resources/views/categories/no-category.twig index caefb483b1..da8077c8b7 100644 --- a/resources/views/categories/no-category.twig +++ b/resources/views/categories/no-category.twig @@ -10,7 +10,7 @@ {% if periods.count > 0 %}
{% endif %} @@ -26,7 +26,7 @@ {% if periods.count > 0 %}

- {{ 'show_all_no_filter'|_ }} + {{ 'show_all_no_filter'|_ }}

{% else %}

@@ -44,7 +44,7 @@ {% if period.count > 0 %}

diff --git a/resources/views/categories/show.twig b/resources/views/categories/show.twig index e456ed03c4..d3ccd0f5e6 100644 --- a/resources/views/categories/show.twig +++ b/resources/views/categories/show.twig @@ -1,7 +1,7 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, category, moment, start, end) }} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, category, '', start, end) }} {% endblock %} {% block content %} @@ -52,7 +52,7 @@ {% if periods.count > 0 %} {% endif %} @@ -69,7 +69,7 @@ {% if periods.count > 0 %}

- + {{ 'show_all_no_filter'|_ }}

@@ -90,7 +90,7 @@ {% if period.spent != 0 or period.earned != 0 or period.sum != 0 %}
diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index dce4573a4c..3c8cfa7263 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -441,20 +441,21 @@ try { function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('categories.show', [$category->id, $moment])); + } + ); - // push when is all: - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); - } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] - ); - $breadcrumbs->push($title, route('categories.show', [$category->id, $moment])); - } + Breadcrumbs::register( + 'categories.show-all', + function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); } ); @@ -463,20 +464,22 @@ try { function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); + $title = trans( + 'firefly.between_dates_breadcrumb', + ['start' => $start->formatLocalized((string)trans('config.month_and_day')), + 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] + ); + $breadcrumbs->push($title, route('categories.no-category', [$moment])); + } + ); - // push when is all: - if ('all' === $moment) { - $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category', ['all'])); - } - // when is specific period or when empty: - if ('all' !== $moment && '(nothing)' !== $moment) { - $title = trans( - 'firefly.between_dates_breadcrumb', - ['start' => $start->formatLocalized((string)trans('config.month_and_day')), - 'end' => $end->formatLocalized((string)trans('config.month_and_day')),] - ); - $breadcrumbs->push($title, route('categories.no-category', [$moment])); - } + + Breadcrumbs::register( + 'categories.no-category-all', + function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + $breadcrumbs->parent('categories.index'); + $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); + $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category-all')); } ); diff --git a/routes/web.php b/routes/web.php index ad10cd6236..b401f43fb5 100755 --- a/routes/web.php +++ b/routes/web.php @@ -225,17 +225,30 @@ Route::group( */ Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'categories', 'as' => 'categories.'], function () { + + // index: Route::get('', ['uses' => 'CategoryController@index', 'as' => 'index']); + + // create Route::get('create', ['uses' => 'CategoryController@create', 'as' => 'create']); - Route::get('edit/{category}', ['uses' => 'CategoryController@edit', 'as' => 'edit']); - Route::get('delete/{category}', ['uses' => 'CategoryController@delete', 'as' => 'delete']); - - Route::get('show/{category}/{moment?}', ['uses' => 'CategoryController@show', 'as' => 'show']); - Route::get('list/no-category/{moment?}', ['uses' => 'CategoryController@noCategory', 'as' => 'no-category']); - Route::post('store', ['uses' => 'CategoryController@store', 'as' => 'store']); + + // edit + Route::get('edit/{category}', ['uses' => 'CategoryController@edit', 'as' => 'edit']); Route::post('update/{category}', ['uses' => 'CategoryController@update', 'as' => 'update']); + + // delete + Route::get('delete/{category}', ['uses' => 'CategoryController@delete', 'as' => 'delete']); Route::post('destroy/{category}', ['uses' => 'CategoryController@destroy', 'as' => 'destroy']); + + // show category: + Route::get('show/{category}/all', ['uses' => 'Category\ShowController@showAll', 'as' => 'show-all']); + Route::get('show/{category}/{start_date?}/{end_date?}', ['uses' => 'Category\ShowController@show', 'as' => 'show']); + + // no category controller: + Route::get('list/no-category/all', ['uses' => 'Category\NoCategoryController@showAll', 'as' => 'no-category-all']); + Route::get('list/no-category/{start_date?}/{end_date?}', ['uses' => 'Category\NoCategoryController@show', 'as' => 'no-category']); + } ); diff --git a/tests/Feature/Controllers/Category/NoCategoryControllerTest.php b/tests/Feature/Controllers/Category/NoCategoryControllerTest.php new file mode 100644 index 0000000000..970bebd2c7 --- /dev/null +++ b/tests/Feature/Controllers/Category/NoCategoryControllerTest.php @@ -0,0 +1,175 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Feature\Controllers\Category; + + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Log; +use Navigation; +use Tests\TestCase; + +/** + * + * Class NoCategoryControllerTest + */ +class NoCategoryControllerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + + /** + * @covers \FireflyIII\Http\Controllers\Category\NoCategoryController + * @dataProvider dateRangeProvider + * + * @param string $range + */ + public function testNoCategory(string $range): void + { + Log::debug(sprintf('Test noCategory(%s)', $range)); + // mock stuff + $collector = $this->mock(JournalCollectorInterface::class); + $categoryRepos = $this->mock(CategoryRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + + + // get the journal with the most recent date for firstNull: + $journal = $this->user()->transactionJournals()->orderBy('date', 'DESC')->first(); + $journalRepos->shouldReceive('firstNull')->twice()->andReturn($journal); + + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('setTypes')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withoutCategory')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn(new Collection); + $collector->shouldReceive('getPaginatedJournals')->andReturn(new LengthAwarePaginator([], 0, 10)); + + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('removeFilter')->withArgs([InternalTransferFilter::class])->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + + $this->be($this->user()); + $this->changeDateRange($this->user(), $range); + $response = $this->get(route('categories.no-category')); + $response->assertStatus(200); + // has bread crumb + $response->assertSee('