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