From 5f4db7874c90e5c127805063f3fec48913da1ccb Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 08:19:18 +0200 Subject: [PATCH 01/15] New view and what-not for feature. --- app/controllers/RecurringController.php | 29 ++++++++++-- .../EloquentTransactionJournalRepository.php | 10 +++++ .../TransactionJournalRepositoryInterface.php | 8 ++++ .../Recurring/EloquentRecurringTrigger.php | 44 +++++++++++++++++++ app/routes.php | 1 + app/views/recurring/show.blade.php | 14 +++++- 6 files changed, 101 insertions(+), 5 deletions(-) diff --git a/app/controllers/RecurringController.php b/app/controllers/RecurringController.php index 3ff0fea28b..a8f8cec1e9 100644 --- a/app/controllers/RecurringController.php +++ b/app/controllers/RecurringController.php @@ -21,7 +21,7 @@ class RecurringController extends BaseController public function __construct(RTR $repository, RI $helper) { $this->_repository = $repository; - $this->_helper = $helper; + $this->_helper = $helper; View::share('title', 'Recurring transactions'); View::share('mainTitleIcon', 'fa-rotate-right'); @@ -102,6 +102,27 @@ class RecurringController extends BaseController } + /** + * @param RecurringTransaction $recurringTransaction + * @return mixed + */ + public function rescan(RecurringTransaction $recurringTransaction) + { + // do something! + /** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $repo */ + $repo = App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface'); + $set = $repo->get(); + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + Event::fire('recurring.rescan', [$recurringTransaction, $journal]); + } + Session::flash('success','Rescanned everything.'); + return Redirect::back(); + + + } + public function store() { $data = Input::except(['_token', 'post_submit_action']); @@ -170,14 +191,14 @@ class RecurringController extends BaseController Session::flash('error', 'Could not update recurring transaction: ' . $messageBag->first()); return Redirect::route('transactions.edit', $recurringTransaction->id)->withInput() - ->withErrors($messageBag); + ->withErrors($messageBag); } break; case 'validate_only': - $data = Input::all(); - $data['id'] = $recurringTransaction->id; + $data = Input::all(); + $data['id'] = $recurringTransaction->id; $messageBags = $this->_helper->validate($data); Session::flash('warnings', $messageBags['warnings']); diff --git a/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php b/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php index 2f2751d873..bb36f667e2 100644 --- a/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php +++ b/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php @@ -5,6 +5,7 @@ namespace Firefly\Storage\TransactionJournal; use Carbon\Carbon; use Illuminate\Queue\Jobs\Job; +use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; /** @@ -24,6 +25,15 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito $this->_user = \Auth::user(); } + /** + * Get them ALL + * + * @return Collection + */ + public function get() { + return $this->_user->transactionjournals()->with('transactions')->get(); + } + /** * @param Job $job * @param array $payload diff --git a/app/lib/Firefly/Storage/TransactionJournal/TransactionJournalRepositoryInterface.php b/app/lib/Firefly/Storage/TransactionJournal/TransactionJournalRepositoryInterface.php index 8726de1f3e..2d01a7884d 100644 --- a/app/lib/Firefly/Storage/TransactionJournal/TransactionJournalRepositoryInterface.php +++ b/app/lib/Firefly/Storage/TransactionJournal/TransactionJournalRepositoryInterface.php @@ -4,6 +4,7 @@ namespace Firefly\Storage\TransactionJournal; use Carbon\Carbon; use Illuminate\Queue\Jobs\Job; +use Illuminate\Support\Collection; /** * Interface TransactionJournalRepositoryInterface @@ -20,6 +21,13 @@ interface TransactionJournalRepositoryInterface */ public function importTransaction(Job $job, array $payload); + /** + * Get them ALL + * + * @return Collection + */ + public function get(); + /** * @param Job $job * @param array $payload diff --git a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php index 0223091ee5..6ec7dfc62d 100644 --- a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php +++ b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php @@ -32,6 +32,48 @@ class EloquentRecurringTrigger { } + public function rescan(\RecurringTransaction $recurring, \TransactionJournal $journal) + { + /* + * Match words. + */ + $wordMatch = false; + $matches = explode(' ', $recurring->match); + $description = strtolower($journal->description); + $count = 0; + foreach ($matches as $word) { + if (!(strpos($description, $word) === false)) { + $count++; + } + } + if ($count > 0) { + $wordMatch = true; + } + + /* + * Match amount. + */ + $transactions = $journal->transactions()->get(); + $amountMatch = false; + if (count($transactions) > 1) { + + $amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount)); + $min = floatval($recurring->amount_min); + $max = floatval($recurring->amount_max); + if ($amount >= $min && $amount <= $max) { + $amountMatch = true; + } + } + + /* + * If both, update! + */ + if ($wordMatch && $amountMatch) { + $journal->recurringTransaction()->associate($recurring); + $journal->save(); + } + } + /** * Trigger! * @@ -39,6 +81,8 @@ class EloquentRecurringTrigger */ public function subscribe(Dispatcher $events) { + //Event::fire('recurring.rematch', [$recurringTransaction, $journal]); + $events->listen('recurring.rescan', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@rescan'); // $events->listen('recurring.destroy', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@destroy'); // $events->listen('recurring.store', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@store'); // $events->listen('recurring.update', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@update'); diff --git a/app/routes.php b/app/routes.php index d3e124e4fd..9fd2bf8fe4 100644 --- a/app/routes.php +++ b/app/routes.php @@ -219,6 +219,7 @@ Route::group(['before' => 'auth'], function () { // recurring transactions controller Route::get('/recurring',['uses' => 'RecurringController@index', 'as' => 'recurring.index']); Route::get('/recurring/show/{recurring}',['uses' => 'RecurringController@show', 'as' => 'recurring.show']); + Route::get('/recurring/rescan/{recurring}',['uses' => 'RecurringController@rescan', 'as' => 'recurring.rescan']); Route::get('/recurring/create',['uses' => 'RecurringController@create', 'as' => 'recurring.create']); Route::get('/recurring/edit/{recurring}',['uses' => 'RecurringController@edit','as' => 'recurring.edit']); Route::get('/recurring/delete/{recurring}',['uses' => 'RecurringController@delete','as' => 'recurring.delete']); diff --git a/app/views/recurring/show.blade.php b/app/views/recurring/show.blade.php index 918b52d638..07450a0840 100644 --- a/app/views/recurring/show.blade.php +++ b/app/views/recurring/show.blade.php @@ -1,7 +1,7 @@ @extends('layouts.default') @section('content')
-
+
{{{$recurring->name}}} @@ -53,6 +53,18 @@
+
+
+
+ More +
+ +
+
From 637a5579ec8e48bc5f57647e7fb315ae52bb5dfb Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:29:19 +0200 Subject: [PATCH 02/15] Small cleanup. --- app/controllers/HomeController.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php index 621dea28c6..0f2a049c9d 100644 --- a/app/controllers/HomeController.php +++ b/app/controllers/HomeController.php @@ -1,7 +1,6 @@ _accounts = $accounts; $this->_preferences = $preferences; $this->_journal = $journal; - $this->_reminders = $reminders; } public function jobDev() @@ -65,10 +61,9 @@ class HomeController extends BaseController public function rangeJump($range) { - $viewRange = $this->_preferences->get('viewRange', '1M'); - $valid = ['1D', '1W', '1M', '3M', '6M', '1Y',]; + $valid = ['1D', '1W', '1M', '3M', '6M', '1Y',]; - if(in_array($range,$valid)) { + if (in_array($range, $valid)) { $this->_preferences->set('viewRange', $range); Session::forget('range'); } From d9ac681a68928dc63da873073603c07567ce234b Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:31:25 +0200 Subject: [PATCH 03/15] Bug fixes in recurring transaction matching. --- .../Firefly/Trigger/Journals/EloquentJournalTrigger.php | 2 +- .../Firefly/Trigger/Recurring/EloquentRecurringTrigger.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/lib/Firefly/Trigger/Journals/EloquentJournalTrigger.php b/app/lib/Firefly/Trigger/Journals/EloquentJournalTrigger.php index 402b1a3326..a78c22737d 100644 --- a/app/lib/Firefly/Trigger/Journals/EloquentJournalTrigger.php +++ b/app/lib/Firefly/Trigger/Journals/EloquentJournalTrigger.php @@ -40,7 +40,7 @@ class EloquentJournalTrigger */ $count = 0; foreach ($matches as $word) { - if (!(strpos($description, $word) === false)) { + if (!(strpos($description, strtolower($word)) === false)) { $count++; \Log::debug('Recurring transaction #' . $recurring->id . ': word "' . $word . '" found in "' . $description . '".'); } diff --git a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php index 6ec7dfc62d..cdc45d09c8 100644 --- a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php +++ b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php @@ -30,8 +30,13 @@ class EloquentRecurringTrigger public function createReminders() { + } + /** + * @param \RecurringTransaction $recurring + * @param \TransactionJournal $journal + */ public function rescan(\RecurringTransaction $recurring, \TransactionJournal $journal) { /* @@ -42,7 +47,7 @@ class EloquentRecurringTrigger $description = strtolower($journal->description); $count = 0; foreach ($matches as $word) { - if (!(strpos($description, $word) === false)) { + if (!(strpos($description, strtolower($word)) === false)) { $count++; } } From b024c18441dafb1bdb6794583e041be9eb8376c8 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:34:10 +0200 Subject: [PATCH 04/15] Cleaned out the reminder controller. --- app/controllers/ReminderController.php | 89 +------------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/app/controllers/ReminderController.php b/app/controllers/ReminderController.php index ab260453b2..657f3e8a10 100644 --- a/app/controllers/ReminderController.php +++ b/app/controllers/ReminderController.php @@ -1,97 +1,10 @@ _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->getPiggybankReminders(); - - /** @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 - * - * @return $this|\Illuminate\Http\RedirectResponse - */ - 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) - ); - } - return View::make('error')->with('message', 'No such reminder.'); - - } - -} \ No newline at end of file +} \ No newline at end of file From 8a22509b4138c91229b8656490a9331b5717cc7e Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:34:30 +0200 Subject: [PATCH 05/15] Removed a no longer used view. --- app/views/reminders/popup.blade.php | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 app/views/reminders/popup.blade.php diff --git a/app/views/reminders/popup.blade.php b/app/views/reminders/popup.blade.php deleted file mode 100644 index 272d33fdbd..0000000000 --- a/app/views/reminders/popup.blade.php +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file From 0ab87de78bb0fd2d68035a9a080497c352a21b86 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:34:47 +0200 Subject: [PATCH 06/15] Removed unused routes. --- app/routes.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/routes.php b/app/routes.php index 9fd2bf8fe4..df3cfeced2 100644 --- a/app/routes.php +++ b/app/routes.php @@ -224,12 +224,6 @@ 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']); From c577dd302ad3c41e8fedfaad5c1b6b06847ad189 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:36:24 +0200 Subject: [PATCH 07/15] Removed unused reminder methods. --- .../Storage/Reminder/EloquentReminderRepository.php | 13 ------------- .../Reminder/ReminderRepositoryInterface.php | 8 -------- 2 files changed, 21 deletions(-) diff --git a/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php b/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php index 9d312ca00a..dfffde8651 100644 --- a/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php +++ b/app/lib/Firefly/Storage/Reminder/EloquentReminderRepository.php @@ -31,17 +31,4 @@ class EloquentReminderRepository implements ReminderRepositoryInterface { $this->_user = \Auth::user(); } - - /** - * @param \Reminder $reminder - * - * @return mixed|void - */ - public function deactivate(\Reminder $reminder) - { - $reminder->active = 0; - $reminder->save(); - - return $reminder; - } } \ No newline at end of file diff --git a/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php b/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php index 560fd474ff..05e6c77a54 100644 --- a/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php +++ b/app/lib/Firefly/Storage/Reminder/ReminderRepositoryInterface.php @@ -10,14 +10,6 @@ namespace Firefly\Storage\Reminder; interface ReminderRepositoryInterface { - /** - * @param \Reminder $reminder - * - * @return mixed - */ - public function deactivate(\Reminder $reminder); - - /** * @param \User $user * @return mixed From 41dd139bdee84ee7cdfec230ab22bcd522e7b758 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:37:31 +0200 Subject: [PATCH 08/15] Trigger no longer fires or creates reminders for piggy banks. --- .../Piggybanks/EloquentPiggybankTrigger.php | 156 ------------------ 1 file changed, 156 deletions(-) diff --git a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php index 11a687b02a..168082a360 100644 --- a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php +++ b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php @@ -107,159 +107,6 @@ class EloquentPiggybankTrigger return true; } - /** - * Whenever a repetition is made, the decision is there to make reminders for it. Or not. - * Some combinations are "invalid" or impossible and will never trigger reminders. Others do. - * - * The numbers below refer to a small list I made in a text-file (it no longer exists) which contained the eight - * binary combinations that can be made of three properties each piggy bank has (among others): - * - * - Whether or not it has a start date. - * - Whether or not it has an end date. - * - Whether or not the piggy bank repeats itself. - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * - * @param \PiggybankRepetition $repetition - * - * @return null - */ - public function createdRepetition(\PiggybankRepetition $repetition) - { - \Log::debug('TRIGGER on createdRepetition() for repetition #' . $repetition->id); - - $piggyBank = $repetition->piggybank; - - // first, exclude all combinations that will not generate (valid) reminders - - // no reminders needed (duh) - if (is_null(($piggyBank->reminder))) { - \Log::debug('No reminders because no reminder needed.'); - return null; - } - - // no start, no target, no repeat (#1): - if (is_null($piggyBank->startdate) && is_null($piggyBank->targetdate) && $piggyBank->repeats == 0) { - \Log::debug('No reminders because no start, no target, no repeat (#1)'); - return null; - } - - // no start, but repeats (#5): - if (is_null($piggyBank->startdate) && $piggyBank->repeats == 1) { - \Log::debug('No reminders because no start, but repeats (#5)'); - return null; - } - - // no start, no end, but repeats (#6) - if (is_null($piggyBank->startdate) && is_null($piggyBank->targetdate) && $piggyBank->repeats == 1) { - \Log::debug('No reminders because no start, no end, but repeats (#6)'); - return null; - } - - // no end, but repeats (#7) - if (is_null($piggyBank->targetdate) && $piggyBank->repeats == 1) { - \Log::debug('No reminders because no end, but repeats (#7)'); - return null; - } - - \Log::debug('Will continue...'); - /* - * #2, #3, #4 and #8 are valid combo's. - * - * We add two years to the end when the repetition has no target date; we "pretend" there is a target date. - * - */ - if (is_null($repetition->targetdate)) { - $end = new Carbon; - $end->addYears(2); - } else { - $end = $repetition->targetdate; - } - /* - * If there is no start date, the start dat becomes right now. - */ - if (is_null($repetition->startdate)) { - $start = new Carbon; - } else { - $start = $repetition->startdate; - } - - /* - * Firefly checks every period X between $start and $end and if necessary creates a reminder. Firefly - * only creates reminders if the $current date is after today. Piggy banks may have their start in the past. - * - * This loop will jump a month when the reminder is set monthly, a week when it's set weekly, etcetera. - */ - $current = $start; - $today = new Carbon; - $today->startOfDay(); - while ($current <= $end) { - \Log::debug('Looping reminder dates; now at ' . $current); - /* - * Piggy bank reminders start X days before the actual date of the event. - */ - $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 the date is past today we create a reminder, otherwise we don't. The end date is the date - * the reminder is due; after that it is invalid. - */ - if ($current >= $today) { - $reminder = new \PiggybankReminder; - $reminder->piggybank()->associate($piggyBank); - $reminder->user()->associate(\Auth::user()); - $reminder->startdate = $reminderStart; - $reminder->enddate = $current; - $reminder->active = 1; - \Log::debug('Will create a reminder. Is it valid?'); - \Log::debug($reminder->validate()); - try { - - $reminder->save(); - } catch (QueryException $e) { - \Log::error('Could not save reminder: ' . $e->getMessage()); - } - } else { - \Log::debug('Current is before today, will not make a reminder.'); - } - - /* - * Here Firefly jumps ahead to the next reminder period. - */ - 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; - } - } - - } - /** * @param \Piggybank $piggyBank * @@ -356,9 +203,6 @@ class EloquentPiggybankTrigger 'piggybanks.check', 'Firefly\Trigger\Piggybanks\EloquentPiggybankTrigger@checkRepeatingPiggies' ); - $events->listen( - 'piggybanks.repetition', 'Firefly\Trigger\Piggybanks\EloquentPiggybankTrigger@createdRepetition' - ); } /** From e4595333e76092f64aadf950fc5724eb411ce5cd Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:39:29 +0200 Subject: [PATCH 09/15] Do not destroy reminders since there aren't any. --- .../Trigger/Piggybanks/EloquentPiggybankTrigger.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php index 168082a360..80023eb48e 100644 --- a/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php +++ b/app/lib/Firefly/Trigger/Piggybanks/EloquentPiggybankTrigger.php @@ -114,13 +114,6 @@ class EloquentPiggybankTrigger */ public function destroy(\Piggybank $piggyBank) { - $reminders = $piggyBank->piggybankreminders()->get(); - /** @var \PiggybankReminder $reminder */ - foreach ($reminders as $reminder) { - $reminder->delete(); - - } - return true; } From b184aa231518cbde2b76d31a2634c4cfdd2a177d Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:39:40 +0200 Subject: [PATCH 10/15] No trigger for recurring transactions. --- .../Firefly/Trigger/Recurring/EloquentRecurringTrigger.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php index cdc45d09c8..c8f1403e53 100644 --- a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php +++ b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php @@ -28,11 +28,6 @@ class EloquentRecurringTrigger } - public function createReminders() - { - - } - /** * @param \RecurringTransaction $recurring * @param \TransactionJournal $journal @@ -91,7 +86,6 @@ class EloquentRecurringTrigger // $events->listen('recurring.destroy', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@destroy'); // $events->listen('recurring.store', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@store'); // $events->listen('recurring.update', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@update'); -// $events->listen('recurring.check', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@createReminders'); } /** From a6bb61050c9b31c4d2908dbea682bb5ea3208e93 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:41:00 +0200 Subject: [PATCH 11/15] Remove piggy bank reminders. --- app/models/Piggybank.php | 70 ----------------------- app/models/PiggybankReminder.php | 97 -------------------------------- 2 files changed, 167 deletions(-) diff --git a/app/models/Piggybank.php b/app/models/Piggybank.php index 5c54acc7b8..b754e133f2 100644 --- a/app/models/Piggybank.php +++ b/app/models/Piggybank.php @@ -104,68 +104,6 @@ class Piggybank extends Ardent return ['created_at', 'updated_at', 'targetdate', 'startdate']; } - /** - * Firefly shouldn't create piggybank repetions that completely - * lie in the future, so we should be able to safely grab the "latest" - * one and use that to calculate when the user will be reminded. - */ - public function nextReminderDate() - { - if (is_null($this->reminder)) { - return null; - } - /** @var \PiggybankRepetition $rep */ - $rep = $this->currentRelevantRep(); - $today = new Carbon; - if ($rep && is_null($rep->startdate)) { - switch ($this->reminder) { - case 'day': - return $today; - break; - case 'week': - return $today->endOfWeek(); - break; - case 'month': - return $today->endOfMonth(); - break; - case 'year': - return $today->endOfYear(); - break; - - } - return null; - } - if ($rep && !is_null($rep->startdate)) { - // start with the start date - // when its bigger than today, return it: - $start = clone $rep->startdate; - while ($start <= $today) { - switch ($this->reminder) { - default: - return null; - break; - case 'day': - $start->addDay(); - break; - case 'week': - $start->addWeek(); - break; - case 'month': - $start->addMonth(); - break; - case 'year': - $start->addYear(); - break; - - } - } - - return $start; - } - - return new Carbon; - } - /** * Grabs the PiggyBankRepetition that's currently relevant / active * @@ -222,14 +160,6 @@ class Piggybank extends Ardent return $this->hasMany('PiggybankEvent'); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function piggybankreminders() - { - return $this->hasMany('PiggybankReminder'); - } - /** * Same but for specific date. * diff --git a/app/models/PiggybankReminder.php b/app/models/PiggybankReminder.php index 58ab497e6d..1910ad964c 100644 --- a/app/models/PiggybankReminder.php +++ b/app/models/PiggybankReminder.php @@ -1,105 +1,8 @@ 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; - } - - - /** - * @return float - * @throws Firefly\Exception\FireflyException - */ - public function amountToSave() - { - /** @var \Piggybank $piggyBank */ - $piggyBank = $this->piggybank; - /** @var \PiggybankRepetition $repetition */ - $repetition = $piggyBank->currentRelevantRep(); - - // if the target date of the repetition is zero, we use the created_at date of the repetition - // and add two years; it's the same routine used elsewhere. - if (is_null($repetition->targetdate)) { - $targetdate = clone $repetition->created_at; - $targetdate->addYears(2); - } else { - $targetdate = $repetition->targetdate; - } - - - $today = new Carbon; - $diff = $today->diff($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': - $toSave = $left;// / ($diff->days / $piggyBank->reminder_skip); - break; - case 'week': - $weeks = ceil($diff->days / 7); - $toSave = $left / ($weeks / $piggyBank->reminder_skip); - 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); - } } \ No newline at end of file From 7c97c558ab4e2767c294a522c697ec5f77cb3500 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:41:10 +0200 Subject: [PATCH 12/15] No reference to reminders. --- app/views/piggybanks/index.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/piggybanks/index.blade.php b/app/views/piggybanks/index.blade.php index 218044670e..bb4af677cf 100644 --- a/app/views/piggybanks/index.blade.php +++ b/app/views/piggybanks/index.blade.php @@ -57,7 +57,7 @@ Target date: {{$piggyBank->targetdate->format('M jS, Y')}}
@endif @if(!is_null($piggyBank->reminder)) - Next reminder: {{$piggyBank->nextReminderDate()->format('M jS, Y')}} ({{$piggyBank->reminder}}) + Next reminder: TODO @endif

