From defad3d82057f2ace5f9039244ebcb192c62b8bf Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 25 Feb 2015 21:19:06 +0100 Subject: [PATCH] More code. --- .../Controllers/PreferencesController.php | 76 +++++++++ app/Http/Controllers/ProfileController.php | 90 ++++++++++ app/Http/Controllers/RelatedController.php | 156 ++++++++++++++++++ .../Controllers/TransactionController.php | 43 ++++- app/Http/Requests/ProfileFormRequest.php | 35 ++++ app/Http/breadcrumbs.php | 1 + app/Http/routes.php | 36 +++- .../Journal/JournalRepository.php | 54 ++++++ .../Journal/JournalRepositoryInterface.php | 8 + public/js/related-manager.js | 107 ++++++++++++ resources/views/preferences/index.blade.php | 103 ++++++++++++ .../views/profile/change-password.blade.php | 54 ++++++ resources/views/profile/index.blade.php | 18 ++ resources/views/related/relate.blade.php | 27 +++ resources/views/transactions/show.blade.php | 22 ++- 15 files changed, 821 insertions(+), 9 deletions(-) create mode 100644 app/Http/Controllers/PreferencesController.php create mode 100644 app/Http/Controllers/ProfileController.php create mode 100644 app/Http/Controllers/RelatedController.php create mode 100644 app/Http/Requests/ProfileFormRequest.php create mode 100644 public/js/related-manager.js create mode 100644 resources/views/preferences/index.blade.php create mode 100644 resources/views/profile/change-password.blade.php create mode 100644 resources/views/profile/index.blade.php create mode 100644 resources/views/related/relate.blade.php diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php new file mode 100644 index 0000000000..50548858c0 --- /dev/null +++ b/app/Http/Controllers/PreferencesController.php @@ -0,0 +1,76 @@ +accounts()->accountTypeIn(['Default account', 'Asset account'])->get(['accounts.*']); + $viewRange = Preferences::get('viewRange', '1M'); + $viewRangeValue = $viewRange->data; + $frontPage = Preferences::get('frontPageAccounts', []); + $budgetMax = Preferences::get('budgetMaximum', 1000); + $budgetMaximum = $budgetMax->data; + + return View::make('preferences.index', compact('budgetMaximum'))->with('accounts', $accounts)->with('frontPageAccounts', $frontPage)->with( + 'viewRange', $viewRangeValue + ); + } + + /** + * @return \Illuminate\Http\RedirectResponse + */ + public function postIndex() + { + // front page accounts + $frontPageAccounts = []; + foreach (Input::get('frontPageAccounts') as $id) { + $frontPageAccounts[] = intval($id); + } + Preferences::set('frontPageAccounts', $frontPageAccounts); + + // view range: + Preferences::set('viewRange', Input::get('viewRange')); + // forget session values: + Session::forget('start'); + Session::forget('end'); + Session::forget('range'); + + // budget maximum: + $budgetMaximum = intval(Input::get('budgetMaximum')); + Preferences::set('budgetMaximum', $budgetMaximum); + + + Session::flash('success', 'Preferences saved!'); + + return Redirect::route('preferences'); + } + +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100644 index 0000000000..e9e6daadef --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,90 @@ +with('title', Auth::user()->email)->with('subTitle', 'Change your password')->with( + 'mainTitleIcon', 'fa-user' + ); + } + + /** + * @return \Illuminate\View\View + * + */ + public function index() + { + return view('profile.index')->with('title', 'Profile')->with('subTitle', Auth::user()->email)->with('mainTitleIcon', 'fa-user'); + } + + /** + * @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View + */ + public function postChangePassword(ProfileFormRequest $request) + { + // old, new1, new2 + if (!Hash::check($request->get('current_password'), Auth::user()->password)) { + Session::flash('error', 'Invalid current password!'); + + return Redirect::route('change-password'); + } + $result = $this->_validatePassword($request->get('current_password'), $request->get('new_password'), $request->get('new_password_confirmation')); + if (!($result === true)) { + Session::flash('error', $result); + + return Redirect::route('change-password'); + } + + // update the user with the new password. + Auth::user()->password = $request->get('new_password'); + Auth::user()->save(); + + Session::flash('success', 'Password changed!'); + + return Redirect::route('profile'); + } + + /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * + * @param string $old + * @param string $new1 + * @param string $new2 + * + * @return string|bool + */ + protected function _validatePassword($old, $new1, $new2) + { + if (strlen($new1) == 0 || strlen($new2) == 0) { + return 'Do fill in a password!'; + + } + if ($new1 == $old) { + return 'The idea is to change your password.'; + } + + if ($new1 !== $new2) { + return 'New passwords do not match!'; + } + + return true; + + } +} diff --git a/app/Http/Controllers/RelatedController.php b/app/Http/Controllers/RelatedController.php new file mode 100644 index 0000000000..9ea88a1352 --- /dev/null +++ b/app/Http/Controllers/RelatedController.php @@ -0,0 +1,156 @@ +transactiongroups()->get() as $group) { + /** @var TransactionJournal $loopJournal */ + foreach ($group->transactionjournals()->get() as $loopJournal) { + if ($loopJournal->id != $journal->id) { + $ids[] = $loopJournal->id; + } + } + } + $unique = array_unique($ids); + if (count($unique) > 0) { + + $set = Auth::user()->transactionjournals()->whereIn('id', $unique)->get(); + $set->each( + function (TransactionJournal $journal) { + /** @var Transaction $t */ + foreach ($journal->transactions()->get() as $t) { + if ($t->amount > 0) { + $journal->amount = $t->amount; + } + } + + } + ); + + return Response::json($set->toArray()); + } else { + return Response::json((new Collection)->toArray()); + } + } + + /** + * @param TransactionJournal $parentJournal + * @param TransactionJournal $childJournal + * + * @return \Illuminate\Http\JsonResponse + */ + public function relate(TransactionJournal $parentJournal, TransactionJournal $childJournal) + { + $group = new TransactionGroup; + $group->relation = 'balance'; + $group->user_id = Auth::user()->id; + $group->save(); + $group->transactionjournals()->save($parentJournal); + $group->transactionjournals()->save($childJournal); + + return Response::json(true); + + } + + /** + * @param TransactionJournal $journal + * + * @return \Illuminate\View\View + */ + public function related(TransactionJournal $journal) + { + $groups = $journal->transactiongroups()->get(); + $members = new Collection; + /** @var TransactionGroup $group */ + foreach ($groups as $group) { + /** @var TransactionJournal $loopJournal */ + foreach ($group->transactionjournals()->get() as $loopJournal) { + if ($loopJournal->id != $journal->id) { + $members->push($loopJournal); + } + } + } + + return view('related.relate', compact('journal', 'members')); + } + + /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * + * @param TransactionJournal $parentJournal + * @param TransactionJournal $childJournal + * + * @return \Illuminate\Http\JsonResponse + * @throws Exception + */ + public function removeRelation(TransactionJournal $parentJournal, TransactionJournal $childJournal) + { + $groups = $parentJournal->transactiongroups()->get(); + /** @var TransactionGroup $group */ + foreach ($groups as $group) { + foreach ($group->transactionjournals()->get() as $loopJournal) { + if ($loopJournal->id == $childJournal->id) { + // remove from group: + $group->transactionjournals()->detach($childJournal); + } + } + if ($group->transactionjournals()->count() == 1) { + $group->delete(); + } + } + + return Response::json(true); + } + + /** + * @param TransactionJournal $journal + * + * @return \Illuminate\Http\JsonResponse + */ + public function search(TransactionJournal $journal, JournalRepositoryInterface $repository) + { + + $search = e(trim(Input::get('searchValue'))); + + $result = $repository->searchRelated($search, $journal); + $result->each( + function (TransactionJournal $journal) { + /** @var Transaction $t */ + foreach ($journal->transactions()->get() as $t) { + if ($t->amount > 0) { + $journal->amount = $t->amount; + } + } + } + ); + + return Response::json($result->toArray()); + } + +} diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 1757e9de8b..6c1d6893ad 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -5,8 +5,11 @@ use Carbon\Carbon; use ExpandedForm; use FireflyIII\Http\Requests; use FireflyIII\Http\Requests\JournalFormRequest; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use Input; use Redirect; use Session; @@ -105,6 +108,42 @@ class TransactionController extends Controller } + + /** + * @param TransactionJournal $journal + * + * @return $this + */ + public function show(TransactionJournal $journal) + { + $journal->transactions->each( + function (Transaction $t) use ($journal) { + $t->before = floatval( + $t->account->transactions()->leftJoin( + 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' + )->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))->where( + 'transaction_journals.created_at', '<=', $journal->created_at->format('Y-m-d H:i:s') + )->where('transaction_journals.id', '!=', $journal->id)->sum('transactions.amount') + ); + $t->after = $t->before + $t->amount; + } + ); + $members = new Collection; + /** @var TransactionGroup $group */ + foreach ($journal->transactiongroups()->get() as $group) { + /** @var TransactionJournal $loopJournal */ + foreach ($group->transactionjournals()->get() as $loopJournal) { + if ($loopJournal->id != $journal->id) { + $members->push($loopJournal); + } + } + } + + return view('transactions.show', compact('journal', 'members'))->with('subTitle', e($journal->transactiontype->type) . ' "' . e($journal->description) . '"' + ); + } + + public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) { @@ -112,8 +151,8 @@ class TransactionController extends Controller 'what' => $request->get('what'), 'description' => $request->get('description'), 'account_id' => intval($request->get('account_id')), - 'account_from_id' => intval($request->get('account_from_id')), - 'account_to_id' => intval($request->get('account_to_id')), + 'account_from_id' => intval($request->get('account_from_id')), + 'account_to_id' => intval($request->get('account_to_id')), 'expense_account' => $request->get('expense_account'), 'revenue_account' => $request->get('revenue_account'), 'amount' => floatval($request->get('amount')), diff --git a/app/Http/Requests/ProfileFormRequest.php b/app/Http/Requests/ProfileFormRequest.php new file mode 100644 index 0000000000..2b337a1037 --- /dev/null +++ b/app/Http/Requests/ProfileFormRequest.php @@ -0,0 +1,35 @@ + 'required', + 'new_password' => 'required|confirmed', + 'new_password_confirmation' => 'required', + ]; + } +} \ No newline at end of file diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 1f6c932318..e4f73da2e3 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -4,6 +4,7 @@ use DaveJamesMiller\Breadcrumbs\Generator; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Budget; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Bill; use FireflyIII\Models\Category; use FireflyIII\Models\LimitRepetition; diff --git a/app/Http/routes.php b/app/Http/routes.php index 125445694d..8cac9b0be4 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -5,6 +5,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\PiggyBank; @@ -26,6 +27,28 @@ Route::bind( } ); +Route::bind( + 'tjSecond', function ($value, $route) { + if (Auth::check()) { + return TransactionJournal:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + + return null; +} +); + +Route::bind( + 'tj', function ($value, $route) { + if (Auth::check()) { + return TransactionJournal:: + where('id', $value)->where('user_id', Auth::user()->id)->first(); + } + + return null; +} +); + Route::bind( 'currency', function ($value, $route) { return TransactionCurrency::find($value); @@ -219,12 +242,23 @@ Route::group( * Preferences Controller */ Route::get('/preferences', ['uses' => 'PreferencesController@index', 'as' => 'preferences']); + Route::post('/preferences', ['uses' => 'PreferencesController@postIndex']); /** * Profile Controller */ Route::get('/profile', ['uses' => 'ProfileController@index', 'as' => 'profile']); - //Route::get('/profile/change-password', ['uses' => 'ProfileController@changePassword', 'as' => 'change-password']); + Route::get('/profile/change-password', ['uses' => 'ProfileController@changePassword', 'as' => 'change-password']); + Route::post('/profile/change-password', ['uses' => 'ProfileController@postChangePassword','as' => 'change-password-post']); + + /** + * Related transactions controller + */ + Route::get('/related/alreadyRelated/{tj}', ['uses' => 'RelatedController@alreadyRelated', 'as' => 'related.alreadyRelated']); + Route::post('/related/relate/{tj}/{tjSecond}', ['uses' => 'RelatedController@relate', 'as' => 'related.relate']); + Route::post('/related/removeRelation/{tj}/{tjSecond}', ['uses' => 'RelatedController@removeRelation', 'as' => 'related.removeRelation']); + Route::get('/related/related/{tj}', ['uses' => 'RelatedController@related', 'as' => 'related.related']); + Route::post('/related/search/{tj}', ['uses' => 'RelatedController@search', 'as' => 'related.search']); /** * Repeated Expenses Controller diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 5e7cefbcb9..0adb8f3ffb 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -9,6 +9,8 @@ use FireflyIII\Models\Category; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use Illuminate\Support\Collection; +use Auth; /** * Class JournalRepository @@ -18,6 +20,58 @@ use FireflyIII\Models\TransactionType; class JournalRepository implements JournalRepositoryInterface { + /** + * @param string $query + * @param TransactionJournal $journal + * + * @return Collection + */ + public function searchRelated($query, TransactionJournal $journal) + { + $start = clone $journal->date; + $end = clone $journal->date; + $start->startOfMonth(); + $end->endOfMonth(); + + // get already related transactions: + $exclude = [$journal->id]; + foreach ($journal->transactiongroups()->get() as $group) { + foreach ($group->transactionjournals()->get() as $current) { + $exclude[] = $current->id; + } + } + $exclude = array_unique($exclude); + + /** @var Collection $collection */ + $collection = Auth::user()->transactionjournals() + ->withRelevantData() + ->before($end)->after($start)->where('encrypted', 0) + ->whereNotIn('id', $exclude) + ->where('description', 'LIKE', '%' . $query . '%') + ->get(); + + // manually search encrypted entries: + /** @var Collection $encryptedCollection */ + $encryptedCollection = Auth::user()->transactionjournals() + ->withRelevantData() + ->before($end)->after($start) + ->where('encrypted', 1) + ->whereNotIn('id', $exclude) + ->get(); + $encrypted = $encryptedCollection->filter( + function (TransactionJournal $journal) use ($query) { + $strPos = strpos(strtolower($journal->description), strtolower($query)); + if ($strPos !== false) { + return $journal; + } + + return null; + } + ); + + return $collection->merge($encrypted); + } + /** * @param array $data * diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 65106022eb..50984df4b8 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -3,6 +3,7 @@ namespace FireflyIII\Repositories\Journal; use FireflyIII\Models\TransactionJournal; +use Illuminate\Support\Collection; /** * Interface JournalRepositoryInterface @@ -18,4 +19,11 @@ interface JournalRepositoryInterface */ public function store(array $data); + /** + * @param string $query + * @param TransactionJournal $journal + * + * @return Collection + */ + public function searchRelated($query, TransactionJournal $journal); } \ No newline at end of file diff --git a/public/js/related-manager.js b/public/js/related-manager.js new file mode 100644 index 0000000000..55cdd19431 --- /dev/null +++ b/public/js/related-manager.js @@ -0,0 +1,107 @@ +$(document).ready(function () { + $('.relateTransaction').click(relateTransaction); + $('.unrelate-checkbox').click(unrelateTransaction); + +}); + +function unrelateTransaction(e) { + var target = $(e.target); + var id = target.data('id'); + var relatedTo = target.data('relatedto'); + + $.post('related/removeRelation/' + id + '/' + relatedTo, {_token:token}).success(function (data) { + target.parent().parent().remove(); + }).fail(function () { + alert('Could not!'); + }); + +} + +function relateTransaction(e) { + var target = $(e.target); + var ID = target.data('id'); + + + $('#relationModal').empty().load('related/related/' + ID, function () { + + $('#relationModal').modal('show'); + getAlreadyRelatedTransactions(e, ID); + $('#searchRelated').submit(function (e) { + searchRelatedTransactions(e, ID); + + return false; + }); + }); + + + return false; +} + + +function searchRelatedTransactions(e, ID) { + var searchValue = $('#relatedSearchValue').val(); + if (searchValue != '') { + $.post('related/search/' + ID, {searchValue: searchValue,_token:token}).success(function (data) { + // post each result to some div. + $('#relatedSearchResults').empty(); + + $.each(data, function (i, row) { + var tr = $(''); + + var checkBox = $('').append($('').attr('type', 'checkbox').data('relateto', ID).data('id', row.id).click(doRelateNewTransaction)); + var description = $('').text(row.description); + var amount = $('').html(row.amount); + tr.append(checkBox).append(description).append(amount); + $('#relatedSearchResults').append(tr); + //$('#relatedSearchResults').append($('
').text(row.id)); + }); + + + }).fail(function () { + alert('Could not search. Sorry.'); + }); + } + + return false; +} + +function doRelateNewTransaction(e) { + // remove the row from the table: + var target = $(e.target); + var id = target.data('id'); + var relateToId = target.data('relateto'); + if (!target.checked) { + var relateID = target.data('id'); + $.post('related/relate/' + id + '/' + relateToId,{_token:token}).success(function (data) { + // success! + target.parent().parent().remove(); + getAlreadyRelatedTransactions(null, relateToId); + }).fail(function () { + // could not relate. + alert('Error!'); + }); + + + } else { + alert('remove again!'); + } +} + +function getAlreadyRelatedTransactions(e, ID) { + //#alreadyRelated + $.get('related/alreadyRelated/' + ID).success(function (data) { + $('#alreadyRelated').empty(); + $.each(data, function (i, row) { + var tr = $(''); + + var checkBox = $('').append($('').attr('type', 'checkbox').data('relateto', ID).data('id', row.id).click(doRelateNewTransaction)); + var description = $('').text(row.description); + var amount = $('').html(row.amount); + tr.append(checkBox).append(description).append(amount); + $('#alreadyRelated').append(tr); + //$('#relatedSearchResults').append($('
').text(row.id)); + }); + }).fail(function () { + alert('Cannot get related stuff.'); + }); +} \ No newline at end of file diff --git a/resources/views/preferences/index.blade.php b/resources/views/preferences/index.blade.php new file mode 100644 index 0000000000..6f4c55e69d --- /dev/null +++ b/resources/views/preferences/index.blade.php @@ -0,0 +1,103 @@ +@extends('layouts.default') +@section('content') +{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!} + +{!! Form::open(['class' => 'form-horizontal','id' => 'preferences']) !!} + +
+
+
+
+ Home screen accounts +
+
+

Which accounts should be displayed on the home page?

+ @foreach($accounts as $account) +
+
+
+ +
+
+
+ @endforeach +
+
+
+
+ Budget settings +
+
+

+ What's the maximum amount of money a budget envelope may contain? +

+ {!! ExpandedForm::amount('budgetMaximum',$budgetMaximum,['label' => 'Budget maximum']) !!} +
+
+ +
+
+
+
+ Home view range +
+
+

By default, Firefly will show you one month of data.

+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +{!! Form::close() !!} + +@stop diff --git a/resources/views/profile/change-password.blade.php b/resources/views/profile/change-password.blade.php new file mode 100644 index 0000000000..e78a9dce3c --- /dev/null +++ b/resources/views/profile/change-password.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.default') +@section('content') +{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!} +
+
+
+
+ Change your password +
+
+ + @if($errors->count() > 0) +
    + @foreach($errors->all() as $err) +
  • {{$err}}
  • + @endforeach +
+ + @endif + + {!! Form::open(['class' => 'form-horizontal','id' => 'change-password']) !!} +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+
+ {!! Form::close() !!} +
+
+
+
+@stop +@section('scripts') +@stop diff --git a/resources/views/profile/index.blade.php b/resources/views/profile/index.blade.php new file mode 100644 index 0000000000..e6eab98606 --- /dev/null +++ b/resources/views/profile/index.blade.php @@ -0,0 +1,18 @@ +@extends('layouts.default') +@section('content') +{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!} +
+
+
+
+ Options +
+ +
+
+
+@stop +@section('scripts') +@stop diff --git a/resources/views/related/relate.blade.php b/resources/views/related/relate.blade.php new file mode 100644 index 0000000000..d3cb6d62d3 --- /dev/null +++ b/resources/views/related/relate.blade.php @@ -0,0 +1,27 @@ + diff --git a/resources/views/transactions/show.blade.php b/resources/views/transactions/show.blade.php index 2bdc086cf1..345f9b8b0d 100644 --- a/resources/views/transactions/show.blade.php +++ b/resources/views/transactions/show.blade.php @@ -1,6 +1,6 @@ @extends('layouts.default') @section('content') -{{ Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $journal) }} +{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $journal) !!}
@@ -74,7 +74,14 @@ {{{$jrnl->description}}} - {{Amount::formatJournal($jrnl, $jrnl->getAmount())}} + + + @foreach($jrnl->transactions()->get() as $t) + @if($t->amount > 0) + {!! Amount::formatTransaction($t) !!} + @endif + @endforeach + @endforeach @@ -97,11 +104,11 @@ - + - + @if(!is_null($t->description)) @@ -126,6 +133,9 @@ @stop @section('scripts') -{{HTML::script('assets/javascript/firefly/transactions.js')}} -{{HTML::script('assets/javascript/firefly/related-manager.js')}} + + + @stop
Amount{{Amount::formatTransaction($t)}}{!! Amount::formatTransaction($t) !!}
New balance{{Amount::format($t->before)}} → {{Amount::format($t->after)}}{!! Amount::format($t->before) !!} → {!! Amount::format($t->after) !!}