diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index cf6a6458f3..c9b73119d9 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -5,6 +5,8 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI; /** * Class AccountController + * + * @SuppressWarnings(PHPMD.CamelCasePropertyName) */ class AccountController extends \BaseController { @@ -33,41 +35,23 @@ class AccountController extends \BaseController /** * @param Account $account * - * @return \Illuminate\View\View + * @return $this */ public function delete(Account $account) { - $accountType = $account->accountType()->first(); - - if ($accountType->description == 'Initial balance account' || $accountType->description == 'Cash account') { - return \View::make('error')->with( - 'message', 'Cannot edit this account type (' . $accountType->description . ').' - ); - } - return View::make('accounts.delete')->with('account', $account); } /** * @param Account $account * - * @return \Illuminate\Http\RedirectResponse + * @return $this|\Illuminate\Http\RedirectResponse */ public function destroy(Account $account) { - $accountType = $account->accountType()->first(); - if ($accountType->description == 'Initial balance account' || $accountType->description == 'Cash account') { - return View::make('error')->with( - 'message', 'Cannot edit this account type (' . $accountType->description . ').' - ); - } - $result = $this->_repository->destroy($account); - if ($result === true) { - Session::flash('success', 'The account was deleted.'); - } else { - Session::flash('error', 'Could not delete the account.'); - } + $this->_repository->destroy($account); + Session::flash('success', 'The account was deleted.'); return Redirect::route('accounts.index'); @@ -76,54 +60,52 @@ class AccountController extends \BaseController /** * @param Account $account * - * @return \Illuminate\View\View + * @return $this */ public function edit(Account $account) { - $accountType = $account->accountType()->first(); - - if ($accountType->description == 'Initial balance account' || $accountType->description == 'Cash account') { - return View::make('error')->with( - 'message', 'Cannot edit this account type (' . $accountType->description . ').' - ); - } $openingBalance = $this->_accounts->openingBalanceTransaction($account); - return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance); } /** - * @return \Illuminate\View\View + * @return $this */ public function index() { $accounts = $this->_repository->get(); - $display = $this->_accounts->index($accounts); + $set = [ + 'personal' => [], + 'beneficiaries' => [] + ]; + foreach ($accounts as $account) { + switch ($account->accounttype->type) { + case 'Default account': + $set['personal'][] = $account; + break; + case 'Beneficiary account': + $set['beneficiaries'][] = $account; + break; + } + } - return View::make('accounts.index')->with('accounts', $display); + return View::make('accounts.index')->with('accounts', $set); } /** * @param Account $account * - * @return \Illuminate\View\View + * @return $this */ public function show(Account $account) { - $accountType = $account->accountType()->first(); - if ($accountType->description == 'Initial balance account' || $accountType->description == 'Cash account') { - return View::make('error')->with( - 'message', 'Cannot show this account type (' . $accountType->description . ').' - ); - } + $data = $this->_accounts->show($account, 40); - $show = $this->_accounts->show($account, 40); - - return View::make('accounts.show')->with('account', $account)->with('show', $show); + return View::make('accounts.show')->with('account', $account)->with('show', $data); } /** - * @return \Illuminate\Http\RedirectResponse + * @return $this|\Illuminate\Http\RedirectResponse */ public function store() { @@ -133,14 +115,14 @@ class AccountController extends \BaseController if ($account->validate()) { // saved! return to wherever. Session::flash('success', 'Account "' . $account->name . '" created!'); - if (Input::get('create') == '1') { + if (intval(Input::get('create')) === 1) { return Redirect::route('accounts.create')->withInput(); } else { return Redirect::route('accounts.index'); } } else { // did not save, return with error: - Session::flash('error', 'Could not save the new account. Please check the form.'); + Session::flash('error', 'Could not save the new account: ' . $account->errors()->first()); return Redirect::route('accounts.create')->withErrors($account->errors())->withInput(); @@ -150,16 +132,10 @@ class AccountController extends \BaseController /** * @param Account $account * - * @return \Illuminate\Http\RedirectResponse + * @return $this|\Illuminate\Http\RedirectResponse */ public function update(Account $account) { - $accountType = $account->accountType()->first(); - if ($accountType->description == 'Initial balance account' || $accountType->description == 'Cash account') { - return View::make('error')->with( - 'message', 'Cannot show this account type (' . $accountType->description . ').' - ); - } $account = $this->_repository->update($account, Input::all()); if ($account->validate()) { Session::flash('success', 'Account "' . $account->name . '" updated.'); diff --git a/app/database/migrations/2014_06_27_163145_create_account_types_table.php b/app/database/migrations/2014_06_27_163145_create_account_types_table.php index 68816c33ec..a20432e87d 100644 --- a/app/database/migrations/2014_06_27_163145_create_account_types_table.php +++ b/app/database/migrations/2014_06_27_163145_create_account_types_table.php @@ -22,7 +22,8 @@ class CreateAccountTypesTable extends Migration 'account_types', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); - $table->string('description', 50); + $table->string('type', 50); + $table->boolean('editable'); } ); } diff --git a/app/database/seeds/AccountTypeSeeder.php b/app/database/seeds/AccountTypeSeeder.php index 8813320dde..55e637c0c0 100644 --- a/app/database/seeds/AccountTypeSeeder.php +++ b/app/database/seeds/AccountTypeSeeder.php @@ -11,16 +11,16 @@ class AccountTypeSeeder extends Seeder DB::table('account_types')->delete(); AccountType::create( - ['description' => 'Default account'] + ['type' => 'Default account','editable' => true] ); AccountType::create( - ['description' => 'Cash account'] + ['type' => 'Cash account','editable' => false] ); AccountType::create( - ['description' => 'Initial balance account'] + ['type' => 'Initial balance account','editable' => false] ); AccountType::create( - ['description' => 'Beneficiary account'] + ['type' => 'Beneficiary account','editable' => true] ); } diff --git a/app/lang/en/validation.php b/app/lang/en/validation.php index b03b926c8f..4c17703856 100644 --- a/app/lang/en/validation.php +++ b/app/lang/en/validation.php @@ -1,104 +1,105 @@ "The :attribute must be accepted.", - "active_url" => "The :attribute is not a valid URL.", - "after" => "The :attribute must be a date after :date.", - "alpha" => "The :attribute may only contain letters.", - "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", - "alpha_num" => "The :attribute may only contain letters and numbers.", - "array" => "The :attribute must be an array.", - "before" => "The :attribute must be a date before :date.", - "between" => array( - "numeric" => "The :attribute must be between :min and :max.", - "file" => "The :attribute must be between :min and :max kilobytes.", - "string" => "The :attribute must be between :min and :max characters.", - "array" => "The :attribute must have between :min and :max items.", - ), - "confirmed" => "The :attribute confirmation does not match.", - "date" => "The :attribute is not a valid date.", - "date_format" => "The :attribute does not match the format :format.", - "different" => "The :attribute and :other must be different.", - "digits" => "The :attribute must be :digits digits.", - "digits_between" => "The :attribute must be between :min and :max digits.", - "email" => "The :attribute must be a valid email address.", - "exists" => "The selected :attribute is invalid.", - "image" => "The :attribute must be an image.", - "in" => "The selected :attribute is invalid.", - "integer" => "The :attribute must be an integer.", - "ip" => "The :attribute must be a valid IP address.", - "max" => array( - "numeric" => "The :attribute may not be greater than :max.", - "file" => "The :attribute may not be greater than :max kilobytes.", - "string" => "The :attribute may not be greater than :max characters.", - "array" => "The :attribute may not have more than :max items.", - ), - "mimes" => "The :attribute must be a file of type: :values.", - "min" => array( - "numeric" => "The :attribute must be at least :min.", - "file" => "The :attribute must be at least :min kilobytes.", - "string" => "The :attribute must be at least :min characters.", - "array" => "The :attribute must have at least :min items.", - ), - "not_in" => "The selected :attribute is invalid.", - "numeric" => "The :attribute must be a number.", - "regex" => "The :attribute format is invalid.", - "required" => "The :attribute field is required.", - "required_if" => "The :attribute field is required when :other is :value.", - "required_with" => "The :attribute field is required when :values is present.", - "required_with_all" => "The :attribute field is required when :values is present.", - "required_without" => "The :attribute field is required when :values is not present.", - "required_without_all" => "The :attribute field is required when none of :values are present.", - "same" => "The :attribute and :other must match.", - "size" => array( - "numeric" => "The :attribute must be :size.", - "file" => "The :attribute must be :size kilobytes.", - "string" => "The :attribute must be :size characters.", - "array" => "The :attribute must contain :size items.", - ), - "unique" => "The :attribute has already been taken.", - "url" => "The :attribute format is invalid.", + "accepted" => "The :attribute must be accepted.", + "active_url" => "The :attribute is not a valid URL.", + "after" => "The :attribute must be a date after :date.", + "alpha" => "The :attribute may only contain letters.", + "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", + "alpha_num" => "The :attribute may only contain letters and numbers.", + "array" => "The :attribute must be an array.", + "before" => "The :attribute must be a date before :date.", + "between" => array( + "numeric" => "The :attribute must be between :min and :max.", + "file" => "The :attribute must be between :min and :max kilobytes.", + "string" => "The :attribute must be between :min and :max characters.", + "array" => "The :attribute must have between :min and :max items.", + ), + "confirmed" => "The :attribute confirmation does not match.", + "date" => "The :attribute is not a valid date.", + "date_format" => "The :attribute does not match the format :format.", + "different" => "The :attribute and :other must be different.", + "digits" => "The :attribute must be :digits digits.", + "digits_between" => "The :attribute must be between :min and :max digits.", + "email" => "The :attribute must be a valid email address.", + "exists" => "The selected :attribute is invalid.", + "image" => "The :attribute must be an image.", + "in" => "The selected :attribute is invalid.", + "integer" => "The :attribute must be an integer.", + "ip" => "The :attribute must be a valid IP address.", + "max" => array( + "numeric" => "The :attribute may not be greater than :max.", + "file" => "The :attribute may not be greater than :max kilobytes.", + "string" => "The :attribute may not be greater than :max characters.", + "array" => "The :attribute may not have more than :max items.", + ), + "mimes" => "The :attribute must be a file of type: :values.", + "min" => array( + "numeric" => "The :attribute must be at least :min.", + "file" => "The :attribute must be at least :min kilobytes.", + "string" => "The :attribute must be at least :min characters.", + "array" => "The :attribute must have at least :min items.", + ), + "not_in" => "The selected :attribute is invalid.", + "numeric" => "The :attribute must be a number.", + "regex" => "The :attribute format is invalid.", + "required" => "The :attribute field is required.", + "required_if" => "The :attribute field is required when :other is :value.", + "required_with" => "The :attribute field is required when :values is present.", + "required_with_all" => "The :attribute field is required when :values is present.", + "required_without" => "The :attribute field is required when :values is not present.", + "required_without_all" => "The :attribute field is required when none of :values are present.", + "same" => "The :attribute and :other must match.", + "size" => array( + "numeric" => "The :attribute must be :size.", + "file" => "The :attribute must be :size kilobytes.", + "string" => "The :attribute must be :size characters.", + "array" => "The :attribute must contain :size items.", + ), + "unique" => "The :attribute has already been taken.", + "url" => "The :attribute format is invalid.", - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ - 'custom' => array( - 'attribute-name' => array( - 'rule-name' => 'custom-message', - ), - ), + 'custom' => array( + 'attribute-name' => array( + 'rule-name' => 'custom-message', + ), + ), + 'alphabasic' => 'The :attribute field must consist of basic alphanumeric characters.', - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ - 'attributes' => array(), + 'attributes' => array(), -); +]; diff --git a/app/lib/Firefly/Helper/Controllers/Account.php b/app/lib/Firefly/Helper/Controllers/Account.php index 55c2cd6a28..3d0787f4ff 100644 --- a/app/lib/Firefly/Helper/Controllers/Account.php +++ b/app/lib/Firefly/Helper/Controllers/Account.php @@ -2,7 +2,7 @@ namespace Firefly\Helper\Controllers; -use Illuminate\Database\Eloquent\Collection; +use Firefly\Exception\FireflyException; /** * Class Account @@ -11,43 +11,6 @@ use Illuminate\Database\Eloquent\Collection; */ class Account implements AccountInterface { - /** - * @param Collection $accounts - * - * @return array|mixed - */ - public function index(Collection $accounts) - { - - $list = [ - 'personal' => [], - 'beneficiaries' => [], - 'initial' => [], - 'cash' => [] - ]; - foreach ($accounts as $account) { - - switch ($account->accounttype->description) { - case 'Default account': - $list['personal'][] = $account; - break; - case 'Cash account': - $list['cash'][] = $account; - break; - case 'Initial balance account': - $list['initial'][] = $account; - break; - case 'Beneficiary account': - $list['beneficiaries'][] = $account; - break; - - } - } - - return $list; - - } - /** * @param \Account $account * @@ -55,17 +18,12 @@ class Account implements AccountInterface */ public function openingBalanceTransaction(\Account $account) { - $transactionType = \TransactionType::where('type', 'Opening balance')->first(); - return \TransactionJournal:: - with( - ['transactions' => function ($q) { - $q->orderBy('amount', 'ASC'); - }] - )->where('transaction_type_id', $transactionType->id) - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $account->id)->first(['transaction_journals.*']); - + withRelevantData()->account($account) + ->leftJoin('transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id') + ->where('transaction_types.type', 'Opening balance') + ->first(['transaction_journals.*']); } /** @@ -77,7 +35,7 @@ class Account implements AccountInterface public function show(\Account $account, $perPage) { $start = \Session::get('start'); - $end = \Session::get('end'); + $end = \Session::get('end'); $stats = [ 'budgets' => [], 'categories' => [], @@ -87,29 +45,51 @@ class Account implements AccountInterface // build a query: - $query = \TransactionJournal::with( - ['transactions' => function ($q) { - $q->orderBy('amount', 'ASC'); - }, 'transactiontype', 'components' => function ($q) { - $q->orderBy('class'); - }, 'transactions.account.accounttype'] - )->orderBy('date', 'DESC')->leftJoin( - 'transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id' - )->where('transactions.account_id', $account->id)->where('date', '>=', $start->format('Y-m-d'))->where( - 'date', '<=', $end->format('Y-m-d') - )->orderBy('transaction_journals.id', 'DESC'); + $query = \TransactionJournal::withRelevantData()->defaultSorting()->account($account)->after($start) + ->before($end); + // filter some: + if (\Input::get('type')) { + switch (\Input::get('type')) { + case 'transactions': + $query->transactionTypes(['Deposit', 'Withdrawal']); + break; + case 'transfers': + $query->transactionTypes(['Transfer']); + break; + default: + throw new FireflyException('No case for type "' . \Input::get('type') . '"!'); + break; + } + } + + if (\Input::get('show')) { + switch (\Input::get('show')) { + case 'expenses': + case 'out': + $query->lessThan(0); + break; + case 'income': + case 'in': + $query->moreThan(0); + break; + default: + throw new FireflyException('No case for show "' . \Input::get('show') . '"!'); + break; + } + } // build paginator: $totalItems = $query->count(); - $page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1; - $skip = ($page - 1) * $perPage; - $result = $query->skip($skip)->take($perPage)->get(['transaction_journals.*']); - // in the mean time, build list of categories, budgets and other accounts: + $page = max(1, intval(\Input::get('page'))); + $skip = ($page - 1) * $perPage; + $result = $query->skip($skip)->take($perPage)->get(['transaction_journals.*']); + + // get the relevant budgets, categories and accounts from this list: /** @var $item \TransactionJournal */ - foreach ($result as $item) { - $items[] = $item; + foreach ($result as $index => $item) { + foreach ($item->components as $component) { if ($component->class == 'Budget') { $stats['budgets'][$component->id] = $component; @@ -118,59 +98,56 @@ class Account implements AccountInterface $stats['categories'][$component->id] = $component; } } - $fromAccount = $item->transactions[0]->account; - $toAccount = $item->transactions[1]->account; + // since it is entirely possible the database is messed up somehow + // it might be that a transaction journal has only one transaction. + // this is mainly caused by wrong deletions and other artefacts from the past. + // if it is the case, we remove $item and continue like nothing ever happened. + + // this will however, mess up some statisics but we can live with that. + // we might be needing some cleanup routine in the future. + + // for now, we simply warn the user of this. + + if (count($item->transactions) < 2) { + \Session::flash('warning', + 'Some transactions are incomplete; they will not be shown. Statistics may differ.'); + unset($result[$index]); + continue; + } + $items[] = $item; + $fromAccount = $item->transactions[0]->account; + $toAccount = $item->transactions[1]->account; $stats['accounts'][$fromAccount->id] = $fromAccount; - $stats['accounts'][$toAccount->id] = $toAccount; + $stats['accounts'][$toAccount->id] = $toAccount; } - unset($result, $page); $paginator = \Paginator::make($items, $totalItems, $perPage); - - // statistics - $stats['period']['in'] = floatval( - \Transaction::where('account_id', $account->id)->where('amount', '>', 0)->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - )->leftJoin( - 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' - )->whereIn('transaction_types.type', ['Deposit', 'Withdrawal'])->where( - 'transaction_journals.date', '>=', $start->format('Y-m-d') - )->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount') - ); + unset($result, $page, $item, $fromAccount, $toAccount); - $stats['period']['out'] = floatval( - \Transaction::where('account_id', $account->id)->where('amount', '<', 0)->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - )->leftJoin( - 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' - )->whereIn('transaction_types.type', ['Deposit', 'Withdrawal'])->where( - 'transaction_journals.date', '>=', $start->format('Y-m-d') - )->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount') - ); - $stats['period']['diff'] = $stats['period']['in'] + $stats['period']['out']; + // statistics (transactions) + $trIn = floatval(\Transaction::before($end)->after($start)->account($account)->moreThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); + $trOut = floatval(\Transaction::before($end)->after($start)->account($account)->lessThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); + $trDiff = $trIn + $trOut; - $stats['period']['t_in'] = floatval( - \Transaction::where('account_id', $account->id)->where('amount', '>', 0)->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - )->leftJoin( - 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' - )->where('transaction_types.type', 'Transfer')->where( - 'transaction_journals.date', '>=', $start->format('Y-m-d') - )->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount') - ); + // statistics (transfers) + $trfIn = floatval(\Transaction::before($end)->after($start)->account($account)->moreThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount')); + $trfOut = floatval(\Transaction::before($end)->after($start)->account($account)->lessThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount')); + $trfDiff = $trfIn + $trfOut; - $stats['period']['t_out'] = floatval( - \Transaction::where('account_id', $account->id)->where('amount', '<', 0)->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' - )->leftJoin( - 'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' - )->where('transaction_types.type', 'Transfer')->where( - 'transaction_journals.date', '>=', $start->format('Y-m-d') - )->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->sum('amount') - ); - $stats['period']['t_diff'] = $stats['period']['t_in'] + $stats['period']['t_out']; + $stats['period'] = [ + 'in' => $trIn, + 'out' => $trOut, + 'diff' => $trDiff, + 't_in' => $trfIn, + 't_out' => $trfOut, + 't_diff' => $trfDiff + ]; $return = [ 'journals' => $paginator, diff --git a/app/lib/Firefly/Helper/Controllers/AccountInterface.php b/app/lib/Firefly/Helper/Controllers/AccountInterface.php index 6676d22950..4c2e378845 100644 --- a/app/lib/Firefly/Helper/Controllers/AccountInterface.php +++ b/app/lib/Firefly/Helper/Controllers/AccountInterface.php @@ -12,15 +12,6 @@ use Illuminate\Database\Eloquent\Collection; interface AccountInterface { - /** - * Build the index: - * - * @param Collection $accounts - * - * @return mixed - */ - public function index(Collection $accounts); - /** * @param \Account $account * diff --git a/app/lib/Firefly/Storage/Account/EloquentAccountRepository.php b/app/lib/Firefly/Storage/Account/EloquentAccountRepository.php index 78115ce1a3..634e071e05 100644 --- a/app/lib/Firefly/Storage/Account/EloquentAccountRepository.php +++ b/app/lib/Firefly/Storage/Account/EloquentAccountRepository.php @@ -36,7 +36,6 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function createOrFind($name, \AccountType $type = null) { - $account = $this->findByName($name, $type); if (!$account) { $data = [ @@ -60,10 +59,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface if (is_null($name) || strlen($name) == 0) { return null; } - $type = \AccountType::where('description', 'Beneficiary account')->first(); - - /** @noinspection PhpParamsInspection */ - + $type = \AccountType::where('type', 'Beneficiary account')->first(); return $this->createOrFind($name, $type); } @@ -74,38 +70,29 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function destroy(\Account $account) { - // find the oldest transaction which also is a "Opening balance" - $first = \Transaction:: - leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_journals.user_id', \Auth::user()->id) - ->where('transaction_types.type', 'Opening balance') - ->where('account_id', '!=', $account->id) - ->orderBy('transactions.id', 'DESC')->first(['transactions.*']); + // find all transaction journals related to this account: + $journals = \TransactionJournal::withRelevantData()->account($account)->get(['transaction_journals.*']); + $accountIDs = []; - $initialbalanceAccount = null; - if (!is_null($first)) { - $initialbalanceAccount = $first->account()->first(); - } - - - // loop the account, find all transaction journals, and delete them: - $transactions = $account->transactions()->with('transactionjournal')->get(); - $journals = []; - /** @var \Transaction $transaction */ - foreach ($transactions as $transaction) { - $journals[$transaction->transaction_journal_id] = $transaction->transactionJournal; - } /** @var \TransactionJournal $journal */ foreach ($journals as $journal) { + // remember the account id's of the transactions involved: + foreach ($journal->transactions as $t) { + $accountIDs[] = $t->account_id; + } $journal->delete(); + } - - if (!is_null($initialbalanceAccount)) { - $initialbalanceAccount->delete(); + $accountIDs = array_unique($accountIDs); + if (count($accountIDs) > 0) { + // find the "initial balance" type accounts in this list. Should be just 1. + $query = \Auth::user()->accounts()->accountType(['Initial balance account']) + ->whereIn('accounts.id', $accountIDs); + if ($query->count() == 1) { + $iba = $query->first(['accounts.*']); + $iba->delete(); + } } - - $account->delete(); /** @@ -135,10 +122,11 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function findByName($name, \AccountType $type = null) { - $type = is_null($type) ? \AccountType::where('description', 'Default account')->first() : $type; + $type = is_null($type) ? \AccountType::where('type', 'Default account')->first() : $type; - return \Auth::user()->accounts()->where('account_type_id', $type->id)->where('name', 'like', '%' . $name . '%') - ->first(); + return \Auth::user()->accounts()->where('account_type_id', $type->id) + ->where('name', 'like', '%' . $name . '%') + ->first(); } /** @@ -155,9 +143,9 @@ class EloquentAccountRepository implements AccountRepositoryInterface public function getActiveDefault() { return \Auth::user()->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where('account_types.description', 'Default account')->where('accounts.active', 1) + ->where('account_types.type', 'Default account')->where('accounts.active', 1) - ->get(['accounts.*']); + ->get(['accounts.*']); } /** @@ -165,12 +153,12 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function getActiveDefaultAsSelectList() { - $list = \Auth::user()->accounts()->leftJoin( - 'account_types', 'account_types.id', '=', 'accounts.account_type_id' + $list = \Auth::user()->accounts()->leftJoin( + 'account_types', 'account_types.id', '=', 'accounts.account_type_id' ) - ->where('account_types.description', 'Default account')->where('accounts.active', 1) + ->where('account_types.type', 'Default account')->where('accounts.active', 1) - ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); + ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); $return = []; foreach ($list as $entry) { $return[intval($entry->id)] = $entry->name; @@ -185,11 +173,11 @@ class EloquentAccountRepository implements AccountRepositoryInterface public function getBeneficiaries() { $list = \Auth::user()->accounts()->leftJoin( - 'account_types', 'account_types.id', '=', 'accounts.account_type_id' + 'account_types', 'account_types.id', '=', 'accounts.account_type_id' ) - ->where('account_types.description', 'Beneficiary account')->where('accounts.active', 1) + ->where('account_types.type', 'Beneficiary account')->where('accounts.active', 1) - ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); + ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); return $list; } @@ -213,7 +201,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function getCashAccount() { - $type = \AccountType::where('description', 'Cash account')->first(); + $type = \AccountType::where('type', 'Cash account')->first(); $cash = \Auth::user()->accounts()->where('account_type_id', $type->id)->first(); return $cash; @@ -226,9 +214,9 @@ class EloquentAccountRepository implements AccountRepositoryInterface public function getDefault() { return \Auth::user()->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where('account_types.description', 'Default account') + ->where('account_types.type', 'Default account') - ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); + ->orderBy('accounts.name', 'ASC')->get(['accounts.*']); } /** @@ -239,16 +227,26 @@ class EloquentAccountRepository implements AccountRepositoryInterface */ public function store($data) { - $defaultAccountType = \AccountType::where('description', 'Default account')->first(); - $accountType = isset($data['account_type']) ? $data['account_type'] : $defaultAccountType; + /** + * If the AccountType has been passed through, use it: + */ + if (isset($data['account_type']) && is_object($data['account_type']) + && get_class($data['account_type']) == 'AccountType' + ) { + $accountType = $data['account_type']; + } else { + $accountType = \AccountType::where('type', 'Default account')->first(); + } - // create Account: + /** + * Create new account: + */ $account = new \Account; $account->accountType()->associate($accountType); $account->user()->associate(\Auth::user()); $account->name = $data['name']; $account->active - = isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval( + = isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval( $data['active'] ) : 1; @@ -257,7 +255,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface // create initial balance, if necessary: if (isset($data['openingbalance']) && isset($data['openingbalancedate'])) { $amount = floatval($data['openingbalance']); - $date = new Carbon($data['openingbalancedate']); + $date = new Carbon($data['openingbalancedate']); $this->_createInitialBalance($account, $amount, $date); } } @@ -286,12 +284,12 @@ class EloquentAccountRepository implements AccountRepositoryInterface /** @var \Firefly\Helper\Controllers\AccountInterface $interface */ $interface = \App::make('Firefly\Helper\Controllers\AccountInterface'); - if ($account->accounttype->description == 'Default account') { + if ($account->accounttype->type == 'Default account') { $journal = $interface->openingBalanceTransaction($account); if ($journal) { - $journal->date = new Carbon($data['openingbalancedate']); + $journal->date = new Carbon($data['openingbalancedate']); $journal->transactions[0]->amount = floatval($data['openingbalance']) * -1; $journal->transactions[1]->amount = floatval($data['openingbalance']); $journal->transactions[0]->save(); @@ -306,8 +304,8 @@ class EloquentAccountRepository implements AccountRepositoryInterface /** * @param \Account $account - * @param int $amount - * @param Carbon $date + * @param int $amount + * @param Carbon $date * * @return bool * @SuppressWarnings(PHPMD.CamelCaseMethodName) @@ -315,24 +313,24 @@ class EloquentAccountRepository implements AccountRepositoryInterface protected function _createInitialBalance(\Account $account, $amount = 0, Carbon $date) { // get account type: - $initialBalanceAT = \AccountType::where('description', 'Initial balance account')->first(); + $initialBalanceAT = \AccountType::where('type', 'Initial balance account')->first(); // create new account: $initial = new \Account; $initial->accountType()->associate($initialBalanceAT); $initial->user()->associate(\Auth::user()); - $initial->name = $account->name . ' initial balance'; + $initial->name = $account->name . ' initial balance'; $initial->active = 0; if ($initial->validate()) { $initial->save(); // create new transaction journal (and transactions): /** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $transactionJournal */ $transactionJournal = \App::make( - 'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface' + 'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface' ); $transactionJournal->createSimpleJournal( - $initial, $account, 'Initial Balance for ' . $account->name, $amount, $date + $initial, $account, 'Initial Balance for ' . $account->name, $amount, $date ); return true; diff --git a/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php b/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php index af1116c383..8e91847901 100644 --- a/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php +++ b/app/lib/Firefly/Storage/TransactionJournal/EloquentTransactionJournalRepository.php @@ -64,8 +64,8 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito } // account types for both: - $toAT = $toAccount->accountType->description; - $fromAT = $from->accountType->description; + $toAT = $toAccount->accountType->type; + $fromAT = $from->accountType->type; $journalType = null; @@ -287,6 +287,7 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito $fromAccount = $accountRepository->find(intval($data['account_id'])); $toAccount = $accountRepository->createOrFindBeneficiary($data['beneficiary']); break; + case 'deposit': $fromAccount = $accountRepository->createOrFindBeneficiary($data['beneficiary']); $toAccount = $accountRepository->find(intval($data['account_id'])); @@ -295,7 +296,6 @@ class EloquentTransactionJournalRepository implements TransactionJournalReposito $fromAccount = $accountRepository->find(intval($data['account_from_id'])); $toAccount = $accountRepository->find(intval($data['account_to_id'])); - break; } // fall back to cash if necessary: diff --git a/app/models/Account.php b/app/models/Account.php index c497916677..45fc7fd840 100644 --- a/app/models/Account.php +++ b/app/models/Account.php @@ -1,5 +1,6 @@ 'required|between:1,100', + 'name' => ['required', 'between:1,100', 'alphabasic'], 'user_id' => 'required|exists:users,id', 'account_type_id' => 'required|exists:account_types,id', 'active' => 'required|boolean' @@ -63,10 +65,10 @@ class Account extends Ardent return floatval( $this->transactions() - ->leftJoin( - 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' + ->leftJoin( + 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' ) - ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount') + ->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount') ); } @@ -96,7 +98,8 @@ class Account extends Ardent public function predict( /** @noinspection PhpUnusedParameterInspection */ \Carbon\Carbon $date - ) { + ) + { return null; } @@ -110,4 +113,13 @@ class Account extends Ardent return $this->belongsTo('User'); } -} \ No newline at end of file + public function scopeAccountType(Builder $query, array $types) { + if(is_null($this->joinedAccountTypes)) { + $query->leftJoin('account_types','account_types.id','=','accounts.account_type_id'); + $this->joinedAccountTypes = true; + } + $query->whereIn('account_types.type',$types); + } + + +} \ No newline at end of file diff --git a/app/models/AccountType.php b/app/models/AccountType.php index c761dcb956..6053be1047 100644 --- a/app/models/AccountType.php +++ b/app/models/AccountType.php @@ -4,18 +4,26 @@ /** * AccountType * - * @property integer $id - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property string $description + * @property integer $id + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * @property string $type + * @property boolean $editable * @property-read \Illuminate\Database\Eloquent\Collection|\Account[] $accounts * @method static \Illuminate\Database\Query\Builder|\AccountType whereId($value) * @method static \Illuminate\Database\Query\Builder|\AccountType whereCreatedAt($value) * @method static \Illuminate\Database\Query\Builder|\AccountType whereUpdatedAt($value) - * @method static \Illuminate\Database\Query\Builder|\AccountType whereDescription($value) + * @method static \Illuminate\Database\Query\Builder|\AccountType whereType($value) + * @method static \Illuminate\Database\Query\Builder|\AccountType whereEditable($value) */ class AccountType extends Eloquent { + public static $rules + = [ + 'type' => ['required', 'between:1,50', 'alphabasic'], + 'editable' => 'required|boolean', + + ]; /** * @return \Illuminate\Database\Eloquent\Relations\HasMany diff --git a/app/tests/controllers/AccountControllerTest.php b/app/tests/controllers/AccountControllerTest.php index d336f68837..6b835ff7e8 100644 --- a/app/tests/controllers/AccountControllerTest.php +++ b/app/tests/controllers/AccountControllerTest.php @@ -57,7 +57,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -72,33 +72,6 @@ class AccountControllerTest extends TestCase $this->assertViewHas('account'); $this->assertResponseOk(); } - - /** - * @covers ::delete - */ - public function testDeleteWrongType() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Initial balance account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - // for successful binding: - Auth::shouldReceive('user')->andReturn($this->_user); - Auth::shouldReceive('check')->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - $this->_user->shouldReceive('getAttribute')->with('email')->andReturn('some@email'); - - $this->action('GET', 'AccountController@delete', $account->id); - $this->assertViewHas('message'); - $this->assertResponseOk(); - } - /** * @covers ::destroy */ @@ -109,7 +82,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -126,57 +99,6 @@ class AccountControllerTest extends TestCase $this->assertSessionHas('success'); } - /** - * @covers ::destroy - */ - public function testDestroyWrongType() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Initial balance account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - - // for successful binding: - Auth::shouldReceive('user')->once()->andReturn($this->_user); - Auth::shouldReceive('check')->once()->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - - $this->action('POST', 'AccountController@destroy', $account->id); - $this->assertViewHas('message'); - $this->assertResponseOk(); - } - - /** - * @covers ::destroy - */ - public function testDestroyFails() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - // for successful binding: - Auth::shouldReceive('user')->once()->andReturn($this->_user); - Auth::shouldReceive('check')->once()->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - $this->_repository->shouldReceive('destroy')->once()->andReturn(false); - - $this->action('POST', 'AccountController@destroy', $account->id); - $this->assertRedirectedToRoute('accounts.index'); - $this->assertSessionHas('error'); - } public function testEdit() { @@ -185,7 +107,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -208,28 +130,6 @@ class AccountControllerTest extends TestCase } - public function testEditWrongType() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Initial balance account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - // for successful binding. - Auth::shouldReceive('user')->andReturn($this->_user); - Auth::shouldReceive('check')->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - $this->_user->shouldReceive('getAttribute')->with('email')->andReturn('some@email'); - - $this->action('GET', 'AccountController@edit', $account->id); - $this->assertViewHas('message'); - $this->assertResponseOk(); - } public function testIndex() { @@ -238,7 +138,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -254,7 +154,6 @@ class AccountControllerTest extends TestCase ]; $this->_repository->shouldReceive('get')->once()->andReturn($collection); - $this->_accounts->shouldReceive('index')->with($collection)->once()->andReturn($list); $this->action('GET', 'AccountController@index'); $this->assertResponseOk(); } @@ -266,7 +165,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -303,29 +202,6 @@ class AccountControllerTest extends TestCase $this->assertResponseOk(); } - public function testShowWrongType() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Initial balance account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - // for successful binding. - Auth::shouldReceive('user')->andReturn($this->_user); - Auth::shouldReceive('check')->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - $this->_user->shouldReceive('getAttribute')->with('email')->andReturn($account->email); - - - $this->action('GET', 'AccountController@show', $account->id); - $this->assertViewHas('message'); - $this->assertResponseOk(); - } public function testStore() { @@ -334,7 +210,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -351,7 +227,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -369,7 +245,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -386,7 +262,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); @@ -402,30 +278,6 @@ class AccountControllerTest extends TestCase } - public function testUpdateWrongType() - { - /** @var \Account $account */ - $account = f::create('Account'); - - /** @var \AccountType $accountType */ - $accountType = f::create('AccountType'); - $accountType->description = 'Initial balance account'; - $accountType->save(); - $account->accountType()->associate($accountType); - $account->save(); - - // for successful binding. - Auth::shouldReceive('user')->andReturn($this->_user); - Auth::shouldReceive('check')->andReturn(true); - $this->_user->shouldReceive('getAttribute')->with('id')->once()->andReturn($account->user_id); - $this->_repository->shouldReceive('update')->andReturn($account); - - $this->action('POST', 'AccountController@update', $account->id); - $this->assertViewHas('message'); - $this->assertResponseOk(); - - } - public function testUpdateFails() { /** @var \Account $account */ @@ -433,7 +285,7 @@ class AccountControllerTest extends TestCase /** @var \AccountType $accountType */ $accountType = f::create('AccountType'); - $accountType->description = 'Default account'; + $accountType->type = 'Default account'; $accountType->save(); $account->accountType()->associate($accountType); $account->save(); diff --git a/app/tests/factories/AccountType.php b/app/tests/factories/AccountType.php index 8b81ff48ed..0ab4f7e630 100644 --- a/app/tests/factories/AccountType.php +++ b/app/tests/factories/AccountType.php @@ -4,7 +4,7 @@ use League\FactoryMuffin\Facade; Facade::define( 'AccountType', [ - 'description' => function () { + 'type' => function () { $types = [ 'Default account', 'Cash account', @@ -13,6 +13,7 @@ Facade::define( ]; return $types[rand(0, 3)]; - } + }, + 'editable' => 1 ] ); \ No newline at end of file diff --git a/app/views/accounts/create.blade.php b/app/views/accounts/create.blade.php index 7a2f3a6ebc..1ed64521d1 100644 --- a/app/views/accounts/create.blade.php +++ b/app/views/accounts/create.blade.php @@ -10,11 +10,14 @@ Accounts are the record holders for transactions and transfers. Money moves from one account to another.

- + + +
+

- In a double-entry bookkeeping system (such as this one) there is a "from" account and a "to" - account, even when money is created from thin air (such as interest, or when new accounts already have - a positive balance). + In a double-entry bookkeeping system (such as this one) there is a "from"-account and a "to"-account, + even when money is created from thin air (such as interest, or when new accounts already have a + positive balance).

This form creates personal accounts only. @@ -34,9 +37,9 @@

{{ Form::text('name', Input::old('name'), ['class' => 'form-control']) }} @if($errors->has('name')) -

{{$errors->first('name')}}

+

{{$errors->first('name')}}

@else - Use something descriptive such as "checking account" or "My Bank Main Account". + Use something descriptive such as "checking account" or "My Bank Main Account". @endif
diff --git a/app/views/accounts/edit.blade.php b/app/views/accounts/edit.blade.php index 19afe6d788..2c9f429eb3 100644 --- a/app/views/accounts/edit.blade.php +++ b/app/views/accounts/edit.blade.php @@ -33,7 +33,7 @@
- @if($account->accounttype->description == 'Default account') + @if($account->accounttype->type == 'Default account')

Optional fields

diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index 696aa274ab..c7f90fd2ba 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -35,22 +35,22 @@ Out {{mf($show['statistics']['period']['out'])}} - + {{mf($show['statistics']['period']['t_out'])}} - + In {{mf($show['statistics']['period']['in'])}} - + {{mf($show['statistics']['period']['t_in'])}} - + @@ -101,7 +101,7 @@

Transactions For selected account and period

- @include('paginated.transactions',['journals' => $show['journals']]) + @include('paginated.transactions',['journals' => $show['journals'],'sum' => true])
diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index f2d7c0b374..378090454f 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -50,7 +50,7 @@ $r = Route::current()->getName();