@@ -119,7 +119,7 @@ @if(!is_null($repeated->reminder)) - Next reminder: {{$repeated->nextReminderDate()->format('M jS, Y')}} ({{$piggyBank->reminder}}) + Next reminder: TODO @endif From 65a2e07d245117b114d5ac7c3904e305ca232019 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Sun, 12 Oct 2014 09:45:57 +0200 Subject: [PATCH 13/15] Cleaned out most of the "reminders" code. --- ...14_08_23_113221_create_reminders_table.php | 14 ------- app/models/PiggybankReminder.php | 8 ---- app/models/RecurringTransaction.php | 9 ---- app/models/RecurringTransactionReminder.php | 42 ------------------- app/models/TransactionJournal.php | 8 ++++ app/models/User.php | 16 ------- 6 files changed, 8 insertions(+), 89 deletions(-) delete mode 100644 app/models/PiggybankReminder.php delete mode 100644 app/models/RecurringTransactionReminder.php 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 index 73e53ae084..24b654a35f 100644 --- a/app/database/migrations/2014_08_23_113221_create_reminders_table.php +++ b/app/database/migrations/2014_08_23_113221_create_reminders_table.php @@ -28,25 +28,11 @@ class CreateRemindersTable extends Migration $table->increments('id'); $table->timestamps(); $table->string('class', 40); - $table->integer('piggybank_id')->unsigned()->nullable(); - $table->integer('recurring_transaction_id')->unsigned()->nullable(); $table->integer('user_id')->unsigned(); $table->date('startdate'); $table->date('enddate')->nullable(); $table->boolean('active'); - - // connect reminders to piggy banks. - $table->foreign('piggybank_id') - ->references('id')->on('piggybanks') - ->onDelete('set null'); - - // connect reminders to recurring transactions. - $table->foreign('recurring_transaction_id') - ->references('id')->on('recurring_transactions') - ->onDelete('set null'); - - // connect reminders to users $table->foreign('user_id') ->references('id')->on('users') diff --git a/app/models/PiggybankReminder.php b/app/models/PiggybankReminder.php deleted file mode 100644 index 1910ad964c..0000000000 --- a/app/models/PiggybankReminder.php +++ /dev/null @@ -1,8 +0,0 @@ -hasMany('RecurringTransactionReminder'); - - } - /** * @return Carbon */ diff --git a/app/models/RecurringTransactionReminder.php b/app/models/RecurringTransactionReminder.php deleted file mode 100644 index 8d0d728419..0000000000 --- a/app/models/RecurringTransactionReminder.php +++ /dev/null @@ -1,42 +0,0 @@ -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 */ From 36d8dee853c3f92a4711ec6583ca7223610c9460 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 13 Oct 2014 17:54:20 +0200 Subject: [PATCH 14/15] Building a chart for the recurring transactions. --- app/controllers/ChartController.php | 189 ++++++++++++++---- app/controllers/RecurringController.php | 6 +- ...14_08_23_113221_create_reminders_table.php | 1 - app/filters.php | 1 + app/lib/Firefly/Helper/Controllers/Json.php | 7 +- app/lib/Firefly/Helper/Toolkit/Toolkit.php | 123 ++++++++++++ .../Helper/Toolkit/ToolkitInterface.php | 9 +- .../Recurring/EloquentRecurringTrigger.php | 5 +- app/models/Reminder.php | 72 +------ app/models/TransactionJournal.php | 16 ++ app/routes.php | 1 + app/views/index.blade.php | 26 ++- public/assets/javascript/firefly/index.js | 38 +++- 13 files changed, 373 insertions(+), 121 deletions(-) diff --git a/app/controllers/ChartController.php b/app/controllers/ChartController.php index f6301fd9ab..546972335d 100644 --- a/app/controllers/ChartController.php +++ b/app/controllers/ChartController.php @@ -1,8 +1,10 @@ 'Overview for budget ' . $budget->name, - 'subtitle' => 'All envelopes', - 'series' => [ + 'subtitle' => 'All envelopes', + 'series' => [ [ - 'type' => 'line', + 'type' => 'line', 'yAxis' => 1, - 'name' => 'Amount in envelope', - 'data' => $envelope + 'name' => 'Amount in envelope', + 'data' => $envelope ], [ 'type' => 'column', @@ -71,10 +73,10 @@ class ChartController extends BaseController 'data' => $expense ], [ - 'type' => 'line', + 'type' => 'line', 'yAxis' => 1, - 'name' => 'Spent percentage for envelope', - 'data' => $left + 'name' => 'Spent percentage for envelope', + 'data' => $left ] @@ -111,14 +113,14 @@ class ChartController extends BaseController $return = [ 'chart_title' => 'Overview for budget ' . $budget->name, - 'subtitle' => + 'subtitle' => 'Between ' . $rep->startdate->format('M jS, Y') . ' and ' . $rep->enddate->format('M jS, Y'), - 'series' => [ + 'series' => [ [ - 'type' => 'column', - 'name' => 'Expenses per day', + 'type' => 'column', + 'name' => 'Expenses per day', 'yAxis' => 1, - 'data' => $expense + 'data' => $expense ], [ 'type' => 'line', @@ -175,8 +177,8 @@ class ChartController extends BaseController } $return = [ 'chart_title' => 'Overview for ' . $budget->name, - 'subtitle' => 'Not organized by an envelope', - 'series' => [ + 'subtitle' => 'Not organized by an envelope', + 'series' => [ [ 'type' => 'column', 'name' => 'Expenses per day', @@ -245,11 +247,11 @@ class ChartController extends BaseController // create a serie for the repetition. $currentSerie = [ - 'type' => 'spline', - 'id' => 'rep-' . $repetition->id, + 'type' => 'spline', + 'id' => 'rep-' . $repetition->id, 'yAxis' => 1, - 'name' => 'Envelope #' . $repetition->id . ' in ' . $repetition->periodShow(), - 'data' => [] + 'name' => 'Envelope #' . $repetition->id . ' in ' . $repetition->periodShow(), + 'data' => [] ]; $current = clone $repetition->startdate; while ($current <= $repetition->enddate) { @@ -271,11 +273,11 @@ class ChartController extends BaseController $return = [ 'chart_title' => 'Overview for budget ' . $budget->name, - 'subtitle' => + 'subtitle' => 'Between ' . Session::get('start')->format('M jS, Y') . ' and ' . Session::get('end')->format( 'M jS, Y' ), - 'series' => $series + 'series' => $series ]; return Response::json($return); @@ -296,8 +298,8 @@ class ChartController extends BaseController $serie = $this->_chart->categoryShowChart($category, $range, $start, $end); $data = [ 'chart_title' => $category->name, - 'subtitle' => 'View more', - 'series' => $serie + 'subtitle' => 'View more', + 'series' => $serie ]; return Response::json($data); @@ -336,8 +338,8 @@ class ChartController extends BaseController 'View more'; $data = [ 'chart_title' => count($accounts) == 1 ? $accounts[0]->name : 'All accounts', - 'subtitle' => $url, - 'series' => [] + 'subtitle' => $url, + 'series' => [] ]; foreach ($accounts as $account) { @@ -431,11 +433,11 @@ class ChartController extends BaseController if ($limit > 0 || $expenses > 0) { $data['labels'][] = $budget->name; $data['series'][0]['data'][] = [ - 'y' => $limit, + 'y' => $limit, 'url' => route('budgets.show', [$budget->id, $id]) . '?' . $parameter ]; $data['series'][1]['data'][] = [ - 'y' => $expenses, + 'y' => $expenses, 'url' => route('budgets.show', [$budget->id, $id]) . '?' . $parameter ]; } @@ -444,26 +446,26 @@ class ChartController extends BaseController $set = \Auth::user()->transactionjournals()->whereNotIn( 'transaction_journals.id', function ($query) use ($start, $end) { $query->select('transaction_journals.id')->from('transaction_journals') - ->leftJoin( - 'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=', - 'transaction_journals.id' - ) - ->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('components.class', 'Budget'); + ->leftJoin( + 'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=', + 'transaction_journals.id' + ) + ->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('components.class', 'Budget'); } )->before($end)->after($start)->lessThan(0)->transactionTypes(['Withdrawal'])->sum('amount'); // This can be debugged by using get(['transaction_journals.*','transactions.amount']); $data['labels'][] = 'No budget'; $data['series'][0]['data'][] = [ - 'y' => 0, - 'url' => route('budgets.nobudget','session') + 'y' => 0, + 'url' => route('budgets.nobudget', 'session') ]; $data['series'][1]['data'][] = [ - 'y' => floatval($set) * -1, - 'url' => route('budgets.nobudget','session') + 'y' => floatval($set) * -1, + 'url' => route('budgets.nobudget', 'session') ]; return Response::json($data); @@ -482,4 +484,111 @@ class ChartController extends BaseController } + + /** + * This method checks all recurring transactions, calculates the current "moment" and returns + * a list of yet to be paid (and paid) recurring transactions. This list can be used to show a beautiful chart + * to the end user who will love it and cherish it. + * + * @throws FireflyException + */ + public function homeRecurring() + { + /** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */ + $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); + $recurringTransactions = \Auth::user()->recurringtransactions()->get(); + $sessionStart = \Session::get('start'); + $sessionEnd = \Session::get('end'); + $paid = []; + $unpaid = []; + + /** @var \RecurringTransaction $recurring */ + foreach ($recurringTransactions as $recurring) { + /* + * Start a loop starting at the $date. + */ + $start = clone $recurring->date; + $end = Carbon::now(); + + /* + * The jump we make depends on the $repeat_freq + */ + $current = clone $start; + + \Log::debug('Now looping recurring transaction #' . $recurring->id . ': ' . $recurring->name); + + while ($current <= $end) { + /* + * Get end of period for $current: + */ + $currentEnd = clone $current; + $toolkit->endOfPeriod($currentEnd, $recurring->repeat_freq); + \Log::debug('Now at $current: ' . $current->format('D d F Y') . ' - ' . $currentEnd->format('D d F Y')); + + /* + * In the current session range? + */ + if ($sessionEnd >= $current and $currentEnd >= $sessionStart) { + /* + * Lets see if we've already spent money on this recurring transaction (it hath recurred). + */ + /** @var Collection $set */ + $set = \Auth::user()->transactionjournals()->where('recurring_transaction_id', $recurring->id) + ->after($current)->before($currentEnd)->get(); + if (count($set) > 1) { + \Log::error('Recurring #' . $recurring->id . ', dates [' . $current . ',' . $currentEnd . ']. Found multiple hits. Cannot handle this!'); + throw new FireflyException('Cannot handle multiple transactions. See logs.'); + } else if (count($set) == 0) { + $unpaid[] = $recurring; + } else { + $paid[] = $set->get(0); + } + + } + + + /* + * Add some time for the next loop! + */ + $toolkit->addPeriod($current, $recurring->repeat_freq, intval($recurring->skip)); + + } + + } + /* + * Loop paid and unpaid to make two haves for a pie chart. + */ + $unPaidColours = $toolkit->colorRange('AA4643', 'FFFFFF', count($unpaid) == 0 ? 1 : count($unpaid)); + $paidColours = $toolkit->colorRange('4572A7', 'FFFFFF', count($paid) == 0 ? 1 : count($paid)); + $serie = [ + 'type' => 'pie', + 'name' => 'Amount', + 'data' => [] + ]; + + /** @var TransactionJournal $entry */ + foreach ($paid as $index => $entry) { + $transactions = $entry->transactions()->get(); + $amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount)); + $serie['data'][] = [ + 'name' => $entry->description, + 'y' => $amount, + 'color' => $paidColours[$index] + ]; + } + + + /** @var RecurringTransaction $entry */ + foreach ($unpaid as $index => $entry) { + $amount = (floatval($entry->amount_max) + floatval($entry->amount_min)) / 2; + $serie['data'][] = [ + 'name' => $entry->name, + 'y' => $amount, + 'color' => $unPaidColours[$index] + ]; + } + + return Response::json([$serie]); + + } } \ No newline at end of file diff --git a/app/controllers/RecurringController.php b/app/controllers/RecurringController.php index a8f8cec1e9..1fe8e051af 100644 --- a/app/controllers/RecurringController.php +++ b/app/controllers/RecurringController.php @@ -108,6 +108,10 @@ class RecurringController extends BaseController */ public function rescan(RecurringTransaction $recurringTransaction) { + if (intval($recurringTransaction->active) == 0) { + Session::flash('warning', 'Inactive recurring transactions cannot be scanned.'); + return Redirect::back(); + } // do something! /** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $repo */ $repo = App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface'); @@ -117,7 +121,7 @@ class RecurringController extends BaseController foreach ($set as $journal) { Event::fire('recurring.rescan', [$recurringTransaction, $journal]); } - Session::flash('success','Rescanned everything.'); + Session::flash('success', 'Rescanned everything.'); return Redirect::back(); 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 index 24b654a35f..12a04c3b00 100644 --- a/app/database/migrations/2014_08_23_113221_create_reminders_table.php +++ b/app/database/migrations/2014_08_23_113221_create_reminders_table.php @@ -27,7 +27,6 @@ class CreateRemindersTable extends Migration 'reminders', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); - $table->string('class', 40); $table->integer('user_id')->unsigned(); $table->date('startdate'); $table->date('enddate')->nullable(); diff --git a/app/filters.php b/app/filters.php index 9a1c96aebc..5b4cf13296 100644 --- a/app/filters.php +++ b/app/filters.php @@ -10,6 +10,7 @@ App::before( $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); $toolkit->getDateRange(); $toolkit->checkImportJobs(); + Event::fire('recurring.verify'); } } diff --git a/app/lib/Firefly/Helper/Controllers/Json.php b/app/lib/Firefly/Helper/Controllers/Json.php index a74a954d92..80b87286f7 100644 --- a/app/lib/Firefly/Helper/Controllers/Json.php +++ b/app/lib/Firefly/Helper/Controllers/Json.php @@ -355,7 +355,7 @@ class Json implements JsonInterface * Loop set and create entries to return. */ foreach ($set as $entry) { - $data['data'][] = [ + $set = [ 'name' => ['name' => $entry->name, 'url' => route('recurring.show', $entry->id)], 'match' => explode(' ', $entry->match), @@ -370,6 +370,11 @@ class Json implements JsonInterface 'delete' => route('recurring.delete', $entry->id) ] ]; + if (intval($entry->skip) > 0) { + $set['repeat_freq'] = $entry->repeat_freq . ' (skip ' . $entry->skip . ')'; + } + $data['data'][] = $set; + } return $data; diff --git a/app/lib/Firefly/Helper/Toolkit/Toolkit.php b/app/lib/Firefly/Helper/Toolkit/Toolkit.php index 0266c01728..51941a2508 100644 --- a/app/lib/Firefly/Helper/Toolkit/Toolkit.php +++ b/app/lib/Firefly/Helper/Toolkit/Toolkit.php @@ -381,4 +381,127 @@ class Toolkit implements ToolkitInterface } return $selectList; } + + /** + * @param string $start + * @param string $end + * @param int $steps + */ + public function colorRange($start, $end, $steps = 5) + { + if (strlen($start) != 6) { + throw new FireflyException('Start, ' . e($start) . ' should be a six character HTML colour.'); + } + if (strlen($end) != 6) { + throw new FireflyException('End, ' . e($end) . ' should be a six character HTML colour.'); + } + if ($steps < 1) { + throw new FireflyException('Steps must be > 1'); + } + + $start = '#' . $start; + $end = '#' . $end; + /* + * Split html colours. + */ + list($rs, $gs, $bs) = sscanf($start, "#%02x%02x%02x"); + list($re, $ge, $be) = sscanf($end, "#%02x%02x%02x"); + + $stepr = ($re - $rs) / $steps; + $stepg = ($ge - $gs) / $steps; + $stepb = ($be - $bs) / $steps; + + $return = []; + for ($i = 0; $i <= $steps; $i++) { + $cr = $rs + ($stepr * $i); + $cg = $gs + ($stepg * $i); + $cb = $bs + ($stepb * $i); + + $return[] = $this->rgb2html($cr, $cg, $cb); + } + + return $return; + } + + protected function rgb2html($r, $g = -1, $b = -1) + { + $r = dechex($r < 0 ? 0 : ($r > 255 ? 255 : $r)); + $g = dechex($g < 0 ? 0 : ($g > 255 ? 255 : $g)); + $b = dechex($b < 0 ? 0 : ($b > 255 ? 255 : $b)); + + $color = (strlen($r) < 2 ? '0' : '') . $r; + $color .= (strlen($g) < 2 ? '0' : '') . $g; + $color .= (strlen($b) < 2 ? '0' : '') . $b; + return '#' . $color; + } + + /** + * @param Carbon $currentEnd + * @param $repeatFreq + * @throws FireflyException + */ + public function endOfPeriod(Carbon $currentEnd, $repeatFreq) + { + switch ($repeatFreq) { + default: + throw new FireflyException('Cannot do getFunctionForRepeatFreq for $repeat_freq ' . $repeatFreq); + break; + case 'daily': + $currentEnd->addDay(); + break; + case 'weekly': + $currentEnd->addWeek()->subDay(); + break; + case 'monthly': + $currentEnd->addMonth()->subDay(); + break; + case 'quarterly': + $currentEnd->addMonths(3)->subDay(); + break; + case 'half-year': + $currentEnd->addMonths(6)->subDay(); + break; + case 'yearly': + $currentEnd->addYear()->subDay(); + break; + } + } + + /** + * @param Carbon $date + * @param $repeatFreq + * @param $skip + * @return Carbon + * @throws FireflyException + */ + public function addPeriod(Carbon $date, $repeatFreq, $skip) + { + $add = ($skip + 1); + switch ($repeatFreq) { + default: + throw new FireflyException('Cannot do getFunctionForRepeatFreq for $repeat_freq ' . $repeatFreq); + break; + case 'daily': + $date->addDays($add); + break; + case 'weekly': + $date->addWeeks($add); + break; + case 'monthly': + $date->addMonths($add); + break; + case 'quarterly': + $months = $add * 3; + $date->addMonths($months); + break; + case 'half-year': + $months = $add * 6; + $date->addMonths($months); + break; + case 'yearly': + $date->addYears($add); + break; + } + return $date; + } } \ 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 3fd58a6b7d..3c09e686c2 100644 --- a/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php +++ b/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php @@ -21,7 +21,7 @@ interface ToolkitInterface * Takes any collection and tries to make a sensible select list compatible array of it. * * @param Collection $set - * @param null $titleField + * @param null $titleField * * @return mixed */ @@ -33,4 +33,11 @@ interface ToolkitInterface public function checkImportJobs(); + /** + * @param string $start + * @param string $end + * @param int $steps + */ + public function colorRange($start, $end, $steps = 5); + } \ No newline at end of file diff --git a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php index c8f1403e53..e8649214a1 100644 --- a/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php +++ b/app/lib/Firefly/Trigger/Recurring/EloquentRecurringTrigger.php @@ -3,6 +3,7 @@ namespace Firefly\Trigger\Recurring; use Carbon\Carbon; +use Firefly\Exception\FireflyException; use Illuminate\Events\Dispatcher; /** @@ -81,11 +82,7 @@ class EloquentRecurringTrigger */ public function subscribe(Dispatcher $events) { - //Event::fire('recurring.rematch', [$recurringTransaction, $journal]); $events->listen('recurring.rescan', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@rescan'); -// $events->listen('recurring.destroy', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@destroy'); -// $events->listen('recurring.store', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@store'); -// $events->listen('recurring.update', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@update'); } /** diff --git a/app/models/Reminder.php b/app/models/Reminder.php index d720bb3fec..ad18a86c40 100644 --- a/app/models/Reminder.php +++ b/app/models/Reminder.php @@ -2,6 +2,8 @@ use Carbon\Carbon; use Firefly\Database\SingleTableInheritanceEntity; +use LaravelBook\Ardent\Ardent; + /** * Reminder @@ -10,33 +12,24 @@ use Firefly\Database\SingleTableInheritanceEntity; * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property string $class - * @property integer $piggybank_id - * @property integer $recurring_transaction_id * @property integer $user_id * @property \Carbon\Carbon $startdate * @property \Carbon\Carbon $enddate * @property boolean $active - * @property-read \Piggybank $piggybank - * @property-read \RecurringTransaction $recurringTransaction * @property-read \User $user - * @method static \Illuminate\Database\Query\Builder|\Reminder whereId($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereCreatedAt($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereUpdatedAt($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereClass($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder wherePiggybankId($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereRecurringTransactionId($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereUserId($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereStartdate($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereEnddate($value) - * @method static \Illuminate\Database\Query\Builder|\Reminder whereActive($value) - * @method static \Reminder validOn($date) - * @method static \Reminder validOnOrAfter($date) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereId($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereCreatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereClass($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereStartdate($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereEnddate($value) + * @method static \Illuminate\Database\Query\Builder|\Reminder whereActive($value) */ -class Reminder extends SingleTableInheritanceEntity +class Reminder extends Ardent { protected $table = 'reminders'; - protected $subclassField = 'class'; /** @@ -47,49 +40,6 @@ class Reminder extends SingleTableInheritanceEntity return ['created_at', 'updated_at', 'startdate', 'enddate']; } - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function piggybank() - { - return $this->belongsTo('Piggybank'); - } - - public function recurringTransaction() - { - return $this->belongsTo('RecurringTransaction'); - } - - 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); - } - - public function scopeValidOnOrAfter($query, Carbon $date) - { - return $query->where( - function ($q) use ($date) { - $q->where('startdate', '<=', $date->format('Y-m-d'))->where( - 'enddate', '>=', $date->format('Y-m-d') - ); - $q->orWhere( - function ($q) use ($date) { - $q->where('startdate', '>=', $date); - $q->where('enddate', '>=', $date); - } - ); - } - )->where('active', 1); - } - /** * User * diff --git a/app/models/TransactionJournal.php b/app/models/TransactionJournal.php index 432ee0aa7f..41c8f79805 100644 --- a/app/models/TransactionJournal.php +++ b/app/models/TransactionJournal.php @@ -223,6 +223,22 @@ use LaravelBook\Ardent\Builder; * 'Budget[] $budgets * @property-read \Illuminate\Database\Eloquent\Collection|\ * 'Category[] $categories + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Category[] $categories + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Category[] $categories + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Category[] $categories + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\ + * 'Category[] $categories */ class TransactionJournal extends Ardent { diff --git a/app/routes.php b/app/routes.php index df3cfeced2..535b161678 100644 --- a/app/routes.php +++ b/app/routes.php @@ -170,6 +170,7 @@ Route::group(['before' => 'auth'], function () { Route::get('/chart/home/budgets', ['uses' => 'ChartController@homeBudgets', 'as' => 'chart.budgets']); Route::get('/chart/home/info/{accountnameA}/{day}/{month}/{year}', ['uses' => 'ChartController@homeAccountInfo', 'as' => 'chart.info']); Route::get('/chart/categories/show/{category}', ['uses' => 'ChartController@categoryShowChart','as' => 'chart.showcategory']); + Route::get('/chart/home/recurring', ['uses' => 'ChartController@homeRecurring', 'as' => 'chart.recurring']); // (new charts for budgets) Route::get('/chart/budget/{budget}/default', ['uses' => 'ChartController@budgetDefault', 'as' => 'chart.budget.default']); Route::get('chart/budget/{budget}/no_envelope', ['uses' => 'ChartController@budgetNoLimits', 'as' => 'chart.budget.nolimit']); diff --git a/app/views/index.blade.php b/app/views/index.blade.php index 353d5d703b..c9137158cc 100644 --- a/app/views/index.blade.php +++ b/app/views/index.blade.php @@ -20,7 +20,7 @@

