diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 373c4fa72f..453ff2d78a 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -18,13 +18,17 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface; 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\Support\Collection; +use Log; use Navigation; use Preferences; +use Steam; use View; /** @@ -152,24 +156,78 @@ class CategoryController extends Controller /** * @return View */ - public function noCategory() + public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '') { - /** @var Carbon $start */ - $start = session('start', Carbon::now()->startOfMonth()); - /** @var Carbon $end */ - $end = session('end', Carbon::now()->startOfMonth()); + // default values: + $range = Preferences::get('viewRange', '1M')->data; + $start = null; + $end = null; + $periods = new Collection; - // new collector: - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->withoutCategory();//->groupJournals(); - $journals = $collector->getJournals(); - $subTitle = trans( - 'firefly.without_category_between', - ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] - ); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_journals_without_category'); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; + } - return view('categories.no-category', compact('journals', 'subTitle')); + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->noCategoryPeriodEntries(); + } + + // prep for current period + if (strlen($moment) === 0) { + $start = clone session('start', Navigation::startOfPeriod(new Carbon, $range)); + $end = clone session('end', Navigation::endOfPeriod(new Carbon, $range)); + $periods = $this->noCategoryPeriodEntries(); + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + + $count = 0; + $loop = 0; + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at no-cat loop start.'); + while ($count === 0 && $loop < 3) { + $loop++; + Log::info('Count is zero, search for journals.'); + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount(); + $collector->disableInternalFilter(); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('/categories/list/no-category'); + $count = $journals->getCollection()->count(); + if ($count === 0) { + $start->subDay(); + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfPeriod($start, $range); + Log::info(sprintf('Count is still zero, go back in time to "%s" and "%s"!', $start->format('Y-m-d'), $end->format('Y-m-d'))); + } + } + + // fix title: + if ((strlen($moment) > 0 && $moment !== 'all') || strlen($moment) === 0) { + $subTitle = trans( + 'firefly.without_category_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + + return view('categories.no-category', compact('journals', 'subTitle', 'moment', 'periods', 'start', 'end')); } /** @@ -356,4 +414,78 @@ class CategoryController extends Controller return $entries; } + + /** + * @return Collection + */ + private function noCategoryPeriodEntries(): Collection + { + $repository = app(JournalRepositoryInterface::class); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $range = Preferences::get('viewRange', '1M')->data; + $start = Navigation::startOfPeriod($start, $range); + $end = Navigation::endOfX(new Carbon, $range); + $entries = new Collection; + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('no-budget-period-entries'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + Log::debug('Going to get period expenses and incomes.'); + while ($end >= $start) { + $end = Navigation::startOfPeriod($end, $range); + $currentEnd = Navigation::endOfPeriod($end, $range); + + // count journals without budget in this period: + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount(); + $count = $collector->getJournals()->count(); + + // amount transferred + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory() + ->withOpposingAccount()->setTypes([TransactionType::TRANSFER])->disableInternalFilter(); + $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); + + // amount spent + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]); + $spent = $collector->getJournals()->sum('transaction_amount'); + + // amount earned + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]); + $earned = $collector->getJournals()->sum('transaction_amount'); + + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $entries->push( + [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $count, + 'spent' => $spent, + 'earned' => $earned, + 'transferred' => $transferred, + 'date' => clone $end, + ] + ); + $end = Navigation::subtractPeriod($end, $range, 1); + } + $cache->store($entries); + + return $entries; + } + } diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 6ef2d16b4b..247eb5f482 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -354,12 +354,27 @@ Breadcrumbs::register( ); Breadcrumbs::register( - 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, $subTitle) { + 'categories.no-category', function (BreadCrumbGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); - $breadcrumbs->push($subTitle, route('categories.no-category')); + $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); + + // push when is all: + if ($moment === 'all') { + $breadcrumbs->push(trans('firefly.all_journals_without_category'), route('categories.no-category', ['all'])); + } + // when is specific period: + if (strlen($moment) > 0 && $moment !== 'all') { + $title = trans('firefly.without_category_between', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + ); + $breadcrumbs->push($title, route('categories.no-category', [$moment])); + } + + } ); + /** * CURRENCIES */ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index cbf0516c67..972214df99 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -116,9 +116,12 @@ return [ 'multi_select_all_selected' => 'All selected', 'multi_select_filter_placeholder' => 'Find..', 'all_journals_without_budget' => 'All transactions without a budget', + 'all_journals_without_category' => 'All transactions without a category', 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_for_account' => 'All transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', + 'journals_without_category' => 'Transactions without a category', + 'all_journals_for_account' => 'All transactions for account :name', + 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', + 'transferred' => 'Transferred', // repeat frequencies: diff --git a/resources/views/budgets/no-budget.twig b/resources/views/budgets/no-budget.twig index 1b169a6270..66b6dbec6a 100644 --- a/resources/views/budgets/no-budget.twig +++ b/resources/views/budgets/no-budget.twig @@ -22,7 +22,7 @@
@@ -53,7 +53,7 @@
{{ 'transactions'|_ }} | +{{ entry.count }} | +
{{ 'spent'|_ }} | +{{ entry.spent|formatAmount }} | +
{{ 'earned'|_ }} | +{{ entry.earned|formatAmount }} | +
{{ 'transferred'|_ }} | +{{ entry.transferred|formatAmountPlain }} | +