diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index e89143d8b7..b0fc0e28ee 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -14,16 +14,19 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; +use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; use Navigation; use Preferences; use Response; +use Steam; use View; /** @@ -59,109 +62,78 @@ class TransactionController extends Controller * * @return View */ - public function index(Request $request, JournalRepositoryInterface $repository, string $what) + public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '') { - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + // default values: $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what); + $page = intval($request->get('page')) == 0 ? 1 : intval($request->get('page')); + $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); + $count = 0; + $loop = 0; $range = Preferences::get('viewRange', '1M')->data; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - // to make sure we only grab a subset, based on the current date (in session): - $start = session('start', Navigation::startOfPeriod(new Carbon, $range)); - $end = session('end', Navigation::endOfPeriod(new Carbon, $range)); + $start = null; + $end = null; + $periods = new Collection; - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->setRange($start, $end)->withBudgetInformation() - ->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what); - - unset($start, $end); - - // then also show a list of periods where the user can click on, based on the - // user's range and the oldest journal the user has: - $first = $repository->first(); - $blockStart = is_null($first->id) ? new Carbon : $first->date; - $blockStart = Navigation::startOfPeriod($blockStart, $range); - $blockEnd = Navigation::endOfX(new Carbon, $range); - $entries = new Collection; - - while ($blockEnd >= $blockStart) { - Log::debug(sprintf('Now at blockEnd: %s', $blockEnd->format('Y-m-d'))); - $blockEnd = Navigation::startOfPeriod($blockEnd, $range); - $dateStr = $blockEnd->format('Y-m-d'); - $dateName = Navigation::periodShow($blockEnd, $range); - $entries->push([$dateStr, $dateName]); - $blockEnd = Navigation::subtractPeriod($blockEnd, $range, 1); + // prep for "all" view. + if ($moment === 'all') { + $subTitle = trans('firefly.all_' . $what); + $first = $repository->first(); + $start = $first->date ?? new Carbon; + $end = new Carbon; } - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'entries')); + // prep for "specific date" view. + if (strlen($moment) > 0 && $moment !== 'all') { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + $periods = $this->getPeriodEntries($what); + } - } + // 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->getPeriodEntries($what); + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } + // grab journals, but be prepared to jump a period back to get the right ones: + Log::info('Now at transaction 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)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + ->disableInternalFilter(); + $journals = $collector->getPaginatedJournals(); + $journals->setPath('/budgets/list/no-budget'); + $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'))); + } + } - /** - * @param Request $request - * @param string $what - * - * @return View - */ - public function indexAll(Request $request, string $what) - { - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = sprintf('%s (%s)', trans('firefly.title_' . $what), strtolower(trans('firefly.everything'))); - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); + // fix title: + if (((strlen($moment) > 0 && $moment !== 'all') || strlen($moment) === 0) && $count > 0) { + $subTitle = trans( + 'firefly.title_' . $what . '_between', + ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] + ); + } - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts()->withBudgetInformation()->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what . '/all'); - - return view('transactions.index-all', compact('subTitle', 'what', 'subTitleIcon', 'journals')); - - } - - /** - * @param Request $request - * @param string $what - * - * @param string $date - * - * @return View - */ - public function indexByDate(Request $request, string $what, string $date) - { - $carbon = new Carbon($date); - $range = Preferences::get('viewRange', '1M')->data; - $start = Navigation::startOfPeriod($carbon, $range); - $end = Navigation::endOfPeriod($carbon, $range); - $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $subTitle = trans('firefly.title_' . $what) . ' (' . Navigation::periodShow($carbon, $range) . ')'; - $page = intval($request->get('page')) === 0 ? 1 : intval($request->get('page')); - - Log::debug(sprintf('Transaction index by date will show between %s and %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); - - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector->setTypes($types)->setLimit($pageSize)->setPage($page)->setAllAssetAccounts(); - $collector->setRange($start, $end)->withBudgetInformation()->withCategoryInformation(); - $collector->withOpposingAccount(); - $collector->disableInternalFilter(); - $journals = $collector->getPaginatedJournals(); - $journals->setPath('transactions/' . $what . '/' . $date); - - return view('transactions.index-date', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'carbon')); + return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'journals', 'periods', 'start', 'end', 'moment')); } @@ -215,4 +187,78 @@ class TransactionController extends Controller } + /** + * @param string $what + * + * @return Collection + * @throws FireflyException + */ + private function getPeriodEntries(string $what): 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; + $types = config('firefly.transactionTypesByWhat.' . $what); + + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($what); + $cache->addProperty('transaction-list-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)->withOpposingAccount()->setTypes($types)->disableInternalFilter(); + $set = $collector->getJournals(); + $sum = $set->sum('transaction_amount'); + $journals = $set->count(); + $dateStr = $end->format('Y-m-d'); + $dateName = Navigation::periodShow($end, $range); + $array = [ + 'string' => $dateStr, + 'name' => $dateName, + 'count' => $journals, + 'spent' => 0, + 'earned' => 0, + 'transferred' => 0, + 'date' => clone $end, + ]; + switch ($what) { + default: + throw new FireflyException(sprintf('Cannot handle "%s"', $what)); + case 'withdrawal': + $array['spent'] = $sum; + break; + case 'deposit': + $array['earned'] = $sum; + break; + case 'transfers': + case 'transfer': + $array['transferred'] = Steam::positive($sum); + break; + + } + $entries->push($array); + $end = Navigation::subtractPeriod($end, $range, 1); + } + $cache->store($entries); + + return $entries; + } + } diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 247eb5f482..3da5f9bd88 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -75,14 +75,10 @@ Breadcrumbs::register( // push when is all: if ($moment === 'all') { - $breadcrumbs->push(trans('firefly.all_journals_for_account', ['name' => $account->name]), route('accounts.show', [$account->id])); + $breadcrumbs->push(trans('firefly.everything'), route('accounts.show', [$account->id])); } // when is specific period: - if (strlen($moment) > 0 && $moment !== 'all') { - $title = trans('firefly.journals_in_period_for_account', ['name' => $account->name, - 'start' => $start->formatLocalized(strval(trans('config.month_and_day'))), - 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] - ); + if (strlen($moment) > 0 && $moment !== 'all') {$title = trans('firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]); $breadcrumbs->push($title, route('accounts.show', [$account->id, $moment])); } @@ -255,12 +251,12 @@ Breadcrumbs::register( // push when is all: if ($moment === 'all') { - $breadcrumbs->push(trans('firefly.all_journals_without_budget'), route('budgets.no-budget', ['all'])); + $breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget', ['all'])); } // when is specific period: if (strlen($moment) > 0 && $moment !== 'all') { - $title = trans('firefly.without_budget_between', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), - 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] + $title = trans('firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))] ); $breadcrumbs->push($title, route('budgets.no-budget', [$moment])); } @@ -282,11 +278,8 @@ Breadcrumbs::register( $breadcrumbs->push(e($budget->name), route('budgets.show', [$budget->id])); $title = trans( - 'firefly.budget_in_period_breadcrumb', [ - 'name' => $budget->name, - 'start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))), - 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))), - ] + 'firefly.between_dates_breadcrumb', ['start' => $budgetLimit->start_date->formatLocalized(strval(trans('config.month_and_day'))), + 'end' => $budgetLimit->end_date->formatLocalized(strval(trans('config.month_and_day'))),] ); $breadcrumbs->push( @@ -360,12 +353,13 @@ Breadcrumbs::register( // push when is all: if ($moment === 'all') { - $breadcrumbs->push(trans('firefly.all_journals_without_category'), route('categories.no-category', ['all'])); + $breadcrumbs->push(trans('firefly.everything'), 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')))] + $title = trans( + 'firefly.between_dates_breadcrumb', ['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])); } @@ -741,36 +735,27 @@ Breadcrumbs::register( * TRANSACTIONS */ Breadcrumbs::register( - 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what) { + 'transactions.index', function (BreadCrumbGenerator $breadcrumbs, string $what, string $moment = '', Carbon $start, Carbon $end) { + + $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); -} -); + if($moment === 'all') { + $breadcrumbs->push(trans('firefly.everything'), route('transactions.index', [$what, 'all'])); + } -Breadcrumbs::register( - 'transactions.index.all', function (BreadCrumbGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('transactions.index', $what); + // when is specific period: + if (strlen($moment) > 0 && $moment !== 'all') { + $title = trans('firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), 'end' => $end->formatLocalized(strval(trans('config.month_and_day')))]); + $breadcrumbs->push($title, route('transactions.index', [$what, $moment])); + } - $title = sprintf('%s (%s)', trans('breadcrumbs.' . $what . '_list'), strtolower(trans('firefly.everything'))); - - $breadcrumbs->push($title, route('transactions.index.all', [$what])); -} -); - -Breadcrumbs::register( - 'transactions.index.date', function (BreadCrumbGenerator $breadcrumbs, string $what, Carbon $date) { - $breadcrumbs->parent('transactions.index', $what); - - $range = Preferences::get('viewRange', '1M')->data; - $title = trans('breadcrumbs.' . $what . '_list') . ' (' . Navigation::periodShow($date, $range) . ')'; - - $breadcrumbs->push($title, route('transactions.index.date', [$what, $date->format('Y-m-d')])); } ); Breadcrumbs::register( 'transactions.create', function (BreadCrumbGenerator $breadcrumbs, string $what) { - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what,'', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); } ); @@ -792,7 +777,7 @@ Breadcrumbs::register( 'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { $what = strtolower($journal->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what,'', new Carbon, new Carbon); $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); } ); @@ -817,7 +802,7 @@ Breadcrumbs::register( if ($journals->count() > 0) { $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what,'', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); return; @@ -832,7 +817,7 @@ Breadcrumbs::register( $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()->transactionType->type); - $breadcrumbs->parent('transactions.index', $what); + $breadcrumbs->parent('transactions.index', $what,'', new Carbon, new Carbon); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds)); } ); diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index cbdeafbabc..8bd46c59fe 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -72,7 +72,7 @@ class Navigation * @return \Carbon\Carbon * @throws FireflyException */ - public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon + public function endOfPeriod(\Carbon\Carbon $end, string $repeatFreq): Carbon { $currentEnd = clone $end; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 972214df99..106c8a77d7 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -115,13 +115,22 @@ return [ 'multi_select_no_selection' => 'None selected', 'multi_select_all_selected' => 'All selected', 'multi_select_filter_placeholder' => 'Find..', + 'between_dates_breadcrumb' => 'Between :start and :end', '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_without_category' => 'All transactions without a category', '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', + 'all_withdrawal' => 'All expenses', + 'title_withdrawal_between' => 'All expenses between :start and :end', + 'all_deposit' => 'All revenue', + 'title_deposit_between' => 'All revenue between :start and :end', + 'all_transfers' => 'All transfers', + 'title_transfers_between' => 'All transfers between :start and :end', + 'all_transfer' => 'All transfers', + 'title_transfer_between' => 'All transfers between :start and :end', // repeat frequencies: diff --git a/resources/views/transactions/index-all.twig b/resources/views/transactions/index-all.twig deleted file mode 100644 index 794c255614..0000000000 --- a/resources/views/transactions/index-all.twig +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "./layout/default" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, what) }} -{% endblock %} - -{% block content %} - {% if journals.count == 0 %} - {% include 'partials.empty' with {what: what, type: 'transactions',route: route('transactions.create', [what])} %} - {% else %} -
- {{ 'no_transactions_in_period'|_ }} -
-- - {{ 'show_all_no_filter'|_ }} - + {{ 'show_all_no_filter'|_ }}
-{{ 'transactions'|_ }} | +{{ entry.count }} | +
{{ 'spent'|_ }} | +{{ entry.spent|formatAmount }} | +
{{ 'earned'|_ }} | +{{ entry.earned|formatAmount }} | +
{{ 'transferred'|_ }} | +{{ entry.transferred|formatAmountPlain }} | +