1 | <?php |
||||
2 | /** |
||||
3 | * CategoryController.php |
||||
4 | * Copyright (c) 2017 [email protected] |
||||
5 | * |
||||
6 | * This file is part of Firefly III. |
||||
7 | * |
||||
8 | * Firefly III is free software: you can redistribute it and/or modify |
||||
9 | * it under the terms of the GNU General Public License as published by |
||||
10 | * the Free Software Foundation, either version 3 of the License, or |
||||
11 | * (at your option) any later version. |
||||
12 | * |
||||
13 | * Firefly III is distributed in the hope that it will be useful, |
||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
16 | * GNU General Public License for more details. |
||||
17 | * |
||||
18 | * You should have received a copy of the GNU General Public License |
||||
19 | * along with Firefly III. If not, see <http://www.gnu.org/licenses/>. |
||||
20 | */ |
||||
21 | declare(strict_types=1); |
||||
22 | |||||
23 | namespace FireflyIII\Http\Controllers; |
||||
24 | |||||
25 | use Carbon\Carbon; |
||||
26 | use FireflyIII\Helpers\Collector\JournalCollectorInterface; |
||||
27 | use FireflyIII\Helpers\Filter\InternalTransferFilter; |
||||
28 | use FireflyIII\Http\Requests\CategoryFormRequest; |
||||
29 | use FireflyIII\Models\AccountType; |
||||
30 | use FireflyIII\Models\Category; |
||||
31 | use FireflyIII\Models\TransactionType; |
||||
32 | use FireflyIII\Repositories\Account\AccountRepositoryInterface; |
||||
33 | use FireflyIII\Repositories\Category\CategoryRepositoryInterface; |
||||
34 | use FireflyIII\Repositories\Journal\JournalRepositoryInterface; |
||||
35 | use FireflyIII\Support\CacheProperties; |
||||
36 | use Illuminate\Http\Request; |
||||
37 | use Illuminate\Pagination\LengthAwarePaginator; |
||||
38 | use Illuminate\Support\Collection; |
||||
39 | use Log; |
||||
40 | use Preferences; |
||||
41 | use Steam; |
||||
42 | use View; |
||||
43 | |||||
44 | /** |
||||
45 | * Class CategoryController. |
||||
46 | */ |
||||
47 | class CategoryController extends Controller |
||||
48 | { |
||||
49 | /** @var AccountRepositoryInterface */ |
||||
50 | private $accountRepos; |
||||
51 | /** @var JournalRepositoryInterface */ |
||||
52 | private $journalRepos; |
||||
53 | /** @var CategoryRepositoryInterface */ |
||||
54 | private $repository; |
||||
55 | |||||
56 | /** |
||||
57 | * |
||||
58 | */ |
||||
59 | public function __construct() |
||||
60 | { |
||||
61 | parent::__construct(); |
||||
62 | |||||
63 | $this->middleware( |
||||
64 | function ($request, $next) { |
||||
65 | app('view')->share('title', trans('firefly.categories')); |
||||
66 | app('view')->share('mainTitleIcon', 'fa-bar-chart'); |
||||
67 | $this->journalRepos = app(JournalRepositoryInterface::class); |
||||
68 | $this->repository = app(CategoryRepositoryInterface::class); |
||||
69 | $this->accountRepos = app(AccountRepositoryInterface::class); |
||||
70 | |||||
71 | return $next($request); |
||||
72 | } |
||||
73 | ); |
||||
74 | } |
||||
75 | |||||
76 | /** |
||||
77 | * @param Request $request |
||||
78 | * |
||||
79 | * @return View |
||||
80 | */ |
||||
81 | public function create(Request $request) |
||||
82 | { |
||||
83 | if (true !== session('categories.create.fromStore')) { |
||||
84 | $this->rememberPreviousUri('categories.create.uri'); |
||||
85 | } |
||||
86 | $request->session()->forget('categories.create.fromStore'); |
||||
87 | $subTitle = trans('firefly.create_new_category'); |
||||
88 | |||||
89 | return view('categories.create', compact('subTitle')); |
||||
90 | } |
||||
91 | |||||
92 | /** |
||||
93 | * @param Category $category |
||||
94 | * |
||||
95 | * @return View |
||||
96 | */ |
||||
97 | public function delete(Category $category) |
||||
98 | { |
||||
99 | $subTitle = trans('firefly.delete_category', ['name' => $category->name]); |
||||
100 | |||||
101 | // put previous url in session |
||||
102 | $this->rememberPreviousUri('categories.delete.uri'); |
||||
103 | |||||
104 | return view('categories.delete', compact('category', 'subTitle')); |
||||
105 | } |
||||
106 | |||||
107 | /** |
||||
108 | * @param Request $request |
||||
109 | * @param Category $category |
||||
110 | * |
||||
111 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector |
||||
112 | */ |
||||
113 | public function destroy(Request $request, Category $category) |
||||
114 | { |
||||
115 | $name = $category->name; |
||||
116 | $this->repository->destroy($category); |
||||
117 | |||||
118 | $request->session()->flash('success', (string)trans('firefly.deleted_category', ['name' => $name])); |
||||
119 | Preferences::mark(); |
||||
120 | |||||
121 | return redirect($this->getPreviousUri('categories.delete.uri')); |
||||
122 | } |
||||
123 | |||||
124 | /** |
||||
125 | * @param Request $request |
||||
126 | * @param Category $category |
||||
127 | * |
||||
128 | * @return View |
||||
129 | */ |
||||
130 | public function edit(Request $request, Category $category) |
||||
131 | { |
||||
132 | $subTitle = trans('firefly.edit_category', ['name' => $category->name]); |
||||
133 | |||||
134 | // put previous url in session if not redirect from store (not "return_to_edit"). |
||||
135 | if (true !== session('categories.edit.fromUpdate')) { |
||||
136 | $this->rememberPreviousUri('categories.edit.uri'); |
||||
137 | } |
||||
138 | $request->session()->forget('categories.edit.fromUpdate'); |
||||
139 | |||||
140 | return view('categories.edit', compact('category', 'subTitle')); |
||||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * @param Request $request |
||||
145 | * |
||||
146 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View |
||||
147 | */ |
||||
148 | public function index(Request $request) |
||||
149 | { |
||||
150 | $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); |
||||
151 | $pageSize = (int)Preferences::get('listPageSize', 50)->data; |
||||
152 | $collection = $this->repository->getCategories(); |
||||
153 | $total = $collection->count(); |
||||
154 | $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); |
||||
155 | |||||
156 | $collection->each( |
||||
157 | function (Category $category) { |
||||
158 | $category->lastActivity = $this->repository->lastUseDate($category, new Collection); |
||||
159 | } |
||||
160 | ); |
||||
161 | |||||
162 | // paginate categories |
||||
163 | $categories = new LengthAwarePaginator($collection, $total, $pageSize, $page); |
||||
164 | $categories->setPath(route('categories.index')); |
||||
165 | |||||
166 | return view('categories.index', compact('categories')); |
||||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * @param Request $request |
||||
171 | * @param string $moment |
||||
172 | * |
||||
173 | * @return View |
||||
174 | */ |
||||
175 | public function noCategory(Request $request, string $moment = '') |
||||
176 | { |
||||
177 | // default values: |
||||
178 | $range = Preferences::get('viewRange', '1M')->data; |
||||
179 | $start = null; |
||||
180 | $end = null; |
||||
181 | $periods = new Collection; |
||||
182 | $page = (int)$request->get('page'); |
||||
183 | $pageSize = (int)Preferences::get('listPageSize', 50)->data; |
||||
184 | |||||
185 | // prep for "all" view. |
||||
186 | if ('all' === $moment) { |
||||
187 | $subTitle = trans('firefly.all_journals_without_category'); |
||||
188 | $first = $this->journalRepos->first(); |
||||
0 ignored issues
–
show
Deprecated Code
introduced
by
Loading history...
|
|||||
189 | $start = $first->date ?? new Carbon; |
||||
190 | $end = new Carbon; |
||||
191 | } |
||||
192 | |||||
193 | // prep for "specific date" view. |
||||
194 | if (strlen($moment) > 0 && 'all' !== $moment) { |
||||
195 | $start = app('navigation')->startOfPeriod(new Carbon($moment), $range); |
||||
196 | $end = app('navigation')->endOfPeriod($start, $range); |
||||
197 | $subTitle = trans( |
||||
198 | 'firefly.without_category_between', |
||||
199 | ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] |
||||
200 | ); |
||||
201 | $periods = $this->getNoCategoryPeriodOverview($start); |
||||
202 | } |
||||
203 | |||||
204 | // prep for current period |
||||
205 | if (0 === strlen($moment)) { |
||||
206 | $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); |
||||
207 | $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); |
||||
208 | $periods = $this->getNoCategoryPeriodOverview($start); |
||||
209 | $subTitle = trans( |
||||
210 | 'firefly.without_category_between', |
||||
211 | ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] |
||||
212 | ); |
||||
213 | } |
||||
214 | |||||
215 | /** @var JournalCollectorInterface $collector */ |
||||
216 | $collector = app(JournalCollectorInterface::class); |
||||
217 | $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() |
||||
218 | ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); |
||||
219 | $collector->removeFilter(InternalTransferFilter::class); |
||||
220 | $transactions = $collector->getPaginatedJournals(); |
||||
221 | $transactions->setPath(route('categories.no-category')); |
||||
222 | |||||
223 | return view('categories.no-category', compact('transactions', 'subTitle', 'moment', 'periods', 'start', 'end')); |
||||
224 | } |
||||
225 | |||||
226 | /** |
||||
227 | * @param Request $request |
||||
228 | * @param CategoryRepositoryInterface $repository |
||||
229 | * @param Category $category |
||||
230 | * @param string $moment |
||||
231 | * |
||||
232 | * @return View |
||||
233 | */ |
||||
234 | public function show(Request $request, CategoryRepositoryInterface $repository, Category $category, string $moment = '') |
||||
235 | { |
||||
236 | // default values: |
||||
237 | $subTitle = $category->name; |
||||
238 | $subTitleIcon = 'fa-bar-chart'; |
||||
239 | $page = (int)$request->get('page'); |
||||
240 | $pageSize = (int)Preferences::get('listPageSize', 50)->data; |
||||
241 | $range = Preferences::get('viewRange', '1M')->data; |
||||
242 | $start = null; |
||||
243 | $end = null; |
||||
244 | $periods = new Collection; |
||||
245 | $path = route('categories.show', [$category->id]); |
||||
246 | |||||
247 | // prep for "all" view. |
||||
248 | if ('all' === $moment) { |
||||
249 | $subTitle = trans('firefly.all_journals_for_category', ['name' => $category->name]); |
||||
250 | $first = $repository->firstUseDate($category); |
||||
251 | /** @var Carbon $start */ |
||||
252 | $start = $first ?? new Carbon; |
||||
253 | $end = new Carbon; |
||||
254 | $path = route('categories.show', [$category->id, 'all']); |
||||
255 | } |
||||
256 | |||||
257 | // prep for "specific date" view. |
||||
258 | if (strlen($moment) > 0 && 'all' !== $moment) { |
||||
259 | $start = app('navigation')->startOfPeriod(new Carbon($moment), $range); |
||||
260 | $end = app('navigation')->endOfPeriod($start, $range); |
||||
261 | $subTitle = trans( |
||||
262 | 'firefly.journals_in_period_for_category', |
||||
263 | ['name' => $category->name, |
||||
264 | 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),] |
||||
265 | ); |
||||
266 | $periods = $this->getPeriodOverview($category, $start); |
||||
267 | $path = route('categories.show', [$category->id, $moment]); |
||||
268 | } |
||||
269 | |||||
270 | // prep for current period |
||||
271 | if (0 === strlen($moment)) { |
||||
272 | /** @var Carbon $start */ |
||||
273 | $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); |
||||
274 | /** @var Carbon $end */ |
||||
275 | $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); |
||||
276 | $periods = $this->getPeriodOverview($category, $start); |
||||
277 | $subTitle = trans( |
||||
278 | 'firefly.journals_in_period_for_category', |
||||
279 | ['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), |
||||
280 | 'end' => $end->formatLocalized($this->monthAndDayFormat),] |
||||
281 | ); |
||||
282 | } |
||||
283 | |||||
284 | /** @var JournalCollectorInterface $collector */ |
||||
285 | $collector = app(JournalCollectorInterface::class); |
||||
286 | $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() |
||||
287 | ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); |
||||
288 | $collector->removeFilter(InternalTransferFilter::class); |
||||
289 | $transactions = $collector->getPaginatedJournals(); |
||||
290 | $transactions->setPath($path); |
||||
291 | |||||
292 | return view('categories.show', compact('category', 'moment', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); |
||||
293 | } |
||||
294 | |||||
295 | /** |
||||
296 | * @param CategoryFormRequest $request |
||||
297 | * @param CategoryRepositoryInterface $repository |
||||
298 | * |
||||
299 | * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector |
||||
300 | */ |
||||
301 | public function store(CategoryFormRequest $request, CategoryRepositoryInterface $repository) |
||||
302 | { |
||||
303 | $data = $request->getCategoryData(); |
||||
304 | $category = $repository->store($data); |
||||
305 | |||||
306 | $request->session()->flash('success', (string)trans('firefly.stored_category', ['name' => $category->name])); |
||||
307 | Preferences::mark(); |
||||
308 | |||||
309 | if (1 === (int)$request->get('create_another')) { |
||||
310 | // @codeCoverageIgnoreStart |
||||
311 | $request->session()->put('categories.create.fromStore', true); |
||||
312 | |||||
313 | return redirect(route('categories.create'))->withInput(); |
||||
314 | // @codeCoverageIgnoreEnd |
||||
315 | } |
||||
316 | |||||
317 | return redirect(route('categories.index')); |
||||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * @param CategoryFormRequest $request |
||||
322 | * @param CategoryRepositoryInterface $repository |
||||
323 | * @param Category $category |
||||
324 | * |
||||
325 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector |
||||
326 | */ |
||||
327 | public function update(CategoryFormRequest $request, CategoryRepositoryInterface $repository, Category $category) |
||||
328 | { |
||||
329 | $data = $request->getCategoryData(); |
||||
330 | $repository->update($category, $data); |
||||
331 | |||||
332 | $request->session()->flash('success', (string)trans('firefly.updated_category', ['name' => $category->name])); |
||||
333 | Preferences::mark(); |
||||
334 | |||||
335 | if (1 === (int)$request->get('return_to_edit')) { |
||||
336 | // @codeCoverageIgnoreStart |
||||
337 | $request->session()->put('categories.edit.fromUpdate', true); |
||||
338 | |||||
339 | return redirect(route('categories.edit', [$category->id])); |
||||
340 | // @codeCoverageIgnoreEnd |
||||
341 | } |
||||
342 | |||||
343 | return redirect($this->getPreviousUri('categories.edit.uri')); |
||||
344 | } |
||||
345 | |||||
346 | /** |
||||
347 | * @param Carbon $theDate |
||||
348 | * |
||||
349 | * @return Collection |
||||
350 | */ |
||||
351 | private function getNoCategoryPeriodOverview(Carbon $theDate): Collection |
||||
352 | { |
||||
353 | $range = Preferences::get('viewRange', '1M')->data; |
||||
354 | $first = $this->journalRepos->first(); |
||||
0 ignored issues
–
show
The function
FireflyIII\Repositories\...itoryInterface::first() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
355 | $start = $first->date ?? new Carbon; |
||||
356 | $end = $theDate ?? new Carbon; |
||||
357 | |||||
358 | // properties for cache |
||||
359 | $cache = new CacheProperties; |
||||
360 | $cache->addProperty($start); |
||||
361 | $cache->addProperty($end); |
||||
362 | $cache->addProperty('no-category-period-entries'); |
||||
363 | |||||
364 | if ($cache->has()) { |
||||
365 | return $cache->get(); // @codeCoverageIgnore |
||||
366 | } |
||||
367 | |||||
368 | $dates = app('navigation')->blockPeriods($start, $end, $range); |
||||
369 | $entries = new Collection; |
||||
370 | |||||
371 | foreach ($dates as $date) { |
||||
372 | |||||
373 | // count journals without category in this period: |
||||
374 | /** @var JournalCollectorInterface $collector */ |
||||
375 | $collector = app(JournalCollectorInterface::class); |
||||
376 | $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() |
||||
377 | ->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); |
||||
378 | $collector->removeFilter(InternalTransferFilter::class); |
||||
379 | $count = $collector->getJournals()->count(); |
||||
380 | |||||
381 | // amount transferred |
||||
382 | /** @var JournalCollectorInterface $collector */ |
||||
383 | $collector = app(JournalCollectorInterface::class); |
||||
384 | $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() |
||||
385 | ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); |
||||
386 | $collector->removeFilter(InternalTransferFilter::class); |
||||
387 | $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); |
||||
388 | |||||
389 | // amount spent |
||||
390 | /** @var JournalCollectorInterface $collector */ |
||||
391 | $collector = app(JournalCollectorInterface::class); |
||||
392 | $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( |
||||
393 | [TransactionType::WITHDRAWAL] |
||||
394 | ); |
||||
395 | $spent = $collector->getJournals()->sum('transaction_amount'); |
||||
396 | |||||
397 | // amount earned |
||||
398 | /** @var JournalCollectorInterface $collector */ |
||||
399 | $collector = app(JournalCollectorInterface::class); |
||||
400 | $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( |
||||
401 | [TransactionType::DEPOSIT] |
||||
402 | ); |
||||
403 | $earned = $collector->getJournals()->sum('transaction_amount'); |
||||
404 | $dateStr = $date['end']->format('Y-m-d'); |
||||
405 | $dateName = app('navigation')->periodShow($date['end'], $date['period']); |
||||
406 | $entries->push( |
||||
407 | [ |
||||
408 | 'string' => $dateStr, |
||||
409 | 'name' => $dateName, |
||||
410 | 'count' => $count, |
||||
411 | 'spent' => $spent, |
||||
412 | 'earned' => $earned, |
||||
413 | 'transferred' => $transferred, |
||||
414 | 'date' => clone $date['end'], |
||||
415 | ] |
||||
416 | ); |
||||
417 | } |
||||
418 | Log::debug('End of loops'); |
||||
419 | $cache->store($entries); |
||||
420 | |||||
421 | return $entries; |
||||
422 | } |
||||
423 | |||||
424 | /** |
||||
425 | * @param Category $category |
||||
426 | * |
||||
427 | * @param Carbon $date |
||||
428 | * |
||||
429 | * @return Collection |
||||
430 | */ |
||||
431 | private function getPeriodOverview(Category $category, Carbon $date): Collection |
||||
432 | { |
||||
433 | $range = Preferences::get('viewRange', '1M')->data; |
||||
434 | $first = $this->journalRepos->first(); |
||||
0 ignored issues
–
show
The function
FireflyIII\Repositories\...itoryInterface::first() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
435 | $start = $first->date ?? new Carbon; |
||||
436 | $end = $date ?? new Carbon; |
||||
437 | $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); |
||||
438 | |||||
439 | // properties for entries with their amounts. |
||||
440 | $cache = new CacheProperties(); |
||||
441 | $cache->addProperty($start); |
||||
442 | $cache->addProperty($end); |
||||
443 | $cache->addProperty($range); |
||||
444 | $cache->addProperty('categories.entries'); |
||||
445 | $cache->addProperty($category->id); |
||||
446 | |||||
447 | if ($cache->has()) { |
||||
448 | return $cache->get(); // @codeCoverageIgnore |
||||
449 | } |
||||
450 | /** @var array $dates */ |
||||
451 | $dates = app('navigation')->blockPeriods($start, $end, $range); |
||||
452 | $entries = new Collection; |
||||
453 | |||||
454 | foreach ($dates as $currentDate) { |
||||
455 | $spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); |
||||
456 | $earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']); |
||||
457 | $dateStr = $currentDate['end']->format('Y-m-d'); |
||||
458 | $dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); |
||||
459 | |||||
460 | // amount transferred |
||||
461 | /** @var JournalCollectorInterface $collector */ |
||||
462 | $collector = app(JournalCollectorInterface::class); |
||||
463 | $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category) |
||||
464 | ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); |
||||
465 | $collector->removeFilter(InternalTransferFilter::class); |
||||
466 | $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); |
||||
467 | |||||
468 | $entries->push( |
||||
469 | [ |
||||
470 | 'string' => $dateStr, |
||||
471 | 'name' => $dateName, |
||||
472 | 'spent' => $spent, |
||||
473 | 'earned' => $earned, |
||||
474 | 'sum' => bcadd($earned, $spent), |
||||
475 | 'transferred' => $transferred, |
||||
476 | 'date' => clone $currentDate['end'], |
||||
477 | ] |
||||
478 | ); |
||||
479 | } |
||||
480 | $cache->store($entries); |
||||
481 | |||||
482 | return $entries; |
||||
483 | } |
||||
484 | } |
||||
485 |