-

Start from scratch

+

Start from scratch

Use this option if you are new to Firefly (III). @@ -29,9 +29,10 @@ @else - +

+
Your accounts @@ -40,6 +41,7 @@
+
Budgets and spending @@ -48,6 +50,7 @@
+
Categories @@ -55,8 +58,15 @@
-
+
+
+ Savings +
+
+ Bla bla +
+
@@ -65,6 +75,16 @@ @include('partials.date_nav') + +
+
+ Recurring transactions +
+
+
+
+
+ @foreach($transactions as $data)
diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index 26e245a0e9..8d34f0a355 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -17,7 +17,7 @@ $(function () { allowDecimals: false, labels: { formatter: function () { - if(this.value >= 1000 || this.value <= -1000) { + if (this.value >= 1000 || this.value <= -1000) { return '\u20AC ' + (this.value / 1000) + 'k'; } return '\u20AC ' + this.value; @@ -36,10 +36,10 @@ $(function () { text: null } }, - legend: {enabled:false}, + legend: {enabled: false}, tooltip: { formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y,2); + return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); } }, plotOptions: { @@ -101,7 +101,7 @@ $(function () { }, labels: { formatter: function () { - if(this.value >= 1000 || this.value <= -1000) { + if (this.value >= 1000 || this.value <= -1000) { return '\u20AC ' + (this.value / 1000) + 'k'; } return '\u20AC ' + this.value; @@ -165,17 +165,17 @@ $(function () { }, tooltip: { formatter: function () { - return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y,2); + return this.series.name + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); } }, yAxis: { min: 0, - title: {text:null}, + title: {text: null}, labels: { overflow: 'justify', formatter: function () { - if(this.value >= 1000 || this.value <= -1000) { + if (this.value >= 1000 || this.value <= -1000) { return '\u20AC ' + (this.value / 1000) + 'k'; } return '\u20AC ' + this.value; @@ -188,7 +188,7 @@ $(function () { cursor: 'pointer', events: { click: function (e) { - if(e.point.url != null) { + if (e.point.url != null) { window.location = e.point.url; } } @@ -202,7 +202,7 @@ $(function () { } }, legend: { - enabled: false, + enabled: false }, credits: { enabled: false @@ -211,5 +211,25 @@ $(function () { }); }); + $.getJSON('chart/home/recurring').success(function (data) { + $('#recurring').highcharts({ + title: { + text: null + }, + credits: { + enabled: false + }, + plotOptions: { + pie: { + allowPointSelect: true, + cursor: 'pointer', + dataLabels: { + enabled: false + } + } + }, + series: data + }); + }); }); \ No newline at end of file From 733b6d7eb7a0cca41d7c227d2db58ea832a15a43 Mon Sep 17 00:00:00 2001 From: Sander Dorigo Date: Mon, 13 Oct 2014 18:50:37 +0200 Subject: [PATCH 15/15] Expand the chart. --- app/controllers/ChartController.php | 59 +++++++++++-------- .../Helper/Toolkit/ToolkitInterface.php | 9 +++ public/assets/javascript/firefly/index.js | 17 ++++++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/app/controllers/ChartController.php b/app/controllers/ChartController.php index 546972335d..9b104ca379 100644 --- a/app/controllers/ChartController.php +++ b/app/controllers/ChartController.php @@ -495,17 +495,23 @@ class ChartController extends BaseController public function homeRecurring() { /** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */ - $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); - $recurringTransactions = \Auth::user()->recurringtransactions()->get(); - $sessionStart = \Session::get('start'); - $sessionEnd = \Session::get('end'); - $paid = []; - $unpaid = []; + $toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface'); + + /* + * Set of paid transaction journals. + * Set of unpaid recurring transactions. + */ + $paid = []; + $unpaid = []; + + /* + * Loop the recurring transactions. + */ /** @var \RecurringTransaction $recurring */ - foreach ($recurringTransactions as $recurring) { + foreach (\Auth::user()->recurringtransactions()->get() as $recurring) { /* - * Start a loop starting at the $date. + * Start another loop starting at the $date. */ $start = clone $recurring->date; $end = Carbon::now(); @@ -515,38 +521,30 @@ class ChartController extends BaseController */ $current = clone $start; - \Log::debug('Now looping recurring transaction #' . $recurring->id . ': ' . $recurring->name); - while ($current <= $end) { /* * Get end of period for $current: */ $currentEnd = clone $current; $toolkit->endOfPeriod($currentEnd, $recurring->repeat_freq); - \Log::debug('Now at $current: ' . $current->format('D d F Y') . ' - ' . $currentEnd->format('D d F Y')); /* * In the current session range? */ - if ($sessionEnd >= $current and $currentEnd >= $sessionStart) { + if (\Session::get('end') >= $current and $currentEnd >= \Session::get('start')) { /* * Lets see if we've already spent money on this recurring transaction (it hath recurred). */ - /** @var Collection $set */ - $set = \Auth::user()->transactionjournals()->where('recurring_transaction_id', $recurring->id) - ->after($current)->before($currentEnd)->get(); - if (count($set) > 1) { - \Log::error('Recurring #' . $recurring->id . ', dates [' . $current . ',' . $currentEnd . ']. Found multiple hits. Cannot handle this!'); - throw new FireflyException('Cannot handle multiple transactions. See logs.'); - } else if (count($set) == 0) { + /** @var TransactionJournal $set */ + $transaction = \Auth::user()->transactionjournals()->where('recurring_transaction_id', $recurring->id)->after($current)->before($currentEnd)->first(); + + if(is_null($transaction)) { $unpaid[] = $recurring; } else { - $paid[] = $set->get(0); + $paid[] = $transaction; } - } - /* * Add some time for the next loop! */ @@ -556,23 +554,32 @@ class ChartController extends BaseController } /* - * Loop paid and unpaid to make two haves for a pie chart. + * Get some colors going. */ $unPaidColours = $toolkit->colorRange('AA4643', 'FFFFFF', count($unpaid) == 0 ? 1 : count($unpaid)); $paidColours = $toolkit->colorRange('4572A7', 'FFFFFF', count($paid) == 0 ? 1 : count($paid)); + + /* + * The chart serie: + */ $serie = [ 'type' => 'pie', 'name' => 'Amount', 'data' => [] ]; + /* + * Loop paid and unpaid to make two haves for a pie chart. + */ /** @var TransactionJournal $entry */ foreach ($paid as $index => $entry) { $transactions = $entry->transactions()->get(); $amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount)); $serie['data'][] = [ - 'name' => $entry->description, + 'name' => 'Already paid "'.$entry->description.'"', 'y' => $amount, + 'url' => route('transactions.show',$entry->id), + 'objType' => 'paid', 'color' => $paidColours[$index] ]; } @@ -582,8 +589,10 @@ class ChartController extends BaseController foreach ($unpaid as $index => $entry) { $amount = (floatval($entry->amount_max) + floatval($entry->amount_min)) / 2; $serie['data'][] = [ - 'name' => $entry->name, + 'name' => 'Still due: '.$entry->name, 'y' => $amount, + 'url' => route('recurring.show',$entry->id), + 'objType' => 'unpaid', 'color' => $unPaidColours[$index] ]; } diff --git a/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php b/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php index 3c09e686c2..aa02c9bca9 100644 --- a/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php +++ b/app/lib/Firefly/Helper/Toolkit/ToolkitInterface.php @@ -2,6 +2,7 @@ namespace Firefly\Helper\Toolkit; +use Carbon\Carbon; use Illuminate\Support\Collection; /** @@ -40,4 +41,12 @@ interface ToolkitInterface */ public function colorRange($start, $end, $steps = 5); + /** + * @param Carbon $date + * @param $repeatFreq + * @param $skip + * @return Carbon + */ + public function addPeriod(Carbon $date, $repeatFreq, $skip); + } \ No newline at end of file diff --git a/public/assets/javascript/firefly/index.js b/public/assets/javascript/firefly/index.js index 8d34f0a355..0b8d96318a 100644 --- a/public/assets/javascript/firefly/index.js +++ b/public/assets/javascript/firefly/index.js @@ -219,8 +219,25 @@ $(function () { credits: { enabled: false }, + tooltip: { + formatter: function () { + if (this.point.objType == 'paid') { + return this.key + ': \u20AC ' + Highcharts.numberFormat(this.y, 2); + } else { + return this.key + ': ~\u20AC ' + Highcharts.numberFormat(this.y, 2); + } + + } + }, plotOptions: { pie: { + events: { + click: function (e) { + if (e.point.url != null) { + window.location = e.point.url; + } + } + }, allowPointSelect: true, cursor: 'pointer', dataLabels: {