From 181c23b07c2a44c1896eae5d10f102e3e0b77f85 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 15 Jun 2018 22:06:33 +0200 Subject: [PATCH] Various updated code for recurring transactions. --- .deploy/docker/entrypoint.sh | 4 +- app/Http/Controllers/DebugController.php | 108 +++++++++++++++++- app/Http/Controllers/HomeController.php | 97 ---------------- .../Controllers/Recurring/IndexController.php | 53 ++++++++- .../Recurring/RecurringRepository.php | 2 +- .../RecurringRepositoryInterface.php | 2 +- app/Transformers/RecurrenceTransformer.php | 2 +- public/js/ff/recurring/create.js | 16 ++- resources/views/recurring/calendar.twig | 16 --- routes/web.php | 8 +- .../Controllers/DebugControllerTest.php | 58 ++++++++++ .../Controllers/HomeControllerTest.php | 54 --------- 12 files changed, 234 insertions(+), 186 deletions(-) delete mode 100644 resources/views/recurring/calendar.twig diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 17d62270c3..d95e3c4626 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -1,8 +1,8 @@ #!/bin/bash # make sure we own the volumes: -chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache -chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache +chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache +chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache # remove any lingering files that may break upgrades: rm -f $FIREFLY_PATH/storage/logs/laravel.log diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 3b0a444d23..a5e7b81036 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -23,13 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; +use Artisan; use Carbon\Carbon; use DB; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Middleware\IsDemoUser; use Illuminate\Http\Request; +use Illuminate\Routing\Route; use Log; use Monolog\Handler\RotatingFileHandler; +use Preferences; +use Route as RouteFacade; /** * Class DebugController @@ -45,6 +50,52 @@ class DebugController extends Controller $this->middleware(IsDemoUser::class); } + /** + * @throws FireflyException + */ + public function displayError() + { + Log::debug('This is a test message at the DEBUG level.'); + Log::info('This is a test message at the INFO level.'); + Log::notice('This is a test message at the NOTICE level.'); + Log::warning('This is a test message at the WARNING level.'); + Log::error('This is a test message at the ERROR level.'); + Log::critical('This is a test message at the CRITICAL level.'); + Log::alert('This is a test message at the ALERT level.'); + Log::emergency('This is a test message at the EMERGENCY level.'); + throw new FireflyException('A very simple test error.'); + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function flush(Request $request) + { + Preferences::mark(); + $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); + Log::debug('Call cache:clear...'); + Artisan::call('cache:clear'); + Log::debug('Call config:clear...'); + Artisan::call('config:clear'); + Log::debug('Call route:clear...'); + Artisan::call('route:clear'); + Log::debug('Call twig:clean...'); + try { + Artisan::call('twig:clean'); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + // don't care + Log::debug('Called twig:clean.'); + } + // @codeCoverageIgnoreEnd + Log::debug('Call view:clear...'); + Artisan::call('view:clear'); + Log::debug('Done! Redirecting...'); + + return redirect(route('index')); + } /** * @param Request $request @@ -120,6 +171,61 @@ class DebugController extends Controller ); } + /** + * @return string + */ + public function routes(): string + { + $set = RouteFacade::getRoutes(); + $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', + 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', + 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', + 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', + 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', + 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', + 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', + 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', + 'transactions.clone', 'two-factor.index', 'api.v1', 'installer.','attachments.view','import.create', + 'import.job.download','import.job.start','import.job.status.json','import.job.store','recurring.events', + 'recurring.suggest' + ]; + $return = ' '; + /** @var Route $route */ + foreach ($set as $route) { + $name = $route->getName(); + if (null !== $name && \strlen($name) > 0 && \in_array('GET', $route->methods(), true)) { + + $found = false; + foreach ($ignore as $string) { + if (!(false === stripos($name, $string))) { + $found = true; + break; + } + } + if ($found === false) { + $return .= 'touch ' . $route->getName() . '.md;'; + } + } + } + + return $return; + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function testFlash(Request $request) + { + $request->session()->flash('success', 'This is a success message.'); + $request->session()->flash('info', 'This is an info message.'); + $request->session()->flash('warning', 'This is a warning.'); + $request->session()->flash('error', 'This is an error!'); + + return redirect(route('home')); + } + /** * Some common combinations. * @@ -151,7 +257,7 @@ class DebugController extends Controller private function collectPackages(): array { $packages = []; - $file = realpath(__DIR__ . '/../../../vendor/composer/installed.json'); + $file = \dirname(__DIR__, 3) . '/vendor/composer/installed.json'; if (!($file === false) && file_exists($file)) { // file exists! $content = file_get_contents($file); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 993d3273e5..dcca1c2d3f 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -99,52 +99,7 @@ class HomeController extends Controller } - /** - * @throws FireflyException - */ - public function displayError() - { - Log::debug('This is a test message at the DEBUG level.'); - Log::info('This is a test message at the INFO level.'); - Log::notice('This is a test message at the NOTICE level.'); - Log::warning('This is a test message at the WARNING level.'); - Log::error('This is a test message at the ERROR level.'); - Log::critical('This is a test message at the CRITICAL level.'); - Log::alert('This is a test message at the ALERT level.'); - Log::emergency('This is a test message at the EMERGENCY level.'); - throw new FireflyException('A very simple test error.'); - } - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function flush(Request $request) - { - Preferences::mark(); - $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); - Log::debug('Call cache:clear...'); - Artisan::call('cache:clear'); - Log::debug('Call config:clear...'); - Artisan::call('config:clear'); - Log::debug('Call route:clear...'); - Artisan::call('route:clear'); - Log::debug('Call twig:clean...'); - try { - Artisan::call('twig:clean'); - // @codeCoverageIgnoreStart - } catch (Exception $e) { - // don't care - Log::debug('Called twig:clean.'); - } - // @codeCoverageIgnoreEnd - Log::debug('Call view:clear...'); - Artisan::call('view:clear'); - Log::debug('Done! Redirecting...'); - - return redirect(route('index')); - } /** * @param AccountRepositoryInterface $repository @@ -193,56 +148,4 @@ class HomeController extends Controller ); } - /** - * @return string - */ - public function routes() - { - $set = RouteFacade::getRoutes(); - $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', - 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', - 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', - 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', - 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', - 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', - 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', - 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', - 'transactions.clone', 'two-factor.index', - ]; - $return = ' '; - /** @var Route $route */ - foreach ($set as $route) { - $name = $route->getName(); - if (null !== $name && \in_array('GET', $route->methods()) && \strlen($name) > 0) { - - $found = false; - foreach ($ignore as $string) { - if (!(false === stripos($name, $string))) { - $found = true; - break; - } - } - if ($found === false) { - $return .= 'touch ' . $route->getName() . '.md;'; - } - } - } - - return $return; - } - - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function testFlash(Request $request) - { - $request->session()->flash('success', 'This is a success message.'); - $request->session()->flash('info', 'This is an info message.'); - $request->session()->flash('warning', 'This is a warning.'); - $request->session()->flash('error', 'This is an error!'); - - return redirect(route('home')); - } } diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index c11541832b..ed25962773 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -25,8 +25,10 @@ namespace FireflyIII\Http\Controllers\Recurring; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use FireflyIII\Transformers\RecurrenceTransformer; use Illuminate\Http\JsonResponse; @@ -66,13 +68,58 @@ class IndexController extends Controller /** * @param Request $request * - * @return string + * @throws FireflyException + * @return JsonResponse */ - public function calendar(Request $request) + function events(RecurringRepositoryInterface $repository, Request $request): JsonResponse { - return view('recurring.calendar'); + $return = []; + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $endsAt = (string)$request->get('ends'); + $repetitionType = explode(',', $request->get('type'))[0]; + $repetitionMoment = ''; + + switch ($repetitionType) { + default: + throw new FireflyException(sprintf('Cannot handle repetition type "%s"', $repetitionType)); + case 'daily': + break; + case 'weekly': + case 'monthly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1'; + break; + case 'ndom': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1,1'; + break; + case 'yearly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '2018-01-01'; + break; + } + + $repetition = new RecurrenceRepetition; + $repetition->repetition_type = $repetitionType; + $repetition->repetition_moment = $repetitionMoment; + $repetition->repetition_skip = (int)$request->get('skip'); + + var_dump($repository->getXOccurrences($repetition, $start, 5)); + exit; + + + // calculate events in range, depending on type: + switch ($endsAt) { + default: + throw new FireflyException(sprintf('Cannot generate events for "%s"', $endsAt)); + case 'forever': + break; + + } + + + return Response::json($return); } + /** * @param Request $request * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 79905c1019..2bf58c2066 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -82,7 +82,7 @@ class RecurringRepository implements RecurringRepositoryInterface * @return array * @throws FireflyException */ - public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array { $return = []; $mutator = clone $date; diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index d72738b785..f0e7646827 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -63,7 +63,7 @@ interface RecurringRepositoryInterface * * @return array */ - public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; /** * Parse the repetition in a string that is user readable. diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index db984a4c85..713928ac3b 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -141,7 +141,7 @@ class RecurrenceTransformer extends TransformerAbstract ]; // get the (future) occurrences for this specific type of repetition: - $occurrences = $this->repository->getOccurrences($repetition, $fromDate, 5); + $occurrences = $this->repository->getXOccurrences($repetition, $fromDate, 5); /** @var Carbon $carbon */ foreach ($occurrences as $carbon) { $repetitionArray['occurrences'][] = $carbon->format('Y-m-d'); diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index 5f2317fe68..083f42f9aa 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -44,6 +44,15 @@ $(document).ready(function () { * */ function showRepCalendar() { + + // pre-append URL with repetition info: + var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val(); + newEventsUri += '&skip=' + $('#ffInput_skip').val(); + newEventsUri += '&ends=' + $('#ffInput_repetition_end').val(); + newEventsUri += '&endDate=' + $('#ffInput_repeat_until').val(); + newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); + + $('#recurring_calendar').fullCalendar( { defaultDate: '2018-06-13', @@ -53,12 +62,7 @@ function showRepCalendar() { contentHeight: 300, aspectRatio: 1.25, eventLimit: true, // allow "more" link when too many events - events: [ - { - title: '', - start: '2018-06-14' - } - ] + events: newEventsUri }); $('#calendarModal').modal('show'); return false; diff --git a/resources/views/recurring/calendar.twig b/resources/views/recurring/calendar.twig deleted file mode 100644 index 2b3446b3fb..0000000000 --- a/resources/views/recurring/calendar.twig +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 176b037608..8493509643 100755 --- a/routes/web.php +++ b/routes/web.php @@ -65,10 +65,10 @@ Route::group( */ Route::group( ['middleware' => 'user-simple-auth', 'namespace' => 'FireflyIII\Http\Controllers'], function () { - Route::get('error', ['uses' => 'HomeController@displayError', 'as' => 'error']); + Route::get('error', ['uses' => 'DebugController@displayError', 'as' => 'error']); Route::any('logout', ['uses' => 'Auth\LoginController@logout', 'as' => 'logout']); - Route::get('flush', ['uses' => 'HomeController@flush', 'as' => 'flush']); - Route::get('routes', ['uses' => 'HomeController@routes', 'as' => 'routes']); + Route::get('flush', ['uses' => 'DebugController@flush', 'as' => 'flush']); + Route::get('routes', ['uses' => 'DebugController@routes', 'as' => 'routes']); Route::get('debug', 'DebugController@index')->name('debug'); } ); @@ -96,7 +96,7 @@ Route::group( Route::group( ['middleware' => ['user-full-auth'], 'namespace' => 'FireflyIII\Http\Controllers'], function () { Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']); - Route::get('/flash', ['uses' => 'HomeController@testFlash', 'as' => 'test-flash']); + Route::get('/flash', ['uses' => 'DebugController@testFlash', 'as' => 'test-flash']); Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']); Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']); } diff --git a/tests/Feature/Controllers/DebugControllerTest.php b/tests/Feature/Controllers/DebugControllerTest.php index 960b9dabed..e55c809226 100644 --- a/tests/Feature/Controllers/DebugControllerTest.php +++ b/tests/Feature/Controllers/DebugControllerTest.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace Tests\Feature\Controllers; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Log; use Tests\TestCase; @@ -43,6 +45,34 @@ class DebugControllerTest extends TestCase Log::debug(sprintf('Now in %s.', \get_class($this))); } + /** + * @covers \FireflyIII\Http\Controllers\DebugController::displayError + */ + public function testDisplayError(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('error')); + $response->assertStatus(500); + } + + /** + * @covers \FireflyIII\Http\Controllers\DebugController::flush + */ + public function testFlush(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('flush')); + $response->assertStatus(302); + } + /** * @covers \FireflyIII\Http\Controllers\DebugController::index * @covers \FireflyIII\Http\Controllers\DebugController::__construct @@ -56,4 +86,32 @@ class DebugControllerTest extends TestCase $response->assertStatus(200); } + /** + * @covers \FireflyIII\Http\Controllers\DebugController::routes() + */ + public function testRoutes(): void + { + $this->be($this->user()); + $response = $this->get(route('routes')); + $response->assertStatus(200); + } + + /** + * @covers \FireflyIII\Http\Controllers\DebugController::testFlash + */ + public function testTestFlash(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('test-flash')); + $response->assertStatus(302); + $response->assertSessionHas('success'); + $response->assertSessionHas('info'); + $response->assertSessionHas('warning'); + $response->assertSessionHas('error'); + } + } diff --git a/tests/Feature/Controllers/HomeControllerTest.php b/tests/Feature/Controllers/HomeControllerTest.php index 807c9d0dd2..a2b68c59b8 100644 --- a/tests/Feature/Controllers/HomeControllerTest.php +++ b/tests/Feature/Controllers/HomeControllerTest.php @@ -100,34 +100,6 @@ class HomeControllerTest extends TestCase $response->assertSessionHas('warning', '91 days of data may take a while to load.'); } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::displayError - */ - public function testDisplayError(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('error')); - $response->assertStatus(500); - } - - /** - * @covers \FireflyIII\Http\Controllers\HomeController::flush - */ - public function testFlush(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('flush')); - $response->assertStatus(302); - } - /** * @covers \FireflyIII\Http\Controllers\HomeController::index * @covers \FireflyIII\Http\Controllers\HomeController::__construct @@ -187,31 +159,5 @@ class HomeControllerTest extends TestCase $response->assertStatus(302); } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::routes() - */ - public function testRoutes(): void - { - $this->be($this->user()); - $response = $this->get(route('routes')); - $response->assertStatus(200); - } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::testFlash - */ - public function testTestFlash(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('test-flash')); - $response->assertStatus(302); - $response->assertSessionHas('success'); - $response->assertSessionHas('info'); - $response->assertSessionHas('warning'); - $response->assertSessionHas('error'); - } }