diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 506528d033..169693f5e2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -11,4 +11,5 @@ // The available directives right now are require, require_directory, and require_tree // //= require jquery -//= require bootstrap/bootstrap.min \ No newline at end of file +//= require bootstrap/bootstrap.min +//= require firefly/reminders \ No newline at end of file diff --git a/app/assets/javascripts/firefly/reminders.js b/app/assets/javascripts/firefly/reminders.js new file mode 100644 index 0000000000..42ea5efb3d --- /dev/null +++ b/app/assets/javascripts/firefly/reminders.js @@ -0,0 +1,69 @@ +$(function () { + + $('#reminderModal').on('loaded.bs.modal', function () { + + // trigger the 24 hour delay, + $('.dismiss-24').on('click', function (ev) { + var target = $(ev.target); + var reminderId = target.data('id'); + + // post dismissal for 24 hours. + $.post('reminders/postpone/' + target.data('id')).success(function (data) { + $('.reminder-row-' + data).hide(200); + }).fail(function () { + alert('Could not postpone, please try later.'); + }); + }); + + // trigger the 'forever' delay + $('.dismiss-forever').on('click', function (ev) { + var target = $(ev.target); + var reminderId = target.data('id'); + + $.post('reminders/dismiss/' + target.data('id')).success(function (data) { + $('.reminder-row-' + data).hide(200); + }).fail(function () { + alert('Could not dismiss, please try later.'); + }); + }); + + // trigger the 'do it' command. + $('.do-it').on('click', function (ev) { + var target = $(ev.target); + var reminderId = target.data('id'); + window.location = 'reminders/redirect/' + reminderId; + }); + + }); + + + $('#reminderModalTrigger').on('click', function () { + + + $('#reminderModal').modal( + { + remote: 'reminders/dialog' + } + ); + + // trigger on the buttons in the popup + + + // get some data from somewhere: +// $.getJSON('json/reminders').success(function (data) { +// +// var html = ''; +// $.each(data,function(i,v) { +// html += v.txt; +// }); +// +// $('#reminderModal .modal-body').html(html); +// $('#reminderModal').modal(); +// }).fail(function () { +// alert('Could not load reminders.'); +// }); +// + + }); + +}); \ No newline at end of file diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php index 4a96f2a404..3763a1a268 100644 --- a/app/controllers/HomeController.php +++ b/app/controllers/HomeController.php @@ -1,4 +1,5 @@ _accounts->count(); $start = Session::get('start'); diff --git a/app/controllers/JsonController.php b/app/controllers/JsonController.php index 30ae6608c1..db30268efa 100644 --- a/app/controllers/JsonController.php +++ b/app/controllers/JsonController.php @@ -1,5 +1,6 @@ with('recurring',$recurringTransaction); + return View::make('recurring.show')->with('recurring', $recurringTransaction); } @@ -117,12 +117,18 @@ class RecurringController extends BaseController { /** @var \RecurringTransaction $recurringTransaction */ $recurringTransaction = $this->_repository->update($recurringTransaction, Input::all()); - if($recurringTransaction->errors()->count() == 0) { + if ($recurringTransaction->errors()->count() == 0) { Session::flash('success', 'The recurring transaction has been updated.'); + return Redirect::route('recurring.index'); } else { - Session::flash('error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first()); - return Redirect::route('recurring.edit',$recurringTransaction->id)->withInput()->withErrors($recurringTransaction->errors()); + Session::flash( + 'error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first() + ); + + return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput()->withErrors( + $recurringTransaction->errors() + ); } } } \ No newline at end of file diff --git a/app/controllers/ReminderController.php b/app/controllers/ReminderController.php new file mode 100644 index 0000000000..ea3f60489b --- /dev/null +++ b/app/controllers/ReminderController.php @@ -0,0 +1,89 @@ +_repository = $repository; + } + + /** + * @param Reminder $reminder + * + * @return \Illuminate\Http\JsonResponse + */ + public function dismiss(\Reminder $reminder) + { + $reminder = $this->_repository->deactivate($reminder); + + return Response::json($reminder->id); + } + + /** + * Returns the reminders currently active for the modal dialog. + */ + public function modalDialog() + { + $today = new Carbon; + $reminders = $this->_repository->get(); + + /** @var \Reminder $reminder */ + foreach ($reminders as $index => $reminder) { + if (\Session::has('dismissal-' . $reminder->id)) { + $time = \Session::get('dismissal-' . $reminder->id); + if ($time >= $today) { + unset($reminders[$index]); + } + + } + } + + return View::make('reminders.popup')->with('reminders', $reminders); + } + + /** + * @param Reminder $reminder + * + * @return \Illuminate\Http\JsonResponse + */ + public function postpone(\Reminder $reminder) + { + $now = new Carbon; + $now->addDay(); + Session::put('dismissal-' . $reminder->id, $now); + + return Response::json($reminder->id); + } + + /** + * @param Reminder $reminder + */ + public function redirect(\Reminder $reminder) + { + if ($reminder instanceof PiggybankReminder) { + // fields to prefill: + $parameters = [ + 'account_to_id' => $reminder->piggybank->account->id, + 'amount' => round($reminder->amountToSave(), 2), + 'description' => 'Money for ' . $reminder->piggybank->name, + 'piggybank_id' => $reminder->piggybank->id, + 'reminder_id' => $reminder->id + ]; + + return Redirect::to( + route('transactions.create', ['what' => 'transfer']) . '?' . http_build_query($parameters) + ); + } + + } + +} \ No newline at end of file diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index d5e1d08125..684b9667fd 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -43,7 +43,6 @@ class TransactionController extends BaseController $piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface'); $piggies = $piggyRepository->get(); - return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with( 'what', $what )->with('piggies', $piggies); @@ -188,6 +187,16 @@ class TransactionController extends BaseController $journal = $this->_repository->store($what, Input::all()); if ($journal->validate()) { Session::flash('success', 'Transaction "' . $journal->description . '" saved!'); + + // if reminder present, deactivate it: + if(Input::get('reminder')) { + /** @var \Firefly\Storage\Reminder\ReminderRepositoryInterface $reminders */ + $reminders = App::make('Firefly\Storage\Reminder\ReminderRepositoryInterface'); + $reminder = $reminders->find(Input::get('reminder')); + $reminders->deactivate($reminder); + + } + if (Input::get('create') == '1') { return Redirect::route('transactions.create', [$what])->withInput(); } else { diff --git a/app/database/migrations/2014_08_23_113221_create_reminders_table.php b/app/database/migrations/2014_08_23_113221_create_reminders_table.php new file mode 100644 index 0000000000..808b11b857 --- /dev/null +++ b/app/database/migrations/2014_08_23_113221_create_reminders_table.php @@ -0,0 +1,50 @@ +increments('id'); + $table->timestamps(); + $table->string('class', 30); + $table->integer('piggybank_id')->unsigned()->nullable(); + $table->integer('user_id')->unsigned(); + $table->date('startdate'); + $table->date('enddate'); + $table->boolean('active'); + + + // connect reminders to piggy banks. + $table->foreign('piggybank_id') + ->references('id')->on('piggybanks') + ->onDelete('set null'); + + // connect reminders to users + $table->foreign('user_id') + ->references('id')->on('users') + ->onDelete('cascade'); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('reminders'); + } + +} diff --git a/app/filters.php b/app/filters.php index ed277adc87..45cb747772 100644 --- a/app/filters.php +++ b/app/filters.php @@ -7,7 +7,9 @@ App::before( if (Auth::check()) { $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); - return $toolkit->getDateRange($request); + $toolkit->getDateRange($request); + $toolkit->getReminders(); + } } diff --git a/app/lib/Firefly/Helper/Toolkit/Toolkit.php b/app/lib/Firefly/Helper/Toolkit/Toolkit.php index 0daace8e43..64cc7f0f1f 100644 --- a/app/lib/Firefly/Helper/Toolkit/Toolkit.php +++ b/app/lib/Firefly/Helper/Toolkit/Toolkit.php @@ -302,4 +302,26 @@ class Toolkit implements ToolkitInterface return $end; } + /** + * @return mixed + */ + public function getReminders() { + // get reminders, for menu, mumble mumble: + $today = new Carbon; + $reminders = \Auth::user()->reminders()->validOn($today)->get(); + + /** @var \Reminder $reminder */ + foreach($reminders as $index => $reminder) { + if(\Session::has('dismissal-' . $reminder->id)) { + $time = \Session::get('dismissal-' . $reminder->id); + if($time >= $today) { + unset($reminders[$index]); + } + + } + } + \Session::put('reminderCount',count($reminders)); + + } + } \ No newline at end of file diff --git a/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php b/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php index 1847dea771..2ecbd8947b 100644 --- a/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php +++ b/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php @@ -23,4 +23,9 @@ interface ToolkitInterface */ public function getDateRangeDates(); + /** + * @return mixed + */ + public function getReminders(); + } \ No newline at end of file diff --git a/app/lib/Firefly/Storage/Piggybank/EloquentPiggybankRepository.php b/app/lib/Firefly/Storage/Piggybank/EloquentPiggybankRepository.php index 7adc705f7a..ffb0f6bd59 100644 --- a/app/lib/Firefly/Storage/Piggybank/EloquentPiggybankRepository.php +++ b/app/lib/Firefly/Storage/Piggybank/EloquentPiggybankRepository.php @@ -136,7 +136,18 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface $account = isset($data['account_id']) ? $accounts->find($data['account_id']) : null; + + $piggyBank = new \Piggybank($data); + + if(!is_null($piggyBank->reminder) && is_null($piggyBank->startdate) && is_null($piggyBank->targetdate)) { + + $piggyBank->errors()->add('reminder','Cannot create reminders without start ~ AND target date.'); + return $piggyBank; + + } + + if($piggyBank->repeats && !isset($data['targetdate'])) { $piggyBank->errors()->add('targetdate','Target date is mandatory!'); return $piggyBank; diff --git a/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php b/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php new file mode 100644 index 0000000000..2edd255c8e --- /dev/null +++ b/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php @@ -0,0 +1,48 @@ +active = 0; + $reminder->save(); + + return $reminder; + } + + /** + * @param $id + * + * @return mixed|void + */ + public function find($id) + { + return \Reminder::find($id); + } + + /** + * @return mixed + */ + public function get() + { + $today = new Carbon; + + return \Auth::user()->reminders()->validOn($today)->get(); + } + +} \ No newline at end of file diff --git a/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php b/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php new file mode 100644 index 0000000000..36b0ee3048 --- /dev/null +++ b/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php @@ -0,0 +1,38 @@ +app->bind( + 'Firefly\Storage\Reminder\ReminderRepositoryInterface', + 'Firefly\Storage\Reminder\EloquentReminderRepository' + ); $this->app->bind( 'Firefly\Storage\Account\AccountRepositoryInterface', diff --git a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php index 18244987a3..bd41a6e8bc 100644 --- a/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php +++ b/app/lib/Firefly/Trigger/Limits/EloquentLimitTrigger.php @@ -85,7 +85,7 @@ class EloquentLimitTrigger */ public function madeRepetition(\LimitRepetition $repetition) { - \Log::info('TRIGGER: Created a limit repetition (#' . $repetition->id . ')'); + \Log::info('TRIGGER: Created a limit repetition (#' . $repetition->id . ')'); } /** @@ -109,8 +109,8 @@ class EloquentLimitTrigger //$events->listen('budgets.change', 'Firefly\Trigger\Limits\EloquentLimitTrigger@updateLimitRepetitions'); $events->listen('limits.destroy', 'Firefly\Trigger\Limits\EloquentLimitTrigger@destroy'); $events->listen('limits.store', 'Firefly\Trigger\Limits\EloquentLimitTrigger@store'); - $events->listen('limits.update', 'Firefly\Trigger\Limits\EloquentLimitTrigger@update'); - $events->listen('limits.check', 'Firefly\Trigger\Limits\EloquentLimitTrigger@checkRepeatingLimits'); + $events->listen('limits.update', 'Firefly\Trigger\Limits\EloquentLimitTrigger@update'); + $events->listen('limits.check', 'Firefly\Trigger\Limits\EloquentLimitTrigger@checkRepeatingLimits'); $events->listen('limits.repetition', 'Firefly\Trigger\Limits\EloquentLimitTrigger@madeRepetition'); //\Event::fire('limits.repetition', [$repetition]); diff --git a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php index f9a28e726a..ae6f1910c9 100644 --- a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php +++ b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php @@ -3,6 +3,7 @@ namespace Firefly\Trigger\Piggybanks; use Carbon\Carbon; +use Illuminate\Database\QueryException; use Illuminate\Events\Dispatcher; /** @@ -116,8 +117,64 @@ class EloquentPiggybankTrigger */ public function madeRep(\PiggybankRepetition $rep) { - // do something. - \Log::info('TRIGGER: Created a piggybank repetition (#' . $rep->id . ')'); + // do something with reminders? + $piggyBank = $rep->piggybank; + if (is_null(($piggyBank->reminder))) { + return null; + } + + $current = clone $rep->startdate; + $today = new Carbon; + while ($current <= $rep->targetdate) { + + // when do we start reminding? + // X days before $current: + $reminderStart = clone $current; + switch ($piggyBank->reminder) { + case 'day': + $reminderStart->subDay(); + break; + case 'week': + $reminderStart->subDays(4); + break; + case 'month': + $reminderStart->subDays(21); + break; + case 'year': + $reminderStart->subMonths(9); + break; + } + + if ($current >= $today) { + $reminder = new \PiggybankReminder; + $reminder->piggybank()->associate($piggyBank); + $reminder->user()->associate(\Auth::user()); + $reminder->startdate = $reminderStart; + $reminder->enddate = $current; + try { + $reminder->save(); + + } catch (QueryException $e) { + } + } + + + switch ($piggyBank->reminder) { + case 'day': + $current->addDays($piggyBank->reminder_skip); + break; + case 'week': + $current->addWeeks($piggyBank->reminder_skip); + break; + case 'month': + $current->addMonths($piggyBank->reminder_skip); + break; + case 'year': + $current->addYears($piggyBank->reminder_skip); + break; + } + } + } /** @@ -166,14 +223,6 @@ class EloquentPiggybankTrigger $piggyBank->createRepetition($piggyBank->startdate, $piggyBank->targetdate); return true; - $rep = new \PiggybankRepetition; - $rep->piggybank()->associate($piggyBank); - $rep->targetdate = $piggyBank->targetdate; - $rep->startdate = $piggyBank->startdate; - $rep->currentamount = 0; - $rep->save(); - - return true; } diff --git a/app/models/Component.php b/app/models/Component.php index 6e33f9c616..b034bf41a5 100644 --- a/app/models/Component.php +++ b/app/models/Component.php @@ -1,4 +1,5 @@ piggybank; + /** @var \PiggybankRepetition $repetition */ + $repetition = $piggyBank->currentRelevantRep(); + + $today = new Carbon; + $diff = $today->diff($repetition->targetdate); + $left = $piggyBank->targetamount - $repetition->currentamount; + // to prevent devide by zero: + $piggyBank->reminder_skip = $piggyBank->reminder_skip < 1 ? 1 : $piggyBank->reminder_skip; + $toSave = 0; + switch ($piggyBank->reminder) { + case 'day': + throw new \Firefly\Exception\FireflyException('No impl day reminder/ PiggyBankReminder Render'); + break; + case 'week': + throw new \Firefly\Exception\FireflyException('No impl week reminder/ PiggyBankReminder Render'); + break; + case 'month': + $toSave = $left / ($diff->m / $piggyBank->reminder_skip); + break; + case 'year': + throw new \Firefly\Exception\FireflyException('No impl year reminder/ PiggyBankReminder Render'); + break; + } + return floatval($toSave); + } + + /** + * @return string + * @throws Firefly\Exception\FireflyException + */ + public function render() + { + /** @var \Piggybank $piggyBank */ + $piggyBank = $this->piggybank; + + + $fullText + = 'In order to save enough money for "' . e( + $piggyBank->name + ) . '" you'; + + $fullText .= ' should save at least ' . mf($this->amountToSave(), false) . ' this ' . $piggyBank->reminder + . ', before ' . $this->enddate->format('M jS, Y'); + + return $fullText; + } + +} \ No newline at end of file diff --git a/app/models/Reminder.php b/app/models/Reminder.php new file mode 100644 index 0000000000..01e8b2584c --- /dev/null +++ b/app/models/Reminder.php @@ -0,0 +1,56 @@ +belongsTo('Piggybank'); + } + + public function render() { + return ''; + + + + } + + public function scopeValidOn($query, Carbon $date) + { + return $query->where('startdate', '<=', $date->format('Y-m-d'))->where('enddate', '>=', $date->format('Y-m-d')) + ->where('active', 1); + } + + /** + * User + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo('User'); + } +} \ No newline at end of file diff --git a/app/models/User.php b/app/models/User.php index 25fb4ad281..92b5b87962 100644 --- a/app/models/User.php +++ b/app/models/User.php @@ -76,6 +76,22 @@ class User extends Ardent implements UserInterface, RemindableInterface return $this->hasMany('Budget'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function reminders() + { + return $this->hasMany('Reminder'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function piggybankreminders() + { + return $this->hasMany('PiggybankReminder'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/routes.php b/app/routes.php index 59ec4b70a7..9f6273479a 100644 --- a/app/routes.php +++ b/app/routes.php @@ -43,6 +43,16 @@ Route::bind('budget', function($value, $route) return null; }); +Route::bind('reminder', function($value, $route) + { + if(Auth::check()) { + return Reminder:: + where('id', $value)-> + where('user_id',Auth::user()->id)->first(); + } + return null; + }); + Route::bind('category', function($value, $route) { if(Auth::check()) { @@ -180,6 +190,12 @@ Route::group(['before' => 'auth'], function () { Route::get('/recurring/edit/{recurring}',['uses' => 'RecurringController@edit','as' => 'recurring.edit']); Route::get('/recurring/delete/{recurring}',['uses' => 'RecurringController@delete','as' => 'recurring.delete']); + // reminder controller + Route::get('/reminders/dialog',['uses' => 'ReminderController@modalDialog']); + Route::post('/reminders/postpone/{reminder}',['uses' => 'ReminderController@postpone']); + Route::post('/reminders/dismiss/{reminder}',['uses' => 'ReminderController@dismiss']); + Route::get('/reminders/redirect/{reminder}',['uses' => 'ReminderController@redirect']); + // report controller: Route::get('/reports',['uses' => 'ReportController@index','as' => 'reports.index']); diff --git a/app/tests/controllers/PiggybankControllerTest.php b/app/tests/controllers/PiggybankControllerTest.php index 0bbddb75f4..75e2bb9071 100644 --- a/app/tests/controllers/PiggybankControllerTest.php +++ b/app/tests/controllers/PiggybankControllerTest.php @@ -311,6 +311,13 @@ class PiggybankControllerTest extends TestCase public function teststorePiggybank() { $piggy = f::create('Piggybank'); + $piggy->repeats = 0; + $piggy->save(); + Event::shouldReceive('fire')->with('piggybanks.storePiggy',[$piggy])->once(); + //Event::fire('piggybanks.storePiggy',[$piggyBank]); + //Event::fire('piggybanks.storeRepeated',[$piggyBank]); + + $this->_piggybanks->shouldReceive('store')->once()->andReturn($piggy); $this->action('POST', 'PiggybankController@storePiggybank'); $this->assertResponseStatus(302); @@ -319,6 +326,9 @@ class PiggybankControllerTest extends TestCase public function testStoreRepeated() { $piggy = f::create('Piggybank'); + $piggy->repeats = 1; + $piggy->save(); + Event::shouldReceive('fire')->with('piggybanks.storeRepeated',[$piggy])->once(); $this->_piggybanks->shouldReceive('store')->once()->andReturn($piggy); $this->action('POST', 'PiggybankController@storeRepeated'); $this->assertResponseStatus(302); diff --git a/app/views/layouts/default.blade.php b/app/views/layouts/default.blade.php index f4cc2aff06..9f0d331398 100644 --- a/app/views/layouts/default.blade.php +++ b/app/views/layouts/default.blade.php @@ -10,13 +10,6 @@ @yield('styles') - - - - + + + + @yield('scripts') diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index 7cf4fcc89e..f2d7c0b374 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -54,9 +54,13 @@ $r = Route::current()->getName(); - + @if(Session::get('reminderCount') == 1) + + @endif + @if(Session::get('reminderCount') > 1) + + @endif @if(\Auth::user() && \Auth::check())