diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index 2ddcd82e42..8b3b4d4795 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -1,31 +1,116 @@ _accounts = $accounts; - $this->_repository = $repository; View::share('mainTitleIcon', 'fa-credit-card'); View::share('title', 'Accounts'); } + /** + * @param string $what + * + * @return View + * @throws FireflyException + */ + public function index($what = 'default') + { + switch ($what) { + default: + throw new FireflyException('Cannot handle account type "' . e($what) . '".'); + break; + case 'asset': + View::share('subTitleIcon', 'fa-money'); + View::share('subTitle', 'Asset accounts'); + break; + case 'expense': + View::share('subTitleIcon', 'fa-shopping-cart'); + View::share('subTitle', 'Expense accounts'); + break; + case 'revenue': + View::share('subTitleIcon', 'fa-download'); + View::share('subTitle', 'Revenue accounts'); + break; + } + return View::make('accounts.index')->with('what', $what); + } + + + /** + * @param string $what + * + * @return \Illuminate\Http\JsonResponse + * @throws FireflyException + */ + public function json($what = 'default') + { + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Shared\Json\Json $json */ + $json = App::make('FireflyIII\Shared\Json\Json'); + + $parameters = $json->dataTableParameters(); + + switch ($what) { + default: + throw new FireflyException('Cannot handle account type "' . e($what) . '".'); + break; + case 'asset': + $accounts = $acct->getAssetAccounts($parameters); + $count = $acct->countAssetAccounts(); + break; + case 'expense': + $accounts = $acct->getExpenseAccounts($parameters); + $count = $acct->countExpenseAccounts(); + break; + case 'revenue': + $accounts = $acct->getRevenueAccounts($parameters); + $count = $acct->countRevenueAccounts(); + break; + } + + /* + * Output the set compatible with data tables: + */ + $return = [ + 'draw' => intval(Input::get('draw')), + 'recordsTotal' => $count, + 'recordsFiltered' => $accounts->count(), + 'data' => [], + ]; + + /* + * Loop the accounts: + */ + /** @var \Account $account */ + foreach ($accounts as $account) { + $entry = [ + 'name' => ['name' => $account->name, 'url' => route('accounts.show', $account->id)], + 'balance' => $account->balance(), + 'id' => [ + 'edit' => route('accounts.edit', $account->id), + 'delete' => route('accounts.delete', $account->id), + ] + ]; + $return['data'][] = $entry; + } + + + return Response::jsoN($return); + } + /** * @return \Illuminate\View\View */ @@ -43,52 +128,7 @@ class AccountController extends \BaseController break; } - - return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with( - 'what', $what - ); - } - - /** - * @return $this - */ - public function asset() - { - View::share('subTitleIcon', 'fa-money'); - - $accounts = $this->_repository->getOfTypes(['Asset account', 'Default account']); - - return View::make('accounts.asset')->with('subTitle', 'Asset accounts')->with( - 'accounts', $accounts - ); - } - - /** - * @return $this - */ - public function expense() - { - View::share('subTitleIcon', 'fa-shopping-cart'); - - $accounts = $this->_repository->getOfTypes(['Expense account', 'Beneficiary account']); - - return View::make('accounts.expense')->with('subTitle', 'Expense accounts')->with( - 'accounts', $accounts - ); - } - - /** - * @return $this - */ - public function revenue() - { - View::share('subTitleIcon', 'fa-download'); - - $accounts = $this->_repository->getOfTypes(['Revenue account']); - - return View::make('accounts.revenue')->with('subTitle', 'Revenue accounts')->with( - 'accounts', $accounts - ); + return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with('what', $what); } /** @@ -99,9 +139,9 @@ class AccountController extends \BaseController public function delete(Account $account) { return View::make('accounts.delete')->with('account', $account) - ->with( - 'subTitle', 'Delete ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' - ); + ->with( + 'subTitle', 'Delete ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' + ); } /** @@ -111,20 +151,67 @@ class AccountController extends \BaseController */ public function destroy(Account $account) { + $type = $account->accountType->type; - $this->_repository->destroy($account); + + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); + + /** @var \FireflyIII\Database\TransactionJournal $jrnls */ + $jrnls = App::make('FireflyIII\Database\TransactionJournal'); + + /* + * Find the "initial balance account", should it exist: + */ + $initialBalance = $acct->findInitialBalanceAccount($account); + + /* + * Get all the transaction journals that are part of this/these account(s): + */ + $journals = []; + if ($initialBalance) { + $transactions = $initialBalance->transactions()->get(); + /** @var \Transaction $transaction */ + foreach ($transactions as $transaction) { + $journals[] = $transaction->transaction_journal_id; + } + } + /** @var \Transaction $transaction */ + foreach ($account->transactions() as $transaction) { + $journals[] = $transaction->transaction_journal_id; + } + + $journals = array_unique($journals); + + /* + * Delete the journals. Should get rid of the transactions as well. + */ + foreach ($journals as $id) { + $journal = $jrnls->find($id); + $journal->delete(); + } + + /* + * Delete it + */ + if ($initialBalance) { + $acct->destroy($initialBalance); + } + + $acct->destroy($account); + Session::flash('success', 'The account was deleted.'); switch ($type) { case 'Asset account': case 'Default account': - return Redirect::route('accounts.asset'); + return Redirect::route('accounts.index', 'asset'); break; case 'Expense account': case 'Beneficiary account': - return Redirect::route('accounts.expense'); + return Redirect::route('accounts.index', 'expense'); break; case 'Revenue account': - return Redirect::route('accounts.revenue'); + return Redirect::route('accounts.index', 'revenue'); break; } @@ -153,18 +240,23 @@ class AccountController extends \BaseController break; } - $openingBalance = $this->_accounts->openingBalanceTransaction($account); - return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance) + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); - ->with('subTitle', 'Edit ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'); - } + $openingBalance = $acct->openingBalanceTransaction($account); + Session::forget('prefilled'); + if (!is_null($openingBalance)) { + $prefilled['openingbalancedate'] = $openingBalance->date->format('Y-m-d'); + $prefilled['openingbalance'] = floatval($openingBalance->transactions()->where('account_id', $account->id)->first()->amount); + Session::flash('prefilled', $prefilled); + } - /** - * @return $this - */ - public function index() - { - return View::make('error')->with('message', 'This view has been disabled'); + + return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance)->with( + 'subTitle', 'Edit ' . strtolower( + $account->accountType->type + ) . ' "' . $account->name . '"' + ); } /** @@ -189,54 +281,55 @@ class AccountController extends \BaseController } - $data = $this->_accounts->show($account, 40); - - return View::make('accounts.show')->with('account', $account)->with('show', $data)->with( - 'subTitle', - 'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"' - ); + //$data = $this->_accounts->show($account, 40); + return View::make('accounts.show') + ->with('account', $account) + ->with('subTitle', 'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'); } /** * @return $this|\Illuminate\Http\RedirectResponse + * @throws FireflyException */ public function store() { $data = Input::all(); $data['what'] = isset($data['what']) && $data['what'] != '' ? $data['what'] : 'asset'; + /** @var \FireflyIII\Database\Account $acct */ + $acct = App::make('FireflyIII\Database\Account'); - - switch ($data['what']) { + switch ($data['post_submit_action']) { default: - case 'asset': - $data['account_type'] = 'Asset account'; - break; - case 'expense': - $data['account_type'] = 'Expense account'; - break; - case 'revenue': - $data['account_type'] = 'Revenue account'; + throw new FireflyException('Cannot handle post_submit_action "' . e($data['post_submit_action']) . '"'); break; + case 'create_another': + case 'store': + $messages = $acct->validate($data); + /** @var MessageBag $messages ['errors'] */ + if ($messages['errors']->count() > 0) { + Session::flash('warnings', $messages['warnings']); + Session::flash('successes', $messages['successes']); + Session::flash('error', 'Could not save account: ' . $messages['errors']->first()); + return Redirect::route('accounts.create', $data['what'])->withInput()->withErrors($messages['errors']); + } + // store! + $acct->store($data); + Session::flash('success', 'New account stored!'); - } - $account = $this->_repository->store($data); - - if ($account->validate()) { - // saved! return to wherever. - Session::flash('success', 'Account "' . $account->name . '" created!'); - if (intval(Input::get('create')) === 1) { + if ($data['post_submit_action'] == 'create_another') { + return Redirect::route('accounts.create', $data['what']); + } else { + return Redirect::route('accounts.index', $data['what']); + } + break; + case 'validate_only': + $messageBags = $acct->validate($data); + Session::flash('warnings', $messageBags['warnings']); + Session::flash('successes', $messageBags['successes']); + Session::flash('errors', $messageBags['errors']); return Redirect::route('accounts.create', $data['what'])->withInput(); - } else { - - return Redirect::route('accounts.' . e($data['what'])); - } - } else { - // did not save, return with error: - Session::flash('error', 'Could not save the new account: ' . $account->errors()->first()); - - return Redirect::route('accounts.create', $data['what'])->withErrors($account->errors())->withInput(); - + break; } } diff --git a/app/lib/Firefly/Form/Form.php b/app/lib/Firefly/Form/Form.php index 6414fd983e..1b697f01fe 100644 --- a/app/lib/Firefly/Form/Form.php +++ b/app/lib/Firefly/Form/Form.php @@ -29,6 +29,14 @@ class Form return self::ffInput('checkbox', $name, $value, $options); } + /** + * @param $name + * @param null $value + * @param array $options + * + * @return string + * @throws FireflyException + */ public static function ffAmount($name, $value = null, array $options = []) { $options['step'] = 'any'; @@ -37,6 +45,21 @@ class Form } + /** + * @param $name + * @param null $value + * @param array $options + * + * @return string + * @throws FireflyException + */ + public static function ffBalance($name, $value = null, array $options = []) + { + $options['step'] = 'any'; + return self::ffInput('amount', $name, $value, $options); + + } + /** * @param $name * @param null $value diff --git a/app/lib/Firefly/Helper/Controllers/Account.php b/app/lib/Firefly/Helper/Controllers/Account.php index a7fb95a02b..93dd3b12d7 100644 --- a/app/lib/Firefly/Helper/Controllers/Account.php +++ b/app/lib/Firefly/Helper/Controllers/Account.php @@ -12,14 +12,17 @@ class Account implements AccountInterface /** * @param \Account $account + * * @return \TransactionJournal|null */ public function openingBalanceTransaction(\Account $account) { return \TransactionJournal::withRelevantData() ->accountIs($account) - ->leftJoin('transaction_types', 'transaction_types.id', '=', - 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id' + ) ->where('transaction_types.type', 'Opening balance') ->first(['transaction_journals.*']); } @@ -36,7 +39,7 @@ class Account implements AccountInterface * For now, Firefly simply warns the user of this. * * @param \Account $account - * @param $perPage + * @param $perPage * * @return array|mixed * @throws \Firefly\Exception\FireflyException @@ -110,17 +113,25 @@ class Account implements AccountInterface // statistics (transactions) - $trIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) - ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); - $trOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) - ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount')); + $trIn = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount') + ); + $trOut = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) + ->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount') + ); $trDiff = $trIn + $trOut; // statistics (transfers) - $trfIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) - ->transactionTypes(['Transfer'])->sum('transactions.amount')); - $trfOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) - ->transactionTypes(['Transfer'])->sum('transactions.amount')); + $trfIn = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->moreThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount') + ); + $trfOut = floatval( + \Transaction::before($end)->after($start)->accountIs($account)->lessThan(0) + ->transactionTypes(['Transfer'])->sum('transactions.amount') + ); $trfDiff = $trfIn + $trfOut; $stats['period'] = [ diff --git a/app/lib/FireflyIII/Database/Account.php b/app/lib/FireflyIII/Database/Account.php new file mode 100644 index 0000000000..70eac86228 --- /dev/null +++ b/app/lib/FireflyIII/Database/Account.php @@ -0,0 +1,426 @@ +setUser(\Auth::user()); + } + + /** + * Get all asset accounts. Optional JSON based parameters. + * + * @param array $parameters + * + * @return Collection + */ + public function getAssetAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Default account', 'Asset account'], $parameters); + + } + + /** + * @param \Account $account + * + * @return \Account|null + */ + public function findInitialBalanceAccount(\Account $account) + { + /** @var \FireflyIII\Database\AccountType $acctType */ + $acctType = \App::make('FireflyIII\Database\AccountType'); + + $accountType = $acctType->findByWhat('initial'); + + return $this->getUser()->accounts()->where('account_type_id', $accountType->id)->where('name', 'LIKE', $account->name . '%')->first(); + } + + /** + * @param array $types + * @param array $parameters + * + * @return Collection + */ + public function getAccountsByType(array $types, array $parameters = []) + { + /* + * Basic query: + */ + $query = $this->getUser()->accounts()->accountTypeIn($types); + + + /* + * Without an opening balance, the rest of these queries will fail. + */ + + $query->leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id'); + $query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'); + + /* + * Not used, but useful for the balance within a certain month / year. + */ + $balanceOnDate = isset($parameters['date']) ? $parameters['date'] : Carbon::now(); + $query->where( + function ($q) use ($balanceOnDate) { + $q->where('transaction_journals.date', '<=', $balanceOnDate->format('Y-m-d')); + $q->orWhereNull('transaction_journals.date'); + } + ); + + $query->groupBy('accounts.id'); + + /* + * If present, process parameters for sorting: + */ + if (isset($parameters['order'])) { + foreach ($parameters['order'] as $instr) { + $query->orderBy($instr['name'], $instr['dir']); + } + } + + /* + * If present, process parameters for searching. + */ + if (isset($parameters['search'])) { + $query->where('name', 'LIKE', '%' . e($parameters['search']['value'] . '%')); + } + + /* + * If present, start at $start: + */ + if (isset($parameters['start'])) { + $query->skip(intval($parameters['start'])); + } + if (isset($parameters['length'])) { + $query->take(intval($parameters['length'])); + } + + return $query->get(['accounts.*', \DB::Raw('SUM(`transactions`.`amount`) as `balance`')]); + } + + /** + * @return int + */ + public function countAssetAccounts() + { + return $this->countAccountsByType(['Default account', 'Asset account']); + } + + /** + * @return int + */ + public function countExpenseAccounts() + { + return $this->countAccountsByType(['Expense account', 'Beneficiary account']); + } + + /** + * @param array $types + * + * @return int + */ + public function countAccountsByType(array $types) + { + return $this->getUser()->accounts()->accountTypeIn($types)->count(); + } + + /** + * @param array $parameters + * + * @return Collection + */ + public function getExpenseAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Expense account', 'Beneficiary account'], $parameters); + } + + /** + * Get all default accounts. + * + * @return Collection + */ + public function getDefaultAccounts() + { + // TODO: Implement getDefaultAccounts() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Counts the number of total revenue accounts. Useful for DataTables. + * + * @return int + */ + public function countRevenueAccounts() + { + return $this->countAccountsByType(['Revenue account']); + } + + /** + * Get all revenue accounts. + * + * @param array $parameters + * + * @return Collection + */ + public function getRevenueAccounts(array $parameters = []) + { + return $this->getAccountsByType(['Revenue account'], $parameters); + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + $model->delete(); + return true; + + } + + /** + * @param \Account $account + * + * @return \TransactionJournal|null + */ + public function openingBalanceTransaction(\Account $account) + { + return \TransactionJournal::withRelevantData() + ->accountIs($account) + ->leftJoin( + 'transaction_types', 'transaction_types.id', '=', + 'transaction_journals.transaction_type_id' + ) + ->where('transaction_types.type', 'Opening balance') + ->first(['transaction_journals.*']); + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + die('No impl'); + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + /* + * Name validation: + */ + if (!isset($model['name'])) { + $errors->add('name', 'Name is mandatory'); + } + + if (isset($model['name']) && strlen($model['name']) == 0) { + $errors->add('name', 'Name is too short'); + } + if (isset($model['name']) && strlen($model['name']) > 100) { + $errors->add('name', 'Name is too long'); + } + $validator = \Validator::make([$model], \Account::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + + /* + * type validation. + */ + if (!isset($model['what'])) { + $errors->add('name', 'Internal error: need to know type of account!'); + } + + /* + * Opening balance and opening balance date. + */ + if (isset($model['what']) && $model['what'] == 'asset') { + if (isset($model['openingbalance']) && strlen($model['openingbalance']) > 0 && !is_numeric($model['openingbalance'])) { + $errors->add('openingbalance', 'This is not a number.'); + } + if (isset($model['openingbalancedate']) && strlen($model['openingbalancedate']) > 0) { + try { + new Carbon($model['openingbalancedate']); + } catch (\Exception $e) { + $errors->add('openingbalancedate', 'This date is invalid.'); + } + } + } + + + if (!$errors->has('name')) { + $successes->add('name', 'OK'); + } + if (!$errors->has('openingbalance')) { + $successes->add('openingbalance', 'OK'); + } + if (!$errors->has('openingbalancedate')) { + $successes->add('openingbalancedate', 'OK'); + } + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + + /* + * Find account type. + */ + /** @var \FireflyIII\Database\AccountType $acctType */ + $acctType = \App::make('FireflyIII\Database\AccountType'); + + $accountType = $acctType->findByWhat($data['what']); + + $data['user_id'] = $this->getUser()->id; + $data['account_type_id'] = $accountType->id; + $data['active'] = isset($data['active']) && $data['active'] === '1' ? 1 : 0; + + + $data = array_except($data, array('_token', 'what')); + $account = new \Account($data); + if (!$account->validate()) { + var_dump($account->errors()->all()); + exit; + } + $account->save(); + if (isset($data['openingbalance']) && floatval($data['openingbalance']) != 0) { + $this->storeInitialBalance($account, $data); + } + + + /* Tell transaction journal to store a new one.*/ + + + return $account; + + } + + /** + * @param \Account $account + * @param array $data + * + * @return bool + */ + public function storeInitialBalance(\Account $account, array $data) + { + $opposingData = [ + 'name' => $account->name . ' Initial Balance', + 'active' => 0, + 'what' => 'initial' + ]; + $opposingAccount = $this->store($opposingData); + + /* + * Create a journal from opposing to account or vice versa. + */ + $balance = floatval($data['openingbalance']); + $date = new Carbon($data['openingbalancedate']); + /** @var \FireflyIII\Database\TransactionJournal $tj */ + $tj = \App::make('FireflyIII\Database\TransactionJournal'); + if ($balance < 0) { + // first transaction draws money from the new account to the opposing + $from = $account; + $to = $opposingAccount; + } else { + // first transaction puts money into account + $from = $opposingAccount; + $to = $account; + } + + // data for transaction journal: + $balance = $balance < 0 ? $balance * -1 : $balance; + $opening = [ + 'what' => 'opening', + 'currency' => 'EUR', + 'amount' => $balance, + 'from' => $from, + 'to' => $to, + 'date' => $date, + 'description' => 'Opening balance for new account ' . $account->name, + ]; + + + $validation = $tj->validate($opening); + if ($validation['errors']->count() == 0) { + $tj->store($opening); + return true; + } else { + var_dump($validation['errors']); + exit; + } + return false; + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/AccountInterface.php b/app/lib/FireflyIII/Database/AccountInterface.php new file mode 100644 index 0000000000..0fcdebdcb1 --- /dev/null +++ b/app/lib/FireflyIII/Database/AccountInterface.php @@ -0,0 +1,109 @@ +first(); + break; + case 'asset': + return \AccountType::whereType('Asset account')->first(); + break; + case 'revenue': + return \AccountType::whereType('Revenue account')->first(); + break; + case 'initial': + return \AccountType::whereType('Initial balance account')->first(); + break; + default: + throw new FireflyException('Cannot find account type described as "' . e($what) . '".'); + break; + + } + return null; + } + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + // TODO: Implement validate() method. + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/AccountTypeInterface.php b/app/lib/FireflyIII/Database/AccountTypeInterface.php new file mode 100644 index 0000000000..9de34619c4 --- /dev/null +++ b/app/lib/FireflyIII/Database/AccountTypeInterface.php @@ -0,0 +1,20 @@ +_user; + } + + /** + * @param $user + */ + public function setUser($user) + { + $this->_user = $user; + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/Transaction.php b/app/lib/FireflyIII/Database/Transaction.php new file mode 100644 index 0000000000..bb10a91b4c --- /dev/null +++ b/app/lib/FireflyIII/Database/Transaction.php @@ -0,0 +1,178 @@ +add('account', 'No account present'); + } + if (isset($model['account']) && !($model['account'] instanceof \Account)) { + $errors->add('account', 'No valid account present'); + } + if (isset($model['account_id']) && intval($model['account_id']) < 0) { + $errors->add('account', 'No valid account_id present'); + } + + if (isset($model['piggybank_id']) && intval($model['piggybank_id']) < 0) { + $errors->add('piggybank', 'No valid piggybank_id present'); + } + + if (!isset($model['transaction_journal_id']) && !isset($model['transaction_journal'])) { + $errors->add('transaction_journal', 'No TJ present'); + } + if (isset($model['transaction_journal']) && !($model['transaction_journal'] instanceof \TransactionJournal)) { + $errors->add('transaction_journal', 'No valid transaction_journal present'); + } + if (isset($model['transaction_journal_id']) && intval($model['transaction_journal_id']) < 0) { + $errors->add('account', 'No valid transaction_journal_id present'); + } + + if (isset($model['description']) && strlen($model['description']) > 255) { + $errors->add('account', 'Description too long'); + } + + if (!isset($model['amount'])) { + $errors->add('amount', 'No amount present.'); + } + if (isset($model['amount']) && floatval($model['amount']) == 0) { + $errors->add('amount', 'Invalid amount.'); + } + + if (!$errors->has('account')) { + $successes->add('account', 'OK'); + } + if (!$errors->has('')) { + $successes->add('piggybank', 'OK'); + } + if (!$errors->has('transaction_journal')) { + $successes->add('transaction_journal', 'OK'); + } + if (!$errors->has('amount')) { + $successes->add('amount', 'OK'); + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + // TODO: Implement store() method. + $transaction = new \Transaction; + $transaction->account()->associate($data['account']); + $transaction->transactionJournal()->associate($data['transaction_journal']); + $transaction->amount = floatval($data['amount']); + if (isset($data['piggybank'])) { + $transaction->piggybank()->associate($data['piggybank']); + } + if (isset($data['description'])) { + $transaction->description = $data['description']; + } + if ($transaction->validate()) { + $transaction->save(); + } else { + throw new FireflyException($transaction->errors()->first()); + } + return $transaction; + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + // TODO: Implement find() method. + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionCurrency.php b/app/lib/FireflyIII/Database/TransactionCurrency.php new file mode 100644 index 0000000000..2aeb26af50 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionCurrency.php @@ -0,0 +1,112 @@ +first(); + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php b/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php new file mode 100644 index 0000000000..5e10785088 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionCurrencyInterface.php @@ -0,0 +1,26 @@ +setUser(\Auth::user()); + } + + + /** + * @param Ardent $model + * + * @return bool + */ + public function destroy(Ardent $model) + { + // TODO: Implement destroy() method. + } + + /** + * Validates a model. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param Ardent $model + * + * @return array + */ + public function validateObject(Ardent $model) + { + // TODO: Implement validateObject() method. + } + + /** + * Validates an array. Returns an array containing MessageBags + * errors/warnings/successes. + * + * @param array $model + * + * @return array + */ + public function validate(array $model) + { + + $warnings = new MessageBag; + $successes = new MessageBag; + $errors = new MessageBag; + + + if (!isset($model['what'])) { + $errors->add('description', 'Internal error: need to know type of transaction!'); + } + if (isset($model['recurring_transaction_id']) && intval($model['recurring_transaction_id']) < 0) { + $errors->add('recurring_transaction_id', 'Recurring transaction is invalid.'); + } + if (!isset($model['description'])) { + $errors->add('description', 'This field is mandatory.'); + } + if (isset($model['description']) && strlen($model['description']) == 0) { + $errors->add('description', 'This field is mandatory.'); + } + if (isset($model['description']) && strlen($model['description']) > 255) { + $errors->add('description', 'Description is too long.'); + } + + if (!isset($model['currency'])) { + $errors->add('description', 'Internal error: currency is mandatory!'); + } + if (isset($model['date']) && !($model['date'] instanceof Carbon) && strlen($model['date']) > 0) { + try { + new Carbon($model['date']); + } catch (\Exception $e) { + $errors->add('date', 'This date is invalid.'); + } + } + if (!isset($model['date'])) { + $errors->add('date', 'This date is invalid.'); + } + + if (isset($model['to_id']) && intval($model['to_id']) < 0) { + $errors->add('account_to', 'Invalid to-account'); + } + if (isset($model['from_id']) && intval($model['from_id']) < 0) { + $errors->add('account_from', 'Invalid from-account'); + } + if (isset($model['to']) && !($model['to'] instanceof \Account)) { + $errors->add('account_to', 'Invalid to-account'); + } + if (isset($model['from']) && !($model['from'] instanceof \Account)) { + $errors->add('account_from', 'Invalid from-account'); + } + if (!isset($model['amount']) || (isset($model['amount']) && floatval($model['amount']) < 0)) { + $errors->add('amount', 'Invalid amount'); + } + if (!isset($model['from']) && !isset($model['to'])) { + $errors->add('account_to', 'No accounts found!'); + } + + $validator = \Validator::make([$model], \Transaction::$rules); + if ($validator->invalid()) { + $errors->merge($errors); + } + + + /* + * Add "OK" + */ + if (!$errors->has('description')) { + $successes->add('description', 'OK'); + } + if (!$errors->has('date')) { + $successes->add('date', 'OK'); + } + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'successes' => $successes + ]; + + + } + + /** + * @param array $data + * + * @return Ardent + */ + public function store(array $data) + { + + /** @var \FireflyIII\Database\TransactionType $typeRepository */ + $typeRepository = \App::make('FireflyIII\Database\TransactionType'); + + /** @var \FireflyIII\Database\TransactionCurrency $currencyRepository */ + $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency'); + + /** @var \FireflyIII\Database\Transaction $transactionRepository */ + $transactionRepository = \App::make('FireflyIII\Database\Transaction'); + + $journalType = $typeRepository->findByWhat($data['what']); + $currency = $currencyRepository->findByCode($data['currency']); + + $journal = new \TransactionJournal; + $journal->transactionType()->associate($journalType); + $journal->transactionCurrency()->associate($currency); + $journal->user()->associate($this->getUser()); + $journal->description = $data['description']; + $journal->date = $data['date']; + $journal->completed = 0; + //$journal->user_id = $this->getUser()->id; + + /* + * This must be enough to store the journal: + */ + if (!$journal->validate()) { + \Log::error($journal->errors()->all()); + throw new FireflyException('store() transactionjournal failed, but it should not!'); + } + $journal->save(); + + /* + * Then store both transactions. + */ + $first = [ + 'account' => $data['from'], + 'transaction_journal' => $journal, + 'amount' => ($data['amount'] * -1), + ]; + $validate = $transactionRepository->validate($first); + if ($validate['errors']->count() == 0) { + $transactionRepository->store($first); + } else { + throw new FireflyException($validate['errors']->first()); + } + + $second = [ + 'account' => $data['to'], + 'transaction_journal' => $journal, + 'amount' => floatval($data['amount']), + ]; + + $validate = $transactionRepository->validate($second); + if ($validate['errors']->count() == 0) { + $transactionRepository->store($second); + } else { + throw new FireflyException($validate['errors']->first()); + } + + $journal->completed = 1; + $journal->save(); + return $journal; + } + + /** + * Returns an object with id $id. + * + * @param int $id + * + * @return Ardent + */ + public function find($id) + { + return $this->getUser()->transactionjournals()->find($id); + } + + /** + * Returns all objects. + * + * @return Collection + */ + public function get() + { + // TODO: Implement get() method. + } + + /** + * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. + * + * @param $what + * + * @return \AccountType|null + */ + public function findByWhat($what) + { + // TODO: Implement findByWhat() method. + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionJournalInterface.php b/app/lib/FireflyIII/Database/TransactionJournalInterface.php new file mode 100644 index 0000000000..b791c06ad6 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionJournalInterface.php @@ -0,0 +1,19 @@ +first(); + break; + default: + throw new FireflyException('Cannot find transaction type described as "' . e($what) . '".'); + break; + + } + return null; + } +} \ No newline at end of file diff --git a/app/lib/FireflyIII/Database/TransactionTypeInterface.php b/app/lib/FireflyIII/Database/TransactionTypeInterface.php new file mode 100644 index 0000000000..a1c4096f41 --- /dev/null +++ b/app/lib/FireflyIII/Database/TransactionTypeInterface.php @@ -0,0 +1,20 @@ + intval(\Input::get('start')), + 'length' => $length, + 'draw' => intval(\Input::get('draw')), + ]; + + + /* + * Columns: + */ + if (!is_null(\Input::get('columns')) && is_array(\Input::get('columns'))) { + foreach (\Input::get('columns') as $column) { + $parameters['columns'][] = [ + 'data' => $column['data'], + 'name' => $column['name'], + 'searchable' => $column['searchable'] == 'true' ? true : false, + 'orderable' => $column['orderable'] == 'true' ? true : false, + 'search' => [ + 'value' => $column['search']['value'], + 'regex' => $column['search']['regex'] == 'true' ? true : false, + ] + ]; + } + } + + + /* + * Sorting. + */ + $parameters['orderOnAccount'] = false; + if (!is_null(\Input::get('order')) && is_array(\Input::get('order'))) { + foreach (\Input::get('order') as $order) { + $columnIndex = intval($order['column']); + $columnName = $parameters['columns'][$columnIndex]['name']; + $parameters['order'][] = [ + 'name' => $columnName, + 'dir' => strtoupper($order['dir']) + ]; + if ($columnName == 'to' || $columnName == 'from') { + $parameters['orderOnAccount'] = true; + } + } + } + /* + * Search parameters: + */ + $parameters['search'] = [ + 'value' => '', + 'regex' => false + ]; + if (!is_null(\Input::get('search')) && is_array(\Input::get('search'))) { + $search = \Input::get('search'); + $parameters['search'] = [ + 'value' => $search['value'], + 'regex' => $search['regex'] == 'true' ? true : false + ]; + } + return $parameters; + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php index 535b161678..22d86fa854 100644 --- a/app/routes.php +++ b/app/routes.php @@ -136,15 +136,12 @@ Route::group(['before' => 'auth'], function () { Route::get('/jump/{range}',['uses' => 'HomeController@rangeJump','as' => 'rangeJump']); // account controller: - Route::get('/accounts', ['uses' => 'AccountController@index', 'as' => 'accounts.index']); - Route::get('/accounts/asset', ['uses' => 'AccountController@asset', 'as' => 'accounts.asset']); - Route::get('/accounts/expense', ['uses' => 'AccountController@expense', 'as' => 'accounts.expense']); - Route::get('/accounts/revenue', ['uses' => 'AccountController@revenue', 'as' => 'accounts.revenue']); - + Route::get('/accounts/json/{what}', ['uses' => 'AccountController@json', 'as' => 'accounts.json'])->where('what','revenue|asset|expense'); + Route::get('/accounts/{what}', ['uses' => 'AccountController@index', 'as' => 'accounts.index'])->where('what','revenue|asset|expense'); Route::get('/accounts/create/{what}', ['uses' => 'AccountController@create', 'as' => 'accounts.create'])->where('what','revenue|asset|expense'); - Route::get('/accounts/{account}', ['uses' => 'AccountController@show', 'as' => 'accounts.show']); - Route::get('/accounts/{account}/edit', ['uses' => 'AccountController@edit', 'as' => 'accounts.edit']); - Route::get('/accounts/{account}/delete', ['uses' => 'AccountController@delete', 'as' => 'accounts.delete']); + 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}',['uses' => 'AccountController@show','as' => 'accounts.show']); // budget controller: Route::get('/budgets/date',['uses' => 'BudgetController@indexByDate','as' => 'budgets.index.date']); diff --git a/app/start/global.php b/app/start/global.php index c5098db09d..10109df498 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -72,30 +72,51 @@ App::down( ); // forms: -\Form::macro('ffText', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffText($name, $value, $options); -}); -\Form::macro('ffSelect', function ($name, array $list = [], $selected = null, array $options = []) { - return \Firefly\Form\Form::ffSelect($name, $list, $selected, $options); -}); -\Form::macro('ffInteger', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffInteger($name, $value, $options); -}); -\Form::macro('ffAmount', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffAmount($name, $value, $options); -}); -\Form::macro('ffDate', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffDate($name, $value, $options); -}); -\Form::macro('ffTags', function ($name, $value = null, array $options = []) { - return \Firefly\Form\Form::ffTags($name, $value, $options); -}); -\Form::macro('ffCheckbox',function ($name, $value = 1, $checked = null, $options = []) { - return \Firefly\Form\Form::ffCheckbox($name, $value, $checked, $options); -}); -\Form::macro('ffOptionsList',function ($type, $name) { - return \Firefly\Form\Form::ffOptionsList($type, $name); -}); +\Form::macro( + 'ffText', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffText($name, $value, $options); + } +); +\Form::macro( + 'ffSelect', function ($name, array $list = [], $selected = null, array $options = []) { + return \Firefly\Form\Form::ffSelect($name, $list, $selected, $options); + } +); +\Form::macro( + 'ffInteger', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffInteger($name, $value, $options); + } +); +\Form::macro( + 'ffAmount', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffAmount($name, $value, $options); + } +); +\Form::macro( + 'ffBalance', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffBalance($name, $value, $options); + } +); +\Form::macro( + 'ffDate', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffDate($name, $value, $options); + } +); +\Form::macro( + 'ffTags', function ($name, $value = null, array $options = []) { + return \Firefly\Form\Form::ffTags($name, $value, $options); + } +); +\Form::macro( + 'ffCheckbox', function ($name, $value = 1, $checked = null, $options = []) { + return \Firefly\Form\Form::ffCheckbox($name, $value, $checked, $options); + } +); +\Form::macro( + 'ffOptionsList', function ($type, $name) { + return \Firefly\Form\Form::ffOptionsList($type, $name); + } +); /* diff --git a/app/views/accounts/create.blade.php b/app/views/accounts/create.blade.php index 6e3c7d2128..0a1c2912d9 100644 --- a/app/views/accounts/create.blade.php +++ b/app/views/accounts/create.blade.php @@ -4,98 +4,53 @@ {{Form::hidden('what',$what)}}
{{$errors->first('name')}}
- @else - @if($what == 'asset') - - Use something descriptive such as "checking account" or "My Bank Main Account". - - @endif - @if($what == 'expense') - - Use something descriptive such as "Albert Heijn" or "Amazon". - - @endif - @if($what == 'revenue') - - Use something descriptive such as "my mom" or "my job". - - @endif - @endif+ +
{{$errors->first('openingbalance')}}
- @else - What's the current balance of this new account? - @endif +{{$errors->first('openingbalancedate')}}
- @else - When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account. - @endif + + + +- Remember that deleting something is permanent. -
-- Account "{{{$account->name}}}" still has {{$account->transactions()->count()}} transaction(s) associated to it. - These will be deleted as well. -
- @endif ++ Are you sure? +
-- Press "Delete permanently" If you are sure you want to delete "{{{$account->name}}}". -
+ @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 +
+{{$errors->first('name')}}
- @else - Use something descriptive such as "checking account" or "Albert Heijn". - @endif - ++ +
{{$errors->first('openingbalance')}}
- @else - What's the current balance of this new account? +{{$errors->first('openingbalancedate')}}
- @else - When was this the balance of the new account? Since your bank statements may lag behind, update this date to match the date of the last known balance of the account. - @endif + + +- Accounts are the record holders for transactions and transfers. Money moves from one account to another. -
-- In a double-entry bookkeeping system almost everything is an account. Your own personal - bank accounts are representated as accounts (naturally), but the stores you buy stuff at are also - represented as accounts. Likewise, if you have a job, your salary is drawn from their account. -
- +Name | +balance | +ID | +
---|
- These are your personal accounts. -
+@stop +@section('scripts') +{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}} +{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}} + + +@stop - @include('accounts.list',['accounts' => $accounts['personal']]) - @endif - - @if(count($accounts['beneficiaries']) > 0) -- These are beneficiaries; places where you spend money or people who pay you. -
- @include('accounts.list',['accounts' => $accounts['beneficiaries']]) - @endif - -