diff --git a/app/Events/JournalDeleted.php b/app/Events/JournalDeleted.php new file mode 100644 index 0000000000..375f2268dc --- /dev/null +++ b/app/Events/JournalDeleted.php @@ -0,0 +1,21 @@ +accountType->type)) . ' "' . e($account->name) . '"'; + + return View::make('accounts.delete', compact('account', 'subTitle')); + } + + /** + * @param Account $account + * + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(Account $account, AccountRepositoryInterface $repository) + { + + $type = $account->accountType->type; + $typeName = Config::get('firefly.shortNamesByFullName.' . $type); + $name = $account->name; + + $repository->destroy($account); + + Session::flash('success', 'The ' . e($typeName) . ' account "' . e($name) . '" was deleted.'); + + return Redirect::route('accounts.index', $typeName); + } + + public function edit(Account $account, AccountRepositoryInterface $repository) + { + $what = Config::get('firefly.shortNamesByFullName')[$account->accountType->type]; + $subTitle = Config::get('firefly.subTitlesByIdentifier.' . $what); + $subTitleIcon = Config::get('firefly.subIconsByIdentifier.' . $what); + $openingBalance = $repository->openingBalanceTransaction($account); + + // pre fill some useful values. + + // the opening balance is tricky: + $openingBalanceAmount = null; + if ($openingBalance) { + $transaction = $openingBalance->transactions()->where('account_id', $account->id)->first(); + $openingBalanceAmount = $transaction->amount; + } + + $preFilled = [ + 'accountRole' => $account->getMeta('accountRole'), + 'openingBalanceDate' => $openingBalance ? $openingBalance->date->format('Y-m-d') : null, + 'openingBalance' => $openingBalanceAmount + ]; + Session::flash('preFilled', $preFilled); + + return view('accounts.edit', compact('account', 'subTitle', 'subTitleIcon', 'openingBalance', 'what')); + } + /** * @param string $what * @@ -84,4 +142,32 @@ class AccountController extends Controller } + /** + * @param Account $account + * @param AccountFormRequest $request + * @param AccountRepositoryInterface $repository + * + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Account $account, AccountFormRequest $request, AccountRepositoryInterface $repository) + { + $what = Config::get('firefly.shortNamesByFullName.' . $account->accountType->type); + $accountData = [ + 'name' => $request->input('name'), + 'active' => $request->input('active'), + 'user' => Auth::user()->id, + 'accountRole' => $request->input('accountRole'), + 'openingBalance' => floatval($request->input('openingBalance')), + 'openingBalanceDate' => new Carbon($request->input('openingBalanceDate')), + 'openingBalanceCurrency' => intval($request->input('balance_currency_id')), + ]; + + $repository->update($account, $accountData); + + Session::flash('success', 'New account "' . $account->name . '" stored!'); + + return Redirect::route('accounts.index', $what); + + } + } diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 7bedd3d057..5e614742d4 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -4,6 +4,8 @@ namespace FireflyIII\Http\Requests; use Auth; use Config; +use FireflyIII\Models\Account; +use Input; /** * Class AccountFormRequest @@ -29,8 +31,13 @@ class AccountFormRequest extends Request $accountRoles = join(',', array_keys(Config::get('firefly.accountRoles'))); $types = join(',', array_keys(Config::get('firefly.subTitlesByIdentifier'))); + $nameRule = 'required|between:1,100|uniqueForUser:accounts,name'; + if (Account::find(Input::get('id'))) { + $nameRule = 'required|between:1,100'; + } + return [ - 'name' => 'required|between:1,100|uniqueForUser:accounts,name', + 'name' => $nameRule, 'openingBalance' => 'numeric', 'openingBalanceDate' => 'date', 'accountRole' => 'in:' . $accountRoles, diff --git a/app/Http/routes.php b/app/Http/routes.php index c320aacbc5..a4df3e1317 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,4 +1,24 @@ where('account_types.editable', 1) + ->where('accounts.id', $value) + ->where('user_id', Auth::user()->id) + ->first(['accounts.*']); + if ($account) { + return $account; + } + } + App::abort(404); + } +); + /** * Home Controller @@ -18,9 +38,10 @@ Route::group( Route::get('/accounts/edit/{account}', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']); Route::get('/accounts/delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']); Route::get('/accounts/show/{account}/{view?}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); + Route::post('/accounts/store', ['uses' => 'AccountController@store', 'as' => 'accounts.store']); - // Route::post('/accounts/update/{account}', ['uses' => 'AccountController@update', 'as' => 'accounts.update']); - // Route::post('/accounts/destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'accounts.destroy']); + Route::post('/accounts/update/{account}', ['uses' => 'AccountController@update', 'as' => 'accounts.update']); + Route::post('/accounts/destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'accounts.destroy']); /** * Bills Controller diff --git a/app/Models/Account.php b/app/Models/Account.php index 4ea183adca..5bad563764 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -24,6 +24,23 @@ class Account extends Model protected $fillable = ['user_id', 'account_type_id', 'name', 'active']; + /** + * @param $fieldName + * + * @return string|null + */ + public function getMeta($fieldName) + { + foreach ($this->accountMeta as $meta) { + if ($meta->name == $fieldName) { + return $meta->data; + } + } + + return null; + + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index c6dbce3080..726327345f 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -1,6 +1,7 @@ 'required|exists:accounts,id', + 'name' => 'required|between:1,100', + 'data' => 'required' + ]; + protected $table = 'account_meta'; + + protected $fillable = ['account_id', 'name', 'data']; + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index fde880b91e..90af507dae 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -11,6 +11,7 @@ use Watson\Validating\ValidatingTrait; */ class Transaction extends Model { + protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount']; protected $rules = [ diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 4c1a4cef0e..ab2cf2abd9 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -188,4 +188,17 @@ class TransactionJournal extends Model return $this->belongsTo('FireflyIII\User'); } + /** + * @param EloquentBuilder $query + * @param Account $account + */ + public function scopeAccountIs(EloquentBuilder $query, Account $account) + { + if (!isset($this->joinedTransactions)) { + $query->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id'); + $this->joinedTransactions = true; + } + $query->where('transactions.account_id', $account->id); + } + } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5d16b0d436..21995a8501 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -1,5 +1,8 @@ [ + 'event.name' => [ 'EventListener', ], + 'App\Events\JournalDeleted' => [ + 'App\Handlers\Events\JournalDeletedHandler@handle', + ], ]; /** @@ -34,7 +40,27 @@ class EventServiceProvider extends ServiceProvider { parent::boot($events); - // + TransactionJournal::deleted( + function (TransactionJournal $journal) { + + /** @var Transaction $transaction */ + foreach ($journal->transactions()->get() as $transaction) { + $transaction->delete(); + } + } + ); + + Account::deleted( + function (Account $account) { + + /** @var Transaction $transaction */ + foreach ($account->transactions()->get() as $transaction) { + $journal = $transaction->transactionJournal()->first(); + $journal->delete(); + } + } + ); + } } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 5821035575..976a7bf73b 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -5,10 +5,12 @@ namespace FireflyIII\Repositories\Account; use App; use Config; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use Log; /** * Class AccountRepository @@ -18,6 +20,31 @@ use FireflyIII\Models\TransactionType; class AccountRepository implements AccountRepositoryInterface { + /** + * @param Account $account + * + * @return boolean + */ + public function destroy(Account $account) + { + $account->delete(); + + return true; + } + + /** + * @param Account $account + * + * @return TransactionJournal|null + */ + public function openingBalanceTransaction(Account $account) + { + return TransactionJournal::accountIs($account) + ->orderBy('transaction_journals.date', 'ASC') + ->orderBy('created_at', 'ASC') + ->first(['transaction_journals.*']); + } + /** * @param array $data * @@ -26,6 +53,7 @@ class AccountRepository implements AccountRepositoryInterface public function store(array $data) { $newAccount = $this->_store($data); + $this->_storeMetadata($newAccount, $data); // continue with the opposing account: @@ -46,6 +74,58 @@ class AccountRepository implements AccountRepositoryInterface } + /** + * @param Account $account + * @param array $data + */ + public function update(Account $account, array $data) + { + // update the account: + $account->name = $data['name']; + $account->active = $data['active'] == '1' ? true : false; + $account->save(); + + // update meta data: + /** @var AccountMeta $meta */ + foreach ($account->accountMeta()->get() as $meta) { + if ($meta->name == 'accountRole') { + $meta->data = $data['accountRole']; + $meta->save(); + } + } + + $openingBalance = $this->openingBalanceTransaction($account); + + // if has openingbalance? + if ($data['openingBalance'] != 0) { + // if opening balance, do an update: + if ($openingBalance) { + // update existing opening balance. + $this->_updateInitialBalance($account, $openingBalance, $data); + } else { + // create new opening balance. + $type = $data['openingBalance'] < 0 ? 'expense' : 'revenue'; + $opposingData = [ + 'user' => $data['user'], + 'accountType' => $type, + 'name' => $data['name'] . ' initial balance', + 'active' => false, + ]; + $opposing = $this->_store($opposingData); + $this->_storeInitialBalance($account, $opposing, $data); + } + + } else { + // opening balance is zero, should we delete it? + if ($openingBalance) { + // delete existing opening balance. + $openingBalance->delete(); + } + } + + return $account; + } + /** * @param array $data * @@ -64,13 +144,38 @@ class AccountRepository implements AccountRepositoryInterface ] ); if (!$newAccount->isValid()) { - App::abort(500); + // does the account already exist? + $existingAccount = Account::where('user_id', $data['user'])->where('account_type_id', $accountType->id)->where('name', $data['name'])->first(); + if (!$existingAccount) { + Log::error('Account create error: ' . $newAccount->getErrors()->toJson()); + var_dump($newAccount->getErrors()->toArray()); + } + $newAccount = $existingAccount; } $newAccount->save(); return $newAccount; } + /** + * @param Account $account + * @param array $data + */ + protected function _storeMetadata(Account $account, array $data) + { + $metaData = new AccountMeta( + [ + 'account_id' => $account->id, + 'name' => 'accountRole', + 'data' => $data['accountRole'] + ] + ); + if (!$metaData->isValid()) { + App::abort(500); + } + $metaData->save(); + } + /** * @param Account $account * @param Account $opposing @@ -143,4 +248,29 @@ class AccountRepository implements AccountRepositoryInterface } + /** + * @param Account $account + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + protected function _updateInitialBalance(Account $account, TransactionJournal $journal, array $data) + { + $journal->date = $data['openingBalanceDate']; + + /** @var Transaction $transaction */ + foreach ($journal->transactions()->get() as $transaction) { + if ($account->id == $transaction->account_id) { + $transaction->amount = $data['openingBalance']; + $transaction->save(); + } + if ($account->id != $transaction->account_id) { + $transaction->amount = $data['openingBalance'] * -1; + $transaction->save(); + } + } + + return $journal; + } } \ No newline at end of file diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index e3c0e41ff6..6e2e7897db 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -2,7 +2,8 @@ namespace FireflyIII\Repositories\Account; - +use FireflyIII\Models\Account; +use FireflyIII\Models\TransactionJournal; /** * Interface AccountRepositoryInterface * @@ -13,7 +14,31 @@ interface AccountRepositoryInterface /** * @param array $data * - * @return mixed + * @return Account */ public function store(array $data); + + /** + * @param Account $account + * + * @return boolean + */ + public function destroy(Account $account); + + /** + * @param Account $account + * @param array $data + * + * @return Account + */ + public function update(Account $account, array $data); + + /** + * @param Account $account + * + * @return TransactionJournal|null + */ + public function openingBalanceTransaction(Account $account); + + } \ No newline at end of file diff --git a/config/firefly.php b/config/firefly.php index 77f4e4b628..004b9803bf 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -70,10 +70,20 @@ return [ 'expense' => ['Expense account', 'Beneficiary account'], 'revenue' => ['Revenue account'], ], - 'accountTypeByIdentifier' => + 'accountTypeByIdentifier' => [ 'asset' => 'Asset account', 'expense' => 'Expense account', 'revenue' => 'Revenue account', ], + 'shortNamesByFullName' => + [ + 'Default account' => 'asset', + 'Asset account' => 'asset', + 'Expense account' => 'expense', + 'Beneficiary account' => 'expense', + 'Revenue account' => 'revenue', + 'Cash account' => 'cash', + ] + ]; diff --git a/resources/views/accounts/delete.blade.php b/resources/views/accounts/delete.blade.php new file mode 100644 index 0000000000..675d6682bc --- /dev/null +++ b/resources/views/accounts/delete.blade.php @@ -0,0 +1,33 @@ +@extends('layouts.default') +@section('content') +{{-- Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $account) --}} +{!! Form::open(['class' => 'form-horizontal','id' => 'destroy','url' => route('accounts.destroy',$account->id)]) !!} +
+
+
+
+ Delete account "{{{$account->name}}}" +
+
+

+ Are you sure? +

+ + @if($account->transactions()->count() > 0) +

+ Account "{{{$account->name}}}" still has {{$account->transactions()->count()}} transaction(s) associated to it. + These will be deleted as well. +

+ @endif + +

+ + Cancel +

+
+
+
+
+ +{!! Form::close() !!} +@stop diff --git a/resources/views/accounts/edit.blade.php b/resources/views/accounts/edit.blade.php new file mode 100644 index 0000000000..0b296380ba --- /dev/null +++ b/resources/views/accounts/edit.blade.php @@ -0,0 +1,51 @@ +@extends('layouts.default') +@section('content') +{{-- Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $account) --}} +{!! Form::model($account, ['class' => 'form-horizontal','id' => 'update','url' => route('accounts.update',$account->id)]) !!} +
+
+
+
+ Mandatory fields +
+
+ {!! ExpandedForm::text('name') !!} +
+
+

+ +

+
+
+
+
+ Optional fields +
+
+ @if($account->accounttype->type == 'Default account' || $account->accounttype->type == 'Asset account') + {!! ExpandedForm::balance('openingBalance',null, ['currency' => $openingBalance ? $openingBalance->transactionCurrency : null]) !!} + {!! ExpandedForm::date('openingBalanceDate') !!} + {!! ExpandedForm::select('accountRole',Config::get('firefly.accountRoles')) !!} + {!! Form::hidden('id',$account->id) !!} + @endif + {!! ExpandedForm::checkbox('active','1') !!} +
+
+ + +
+
+ Options +
+
+ {!! ExpandedForm::optionsList('update','account') !!} +
+
+ +
+
+ +{!! Form::close() !!} +@stop