Various updated code for recurring transactions.

This commit is contained in:
James Cole 2018-06-15 22:06:33 +02:00
parent 955cde3ed9
commit 181c23b07c
12 changed files with 234 additions and 186 deletions

View File

@ -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

View File

@ -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);

View File

@ -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'));
}
}

View File

@ -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
*

View File

@ -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;

View File

@ -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.

View File

@ -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');

View File

@ -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;

View File

@ -1,16 +0,0 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Calendar view yay</h4>
</div>
<div class="modal-body">
<div id="recurring_calendar">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>

View File

@ -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']);
}

View File

@ -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');
}
}

View File

@ -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');
}
}