From 7d4006b20597eb8f0f275fe0bd66dcf01c3fbd55 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 07:14:01 +0100 Subject: [PATCH 01/71] Fixed some bugs while registering users. --- app/controllers/UserController.php | 9 ++++++++- .../FireflyIII/Shared/Mail/Registration.php | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index 82309e2460..2a4af46abb 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -77,7 +77,11 @@ class UserController extends BaseController $user = $repository->register(Input::all()); if ($user) { - $email->sendVerificationMail($user); + $result = $email->sendVerificationMail($user); + if($result === false) { + $user->delete(); + return View::make('error')->with('message','The email message could not be send. See the log files.'); + } return View::make('user.verification-pending'); } @@ -121,6 +125,9 @@ class UserController extends BaseController */ public function register() { + if (Config::get('mail.from.address') == '@gmail.com' || Config::get('mail.from.address') == '') { + return View::make('error')->with('message', 'Configuration error in app/config/'.App::environment().'/mail.php'); + } return View::make('user.register'); } diff --git a/app/lib/FireflyIII/Shared/Mail/Registration.php b/app/lib/FireflyIII/Shared/Mail/Registration.php index 4a8367c408..62f98eefe9 100644 --- a/app/lib/FireflyIII/Shared/Mail/Registration.php +++ b/app/lib/FireflyIII/Shared/Mail/Registration.php @@ -3,6 +3,8 @@ namespace FireflyIII\Shared\Mail; use Swift_RfcComplianceException; use Illuminate\Mail\Message; +use Swift_TransportException; + /** * Class Registration * @@ -57,7 +59,16 @@ class Registration implements RegistrationInterface } ); } catch (Swift_RfcComplianceException $e) { + \Log::error($e->getMessage()); + return false; + } catch(Swift_TransportException $e) { + \Log::error($e->getMessage()); + return false; + } catch(\Exception $e) { + \Log::error($e->getMessage()); + return false; } + return true; } @@ -84,7 +95,16 @@ class Registration implements RegistrationInterface } ); } catch (Swift_RfcComplianceException $e) { + \Log::error($e->getMessage()); + return false; + } catch(Swift_TransportException $e) { + \Log::error($e->getMessage()); + return false; + } catch(\Exception $e) { + \Log::error($e->getMessage()); + return false; } + return true; } } From a854b2c17ef512388a7e8109c8c201f247617145 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 07:25:44 +0100 Subject: [PATCH 02/71] Some code cleanup in the account code. --- app/controllers/GoogleChartController.php | 2 +- app/controllers/HomeController.php | 4 +- app/controllers/JsonController.php | 4 +- app/controllers/PiggybankController.php | 4 +- app/controllers/PreferencesController.php | 2 +- app/controllers/RepeatedExpenseController.php | 4 +- app/controllers/UserController.php | 7 +- .../FireflyIII/Database/Account/Account.php | 188 +++--------------- .../Database/Account/AccountInterface.php | 46 ----- app/lib/FireflyIII/Report/Report.php | 2 +- 10 files changed, 40 insertions(+), 223 deletions(-) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index f413c9c5a3..98696a972f 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -88,7 +88,7 @@ class GoogleChartController extends BaseController /** @var \FireflyIII\Database\Account\Account $acct */ $acct = App::make('FireflyIII\Database\Account\Account'); - $accounts = count($pref->data) > 0 ? $acct->getByIds($pref->data) : $acct->getAssetAccounts(); + $accounts = count($pref->data) > 0 ? $acct->getByIds($pref->data) : $acct->getAccountsByType(['Default account', 'Asset account']); /** @var Account $account */ foreach ($accounts as $account) { diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php index 33bd974e5e..485f71cb15 100644 --- a/app/controllers/HomeController.php +++ b/app/controllers/HomeController.php @@ -33,7 +33,7 @@ class HomeController extends BaseController /** @var \FireflyIII\Shared\Preferences\PreferencesInterface $preferences */ $preferences = App::make('FireflyIII\Shared\Preferences\PreferencesInterface'); - $count = $acct->countAssetAccounts(); + $count = $acct->countAccountsByType(['Default account', 'Asset account']); $start = Session::get('start', Carbon::now()->startOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth()); @@ -42,7 +42,7 @@ class HomeController extends BaseController // get the preference for the home accounts to show: $frontPage = $preferences->get('frontPageAccounts', []); if ($frontPage->data == []) { - $accounts = $acct->getAssetAccounts(); + $accounts = $acct->getAccountsByType(['Default account', 'Asset account']); } else { $accounts = $acct->getByIds($frontPage->data); } diff --git a/app/controllers/JsonController.php b/app/controllers/JsonController.php index fedc5768d2..a0e6f08262 100644 --- a/app/controllers/JsonController.php +++ b/app/controllers/JsonController.php @@ -36,7 +36,7 @@ class JsonController extends BaseController { /** @var \FireflyIII\Database\Account\Account $accounts */ $accounts = App::make('FireflyIII\Database\Account\Account'); - $list = $accounts->getExpenseAccounts(); + $list = $accounts->getAccountsByType(['Expense account', 'Beneficiary account']); $return = []; foreach ($list as $entry) { $return[] = $entry->name; @@ -53,7 +53,7 @@ class JsonController extends BaseController { /** @var \FireflyIII\Database\Account\Account $accounts */ $accounts = App::make('FireflyIII\Database\Account\Account'); - $list = $accounts->getRevenueAccounts(); + $list = $accounts->getAccountsByType(['Revenue account']); $return = []; foreach ($list as $entry) { $return[] = $entry->name; diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 69222f22fa..16089486f2 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -62,7 +62,7 @@ class PiggyBankController extends BaseController $acct = App::make('FireflyIII\Database\Account\Account'); $periods = Config::get('firefly.piggy_bank_periods'); - $accounts = FFForm::makeSelectList($acct->getAssetAccounts()); + $accounts = FFForm::makeSelectList($acct->getAccountsByType(['Default account', 'Asset account'])); $subTitle = 'Create new piggy bank'; $subTitleIcon = 'fa-plus'; @@ -107,7 +107,7 @@ class PiggyBankController extends BaseController $acct = App::make('FireflyIII\Database\Account\Account'); $periods = Config::get('firefly.piggy_bank_periods'); - $accounts = FFForm::makeSelectList($acct->getAssetAccounts()); + $accounts = FFForm::makeSelectList($acct->getAccountsByType(['Default account', 'Asset account'])); $subTitle = 'Edit piggy bank "' . e($piggyBank->name) . '"'; $subTitleIcon = 'fa-pencil'; diff --git a/app/controllers/PreferencesController.php b/app/controllers/PreferencesController.php index b1a5ca0fc7..ab4b81f717 100644 --- a/app/controllers/PreferencesController.php +++ b/app/controllers/PreferencesController.php @@ -29,7 +29,7 @@ class PreferencesController extends BaseController /** @var \FireflyIII\Shared\Preferences\Preferences $preferences */ $preferences = App::make('FireflyIII\Shared\Preferences\Preferences'); - $accounts = $acct->getAssetAccounts(); + $accounts = $acct->getAccountsByType(['Default account', 'Asset account']); $viewRange = $preferences->get('viewRange', '1M'); $viewRangeValue = $viewRange->data; $frontPage = $preferences->get('frontPageAccounts', []); diff --git a/app/controllers/RepeatedExpenseController.php b/app/controllers/RepeatedExpenseController.php index 652e255578..78272a9453 100644 --- a/app/controllers/RepeatedExpenseController.php +++ b/app/controllers/RepeatedExpenseController.php @@ -34,7 +34,7 @@ class RepeatedExpenseController extends BaseController /** @var \FireflyIII\Database\Account\Account $acct */ $acct = App::make('FireflyIII\Database\Account\Account'); $periods = Config::get('firefly.piggy_bank_periods'); - $accounts = FFForm::makeSelectList($acct->getAssetAccounts()); + $accounts = FFForm::makeSelectList($acct->getAccountsByType(['Default account', 'Asset account'])); return View::make('repeatedExpense.create', compact('accounts', 'periods'))->with('subTitle', 'Create new repeated expense')->with( 'subTitleIcon', 'fa-plus' @@ -79,7 +79,7 @@ class RepeatedExpenseController extends BaseController $acct = App::make('FireflyIII\Database\Account\Account'); $periods = Config::get('firefly.piggy_bank_periods'); - $accounts = FFForm::makeSelectList($acct->getAssetAccounts()); + $accounts = FFForm::makeSelectList($acct->getAccountsByType(['Default account', 'Asset account'])); $subTitle = 'Edit repeated expense "' . e($repeatedExpense->name) . '"'; $subTitleIcon = 'fa-pencil'; diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index 2a4af46abb..74be723b18 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -78,9 +78,10 @@ class UserController extends BaseController if ($user) { $result = $email->sendVerificationMail($user); - if($result === false) { + if ($result === false) { $user->delete(); - return View::make('error')->with('message','The email message could not be send. See the log files.'); + + return View::make('error')->with('message', 'The email message could not be send. See the log files.'); } return View::make('user.verification-pending'); @@ -126,7 +127,7 @@ class UserController extends BaseController public function register() { if (Config::get('mail.from.address') == '@gmail.com' || Config::get('mail.from.address') == '') { - return View::make('error')->with('message', 'Configuration error in app/config/'.App::environment().'/mail.php'); + return View::make('error')->with('message', 'Configuration error in app/config/' . App::environment() . '/mail.php'); } return View::make('user.register'); diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index 368aeecac6..257ae7b79a 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -41,47 +41,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte return $this->getUser()->accounts()->accountTypeIn($types)->count(); } - /** - * @return int - */ - public function countAssetAccounts() - { - return $this->countAccountsByType(['Default account', 'Asset account']); - } - - /** - * @return int - */ - public function countExpenseAccounts() - { - return $this->countAccountsByType(['Expense account', 'Beneficiary account']); - } - - /** - * Counts the number of total revenue accounts. Useful for DataTables. - * - * @return int - */ - public function countRevenueAccounts() - { - return $this->countAccountsByType(['Revenue account']); - } - - /** - * @param \Account $account - * - * @return \Account|null - */ - public function findInitialBalanceAccount(\Account $account) - { - /** @var \FireflyIII\Database\AccountType\AccountType $acctType */ - $acctType = \App::make('FireflyIII\Database\AccountType\AccountType'); - - $accountType = $acctType->findByWhat('initial'); - - return $this->getUser()->accounts()->where('account_type_id', $accountType->id)->where('name', 'LIKE', $account->name . '%')->first(); - } - /** * @param array $types * @@ -107,57 +66,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte return $set; } - /** - * Get all asset accounts. Optional JSON based parameters. - * - * @param array $metaFilter - * - * @return Collection - */ - public function getAssetAccounts($metaFilter = []) - { - $list = $this->getAccountsByType(['Default account', 'Asset account']); - $list->each( - function (\Account $account) { - - // get accountRole: - - /** @var \AccountMeta $entry */ - $accountRole = $account->accountmeta()->whereName('accountRole')->first(); - if (!$accountRole) { - $accountRole = new \AccountMeta; - $accountRole->account_id = $account->id; - $accountRole->name = 'accountRole'; - $accountRole->data = 'defaultExpense'; - $accountRole->save(); - - } - $account->accountRole = $accountRole->data; - } - ); - - return $list; - - } - - /** - * @return Collection - */ - public function getExpenseAccounts() - { - return $this->getAccountsByType(['Expense account', 'Beneficiary account']); - } - - /** - * Get all revenue accounts. - * - * @return Collection - */ - public function getRevenueAccounts() - { - return $this->getAccountsByType(['Revenue account']); - } - /** * @param \Account $account * @@ -235,6 +143,31 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } + /** + * @param \Account $account + * + * @return int + */ + public function getLastActivity(\Account $account) + { + $lastActivityKey = 'account.' . $account->id . '.lastActivityDate'; + if (\Cache::has($lastActivityKey)) { + return \Cache::get($lastActivityKey); + } + + $transaction = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'DESC')->first(); + if ($transaction) { + $date = $transaction->transactionJournal->date; + } else { + $date = 0; + } + \Cache::forever($lastActivityKey, $date); + + return $date; + } + /** * @param Eloquent $model * @@ -571,57 +504,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } - /** - * @param \Account $account - * @param int $limit - * - * @return \Illuminate\Pagination\Paginator - */ - public function getAllTransactionJournals(\Account $account, $limit = 50) - { - $offset = intval(\Input::get('page')) > 0 ? intval(\Input::get('page')) * $limit : 0; - $set = $this->getUser()->transactionJournals()->withRelevantData()->leftJoin( - 'transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id' - )->where('transactions.account_id', $account->id)->take($limit)->offset($offset)->orderBy('date', 'DESC')->get( - ['transaction_journals.*'] - ); - $count = $this->getUser()->transactionJournals()->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->orderBy('date', 'DESC')->where('transactions.account_id', $account->id)->count(); - $items = []; - foreach ($set as $entry) { - $items[] = $entry; - } - - return \Paginator::make($items, $count, $limit); - - - } - - /** - * @param \Account $account - * - * @return int - */ - public function getLastActivity(\Account $account) - { - $lastActivityKey = 'account.' . $account->id . '.lastActivityDate'; - if (\Cache::has($lastActivityKey)) { - return \Cache::get($lastActivityKey); - } - - $transaction = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'DESC')->first(); - if ($transaction) { - $date = $transaction->transactionJournal->date; - } else { - $date = 0; - } - \Cache::forever($lastActivityKey, $date); - - return $date; - } - /** * @param \Account $account * @param int $limit @@ -656,24 +538,4 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } - /** - * @param \Account $account - * @param Carbon $start - * @param Carbon $end - * - * @return \Illuminate\Pagination\Paginator - */ - public function getTransactionJournalsInRange(\Account $account, Carbon $start, Carbon $end) - { - $set = $this->getUser()->transactionJournals()->transactionTypes(['Withdrawal'])->withRelevantData()->leftJoin( - 'transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id' - )->where('transactions.account_id', $account->id)->before($end)->after($start)->orderBy('date', 'DESC')->get( - ['transaction_journals.*'] - ); - - return $set; - - } - - } diff --git a/app/lib/FireflyIII/Database/Account/AccountInterface.php b/app/lib/FireflyIII/Database/Account/AccountInterface.php index 336df90401..c9d21ba229 100644 --- a/app/lib/FireflyIII/Database/Account/AccountInterface.php +++ b/app/lib/FireflyIII/Database/Account/AccountInterface.php @@ -21,34 +21,6 @@ interface AccountInterface */ public function countAccountsByType(array $types); - /** - * Counts the number of total asset accounts. Useful for DataTables. - * - * @return int - */ - public function countAssetAccounts(); - - /** - * Counts the number of total expense accounts. Useful for DataTables. - * - * @return int - */ - public function countExpenseAccounts(); - - /** - * Counts the number of total revenue accounts. Useful for DataTables. - * - * @return int - */ - public function countRevenueAccounts(); - - /** - * @param \Account $account - * - * @return \Account|null - */ - public function findInitialBalanceAccount(\Account $account); - /** * Get all accounts of the selected types. Is also capable of handling DataTables' parameters. * @@ -58,24 +30,6 @@ interface AccountInterface */ public function getAccountsByType(array $types); - /** - * Get all asset accounts. The parameters are optional and are provided by the DataTables plugin. - * - * @return Collection - */ - public function getAssetAccounts(); - - /** - * @return Collection - */ - public function getExpenseAccounts(); - - /** - * Get all revenue accounts. - * - * @return Collection - */ - public function getRevenueAccounts(); /** * @param \Account $account diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index 56deca08ec..f3040fe30f 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -387,7 +387,7 @@ class Report implements ReportInterface $sharedAccounts[] = $account->id; } - $accounts = $this->_accounts->getAssetAccounts()->filter( + $accounts = $this->_accounts->getAccountsByType(['Default account', 'Asset account'])->filter( function (\Account $account) use ($sharedAccounts) { if (!in_array($account->id, $sharedAccounts)) { return $account; From d9c2df5b0d44c8d00a9c526d5170f098b53ad6a9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 07:33:43 +0100 Subject: [PATCH 03/71] Removed unused methods. --- app/lib/FireflyIII/Database/Bill/Bill.php | 13 -- .../Database/Bill/BillInterface.php | 12 -- app/lib/FireflyIII/Database/Budget/Budget.php | 175 +++++++----------- .../FireflyIII/Database/Category/Category.php | 13 -- 4 files changed, 68 insertions(+), 145 deletions(-) diff --git a/app/lib/FireflyIII/Database/Bill/Bill.php b/app/lib/FireflyIII/Database/Bill/Bill.php index 6e17ffe827..9c9683b7cf 100644 --- a/app/lib/FireflyIII/Database/Bill/Bill.php +++ b/app/lib/FireflyIII/Database/Bill/Bill.php @@ -189,19 +189,6 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface return $this->getUser()->bills()->where('active', 1)->get(); } - /** - * @param \Bill $bill - * @param Carbon $start - * @param Carbon $end - * - * @return \TransactionJournal|null - */ - public function getJournalForBillInRange(\Bill $bill, Carbon $start, Carbon $end) - { - return $this->getUser()->transactionjournals()->where('bill_id', $bill->id)->after($start)->before($end)->first(); - - } - /** * @param \Bill $bill * diff --git a/app/lib/FireflyIII/Database/Bill/BillInterface.php b/app/lib/FireflyIII/Database/Bill/BillInterface.php index f6dca6325f..ec75dd1167 100644 --- a/app/lib/FireflyIII/Database/Bill/BillInterface.php +++ b/app/lib/FireflyIII/Database/Bill/BillInterface.php @@ -11,18 +11,6 @@ use Carbon\Carbon; */ interface BillInterface { - /** - * @param \Bill $bill - * @param Carbon $start - * @param Carbon $end - * - * @return null|\TransactionJournal - * @internal param Carbon $current - * @internal param Carbon $currentEnd - * - */ - public function getJournalForBillInRange(\Bill $bill, Carbon $start, Carbon $end); - /** * @param \Bill $bill * diff --git a/app/lib/FireflyIII/Database/Budget/Budget.php b/app/lib/FireflyIII/Database/Budget/Budget.php index 05f712a259..aaf6eabe3c 100644 --- a/app/lib/FireflyIII/Database/Budget/Budget.php +++ b/app/lib/FireflyIII/Database/Budget/Budget.php @@ -8,9 +8,9 @@ use FireflyIII\Database\SwitchUser; use FireflyIII\Exception\FireflyException; use FireflyIII\Exception\NotImplementedException; use Illuminate\Database\Eloquent\Model as Eloquent; +use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; -use Illuminate\Database\Query\Builder as QueryBuilder; /** * Class Budget @@ -97,6 +97,71 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes]; } + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function expenseNoBudget(Carbon $start, Carbon $end) + { + // Add expenses that have no budget: + return $this->getUser() + ->transactionjournals() + ->whereNotIn( + 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { + $query + ->select('transaction_journals.id') + ->from('transaction_journals') + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); + } + ) + ->before($end) + ->after($start) + ->lessThan(0) + ->transactionTypes(['Withdrawal']) + ->get(); + } + + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function journalsNoBudget(Carbon $start, Carbon $end) + { + $set = $this->getUser() + ->transactionjournals() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('budget_transaction_journal.id') + ->before($end) + ->after($start) + ->orderBy('transaction_journals.date') + ->get(['transaction_journals.*']); + + return $set; + } + + /** + * This method includes the time because otherwise, SQLite does not understand it. + * + * @param \Budget $budget + * @param Carbon $date + * + * @return \LimitRepetition|null + */ + public function repetitionOnStartingOnDate(\Budget $budget, Carbon $date) + { + return \LimitRepetition:: + leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') + ->where('limit_repetitions.startdate', $date->format('Y-m-d 00:00:00')) + ->where('budget_limits.budget_id', $budget->id) + ->first(['limit_repetitions.*']); + } + /** * Returns an object with id $id. * @@ -210,96 +275,6 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf } - /** - * @param \Budget $budget - * @param \LimitRepetition $repetition - * @param int $limit - * - * @return \Illuminate\Pagination\Paginator - */ - public function getTransactionJournalsInRepetition(\Budget $budget, \LimitRepetition $repetition, $limit = 50) - { - $start = $repetition->startdate; - $end = $repetition->enddate; - - $offset = intval(\Input::get('page')) > 0 ? intval(\Input::get('page')) * $limit : 0; - $set = $budget->transactionJournals()->withRelevantData()->before($end)->after($start)->take($limit)->offset($offset)->orderBy('date', 'DESC')->get( - ['transaction_journals.*'] - ); - $count = $budget->transactionJournals()->before($end)->after($start)->count(); - $items = []; - foreach ($set as $entry) { - $items[] = $entry; - } - - return \Paginator::make($items, $count, $limit); - } - - /** - * This method includes the time because otherwise, SQLite does not understand it. - * - * @param \Budget $budget - * @param Carbon $date - * - * @return \LimitRepetition|null - */ - public function repetitionOnStartingOnDate(\Budget $budget, Carbon $date) - { - return \LimitRepetition:: - leftJoin('budget_limits', 'limit_repetitions.budget_limit_id', '=', 'budget_limits.id') - ->where('limit_repetitions.startdate', $date->format('Y-m-d 00:00:00')) - ->where('budget_limits.budget_id', $budget->id) - ->first(['limit_repetitions.*']); - } - - /** - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function expenseNoBudget(Carbon $start, Carbon $end) - { - // Add expenses that have no budget: - return $this->getUser() - ->transactionjournals() - ->whereNotIn( - 'transaction_journals.id', function (QueryBuilder $query) use ($start, $end) { - $query - ->select('transaction_journals.id') - ->from('transaction_journals') - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); - } - ) - ->before($end) - ->after($start) - ->lessThan(0) - ->transactionTypes(['Withdrawal']) - ->get(); - } - - /** - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function journalsNoBudget(Carbon $start, Carbon $end) - { - $set = $this->getUser() - ->transactionjournals() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('budget_transaction_journal.id') - ->before($end) - ->after($start) - ->orderBy('transaction_journals.date') - ->get(['transaction_journals.*']); - - return $set; - } - /** * @param \Budget $budget * @param Carbon $date @@ -316,20 +291,6 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf return $sum; } - /** - * @param \Budget $budget - * @param Carbon $start - * @param Carbon $end - * - * @return float - */ - public function spentInPeriod(\Budget $budget, Carbon $start, Carbon $end) - { - $sum = floatval($budget->transactionjournals()->before($end)->after($start)->lessThan(0)->sum('amount')) * -1; - - return $sum; - } - /** * This method updates the amount (envelope) for the given date and budget. This results in a (new) limit (aka an envelope) * for that budget. Returned to the user is the new limit repetition. @@ -343,7 +304,7 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf */ public function updateLimitAmount(\Budget $budget, Carbon $date, $amount) { - /** @var \Limit $limit */ + /** @var \BudgetLimit $limit */ $limit = $this->limitOnStartingOnDate($budget, $date); if (!$limit) { // create one! @@ -381,7 +342,7 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf * @param \Budget $budget * @param Carbon $date * - * @return \Limit + * @return \BudgetLimit */ public function limitOnStartingOnDate(\Budget $budget, Carbon $date) { diff --git a/app/lib/FireflyIII/Database/Category/Category.php b/app/lib/FireflyIII/Database/Category/Category.php index 873ac9c312..14d8b64da3 100644 --- a/app/lib/FireflyIII/Database/Category/Category.php +++ b/app/lib/FireflyIII/Database/Category/Category.php @@ -195,19 +195,6 @@ class Category implements CUDInterface, CommonDatabaseCallsInterface return $set; } - /** - * @param \Category $category - * @param Carbon $date - * - * @return null - * @throws NotImplementedException - * @internal param \Category $budget - */ - public function repetitionOnStartingOnDate(\Category $category, Carbon $date) - { - throw new NotImplementedException; - } - /** * @param \Category $category * @param Carbon $date From fa7a59572a34d61ca394475d365abd74615e254f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 08:57:55 +0100 Subject: [PATCH 04/71] Code cleanup for all controllers. --- app/controllers/BillController.php | 23 +- app/controllers/BudgetController.php | 3 - app/controllers/CategoryController.php | 1 - app/controllers/CurrencyController.php | 1 - app/controllers/GoogleChartController.php | 4 - app/controllers/HelpController.php | 78 ++- app/controllers/PiggybankController.php | 3 - app/controllers/PreferencesController.php | 1 - app/controllers/ProfileController.php | 42 +- app/controllers/RelatedController.php | 5 + app/controllers/RepeatedExpenseController.php | 2 - app/controllers/TransactionController.php | 5 - app/database/seeds/DefaultUserSeeder.php | 22 - app/database/seeds/TestContentSeeder.php | 561 ------------------ 14 files changed, 99 insertions(+), 652 deletions(-) delete mode 100644 app/database/seeds/DefaultUserSeeder.php delete mode 100644 app/database/seeds/TestContentSeeder.php diff --git a/app/controllers/BillController.php b/app/controllers/BillController.php index 521c21736b..0fdd0f7309 100644 --- a/app/controllers/BillController.php +++ b/app/controllers/BillController.php @@ -5,8 +5,7 @@ use FireflyIII\Exception\FireflyException; /** * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("NPathComplexity") + * * Class BillController * */ @@ -120,9 +119,9 @@ class BillController extends BaseController */ public function show(Bill $bill) { - $journals = $bill->transactionjournals()->withRelevantData()->orderBy('date', 'DESC')->get(); + $journals = $bill->transactionjournals()->withRelevantData()->orderBy('date', 'DESC')->get(); $bill->nextExpectedMatch = $this->_repository->nextExpectedMatch($bill); - $hideBill = true; + $hideBill = true; return View::make('bills.show', compact('journals', 'hideBill', 'bill'))->with( @@ -136,7 +135,7 @@ class BillController extends BaseController */ public function store() { - $data = Input::all(); + $data = Input::except(['_token', 'post_submit_action']); $data['user_id'] = Auth::user()->id; @@ -149,17 +148,19 @@ class BillController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not store bill: ' . $messages['errors']->first()); + + return Redirect::route('bills.create')->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if (Input::get('post_submit_action') == 'validate_only') { return Redirect::route('bills.create')->withInput(); } // store $this->_repository->store($data); Session::flash('success', 'Bill "' . e($data['name']) . '" stored.'); - if ($data['post_submit_action'] == 'store') { + if (Input::get('post_submit_action') == 'store') { return Redirect::route('bills.index'); } @@ -176,8 +177,8 @@ class BillController extends BaseController public function update(Bill $bill) { $data = Input::except('_token'); - $data['active'] = isset($data['active']) ? 1 : 0; - $data['automatch'] = isset($data['automatch']) ? 1 : 0; + $data['active'] = intval(Input::get('active')); + $data['automatch'] = intval(Input::get('automatch')); $data['user_id'] = Auth::user()->id; // always validate: @@ -189,10 +190,12 @@ class BillController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update bill: ' . $messages['errors']->first()); + + return Redirect::route('bills.edit', $bill->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('bills.edit', $bill->id)->withInput(); } diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index 500ed85573..9365d67d2c 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -8,9 +8,6 @@ use FireflyIII\Shared\Preferences\PreferencesInterface as Pref; * Class BudgetController * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("TooManyMethods") // I'm also fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("CouplingBetweenObjects") // There's only so much I can remove. * */ class BudgetController extends BaseController diff --git a/app/controllers/CategoryController.php b/app/controllers/CategoryController.php index 8459b4c40f..915562cfc2 100644 --- a/app/controllers/CategoryController.php +++ b/app/controllers/CategoryController.php @@ -6,7 +6,6 @@ use FireflyIII\Exception\FireflyException; /** * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. * * Class CategoryController */ diff --git a/app/controllers/CurrencyController.php b/app/controllers/CurrencyController.php index d8c2d5b5d6..5e79791af3 100644 --- a/app/controllers/CurrencyController.php +++ b/app/controllers/CurrencyController.php @@ -4,7 +4,6 @@ use FireflyIII\Database\TransactionCurrency\TransactionCurrency as Repository; /** * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. * * Class CurrencyController */ diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index 98696a972f..f2cfb3a0cf 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -6,10 +6,6 @@ use Grumpydictator\Gchart\GChart as GChart; /** * Class GoogleChartController * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("TooManyMethods") // I'm also fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("MethodLength") // There is one with 45 lines and im gonna move it. - * @SuppressWarnings("CouplingBetweenObjects") // There's only so much I can remove. */ class GoogleChartController extends BaseController { diff --git a/app/controllers/HelpController.php b/app/controllers/HelpController.php index 813b8faf5c..356f86b397 100644 --- a/app/controllers/HelpController.php +++ b/app/controllers/HelpController.php @@ -1,7 +1,6 @@ There is no help for this route!

'; - $helpTitle = 'Help'; + $content = [ + 'text' => '

There is no help for this route!

', + 'title' => 'Help', + ]; + if (!Route::has($route)) { \Log::error('No such route: ' . $route); - return Response::json(['title' => $helpTitle, 'text' => $helpText]); - } - if (Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text')) { - $helpText = Cache::get('help.' . $route . '.text'); - $helpTitle = Cache::get('help.' . $route . '.title'); - - return Response::json(['title' => $helpTitle, 'text' => $helpText]); + return Response::json($content); } - $uri = 'https://raw.githubusercontent.com/JC5/firefly-iii-help/master/' . e($route) . '.md'; - \Log::debug('URL is: ' . $uri); + if ($this->_inCache($route)) { + $content = [ + 'text' => Cache::get('help.' . $route . '.text'), + 'title' => Cache::get('help.' . $route . '.title'), + ]; + + return Response::json($content); + } + $content = $this->_getFromGithub($route); + + + Cache::put('help.' . $route . '.text', $content['text'], 10080); // a week. + Cache::put('help.' . $route . '.title', $content['title'], 10080); + + return Response::json($content); + + } + + /** + * @param $route + * + * @return bool + */ + protected function _inCache($route) + { + return Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text'); + } + + /** + * @param $route + * + * @return array + */ + protected function _getFromGithub($route) + { + $uri = 'https://raw.githubusercontent.com/JC5/firefly-iii-help/master/' . e($route) . '.md'; + $content = [ + 'text' => '

There is no help for this route!

', + 'title' => $route, + ]; try { - $helpText = file_get_contents($uri); + $content['text'] = file_get_contents($uri); } catch (ErrorException $e) { \Log::error(trim($e->getMessage())); } - \Log::debug('Found help for ' . $route); - \Log::debug('Help text length for route ' . $route . ' is ' . strlen($helpText)); - \Log::debug('Help text IS: "' . $helpText . '".'); - if (strlen(trim($helpText)) == 0) { - $helpText = '

There is no help for this route.

'; + if (strlen(trim($content['text'])) == 0) { + $content['text'] = '

There is no help for this route.

'; } + $content['text'] = \Michelf\Markdown::defaultTransform($content['text']); - $helpText = \Michelf\Markdown::defaultTransform($helpText); - $helpTitle = $route; + return $content; - Cache::put('help.' . $route . '.text', $helpText, 10080); // a week. - Cache::put('help.' . $route . '.title', $helpTitle, 10080); - - return Response::json(['title' => $helpTitle, 'text' => $helpText]); } -} +} + diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 16089486f2..1523206116 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -8,9 +8,6 @@ use Illuminate\Support\Collection; /** * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("TooManyMethods") // I'm also fine with this. - * @SuppressWarnings("CouplingBetweenObjects") // There's only so much I can remove. * * * Class PiggyBankController diff --git a/app/controllers/PreferencesController.php b/app/controllers/PreferencesController.php index ab4b81f717..d0d62d797d 100644 --- a/app/controllers/PreferencesController.php +++ b/app/controllers/PreferencesController.php @@ -3,7 +3,6 @@ /** * Class PreferencesController * - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. * */ class PreferencesController extends BaseController diff --git a/app/controllers/ProfileController.php b/app/controllers/ProfileController.php index e9b397833c..a9f9419bea 100644 --- a/app/controllers/ProfileController.php +++ b/app/controllers/ProfileController.php @@ -1,7 +1,6 @@ _validatePassword(Input::get('old'), Input::get('new1'), Input::get('new2')); + if (!($result === true)) { + Session::flash('error', $result); return View::make('profile.change-password'); } @@ -66,4 +55,29 @@ class ProfileController extends BaseController return Redirect::route('profile'); } + /** + * @param string $old + * @param string $new1 + * @param string $new2 + * + * @return string|bool + */ + protected function _validatePassword($old, $new1, $new2) + { + if (strlen($new1) == 0 || strlen($new2) == 0) { + return 'Do fill in a password!'; + + } + if ($new1 == $old) { + return 'The idea is to change your password.'; + } + + if ($new1 !== $new2) { + return 'New passwords do not match!'; + } + + return true; + + } + } diff --git a/app/controllers/RelatedController.php b/app/controllers/RelatedController.php index 747dad5e3f..ec533564bb 100644 --- a/app/controllers/RelatedController.php +++ b/app/controllers/RelatedController.php @@ -3,6 +3,8 @@ use FireflyIII\Helper\Related\RelatedInterface; use Illuminate\Support\Collection; /** + * @SuppressWarnings("CamelCase") // I'm fine with this. + * * Class RelatedController */ class RelatedController extends BaseController @@ -10,6 +12,9 @@ class RelatedController extends BaseController protected $_repository; + /** + * @param RelatedInterface $repository + */ public function __construct(RelatedInterface $repository) { $this->_repository = $repository; diff --git a/app/controllers/RepeatedExpenseController.php b/app/controllers/RepeatedExpenseController.php index 78272a9453..d9bf9f4002 100644 --- a/app/controllers/RepeatedExpenseController.php +++ b/app/controllers/RepeatedExpenseController.php @@ -6,8 +6,6 @@ use FireflyIII\Exception\FireflyException; /** * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("CouplingBetweenObjects") // There's only so much I can remove. * * Class RepeatedExpenseController */ diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index a7c9b12e71..5cef244661 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -9,10 +9,6 @@ use Illuminate\Support\Collection; /** * * @SuppressWarnings("CamelCase") // I'm fine with this. - * @SuppressWarnings("CyclomaticComplexity") // It's all 5. So ok. - * @SuppressWarnings("CouplingBetweenObjects") // There's only so much I can remove. - * @SuppressWarnings("TooManyMethods") // I'm also fine with this. - * @SuppressWarnings("ExcessiveClassComplexity") * * Class TransactionController * @@ -198,7 +194,6 @@ class TransactionController extends BaseController } - /** * @param TransactionJournal $journal * diff --git a/app/database/seeds/DefaultUserSeeder.php b/app/database/seeds/DefaultUserSeeder.php deleted file mode 100644 index 1b40e29097..0000000000 --- a/app/database/seeds/DefaultUserSeeder.php +++ /dev/null @@ -1,22 +0,0 @@ -delete(); - if (App::environment() == 'testing' || App::environment() == 'homestead') { - - User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => 'james', 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'acceptance@example.com', 'password' => 'acceptance', 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'functional@example.com', 'password' => 'functional', 'reset' => null, 'remember_token' => null]); - User::create(['email' => 'reset@example.com', 'password' => 'functional', 'reset' => 'okokokokokokokokokokokokokokokok', 'remember_token' => null]); - - } - - } - -} diff --git a/app/database/seeds/TestContentSeeder.php b/app/database/seeds/TestContentSeeder.php deleted file mode 100644 index e2cdebf7d7..0000000000 --- a/app/database/seeds/TestContentSeeder.php +++ /dev/null @@ -1,561 +0,0 @@ -_startOfMonth = Carbon::now()->startOfMonth(); - $this->som = $this->_startOfMonth->format('Y-m-d'); - - $this->_endOfMonth = Carbon::now()->endOfMonth(); - $this->eom = $this->_endOfMonth->format('Y-m-d'); - - $this->_nextStartOfMonth = Carbon::now()->addMonth()->startOfMonth(); - $this->nsom = $this->_nextStartOfMonth->format('Y-m-d'); - - $this->_nextEndOfMonth = Carbon::now()->addMonth()->endOfMonth(); - $this->neom = $this->_nextEndOfMonth->format('Y-m-d'); - - $this->_yearAgoStartOfMonth = Carbon::now()->subYear()->startOfMonth(); - $this->yasom = $this->_yearAgoStartOfMonth->format('Y-m-d'); - - $this->_yearAgoEndOfMonth = Carbon::now()->subYear()->startOfMonth(); - $this->yaeom = $this->_yearAgoEndOfMonth->format('Y-m-d'); - - - $this->_today = Carbon::now(); - $this->today = $this->_today->format('Y-m-d'); - } - - /** - * Dates are always this month, the start of this month or earlier. - */ - public function run() - { - if (App::environment() == 'testing' || App::environment() == 'homestead') { - - $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); - - // create initial accounts and various other stuff: - $this->createAssetAccounts($user); - $this->createBudgets($user); - $this->createCategories($user); - $this->createPiggyBanks($user); - $this->createReminders($user); - $this->createRecurringTransactions($user); - $this->createBills($user); - $this->createExpenseAccounts($user); - $this->createRevenueAccounts($user); - - // get some objects from the database: - $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); - $landLord = Account::whereName('Land lord')->orderBy('id', 'DESC')->first(); - $utilities = Account::whereName('Utilities company')->orderBy('id', 'DESC')->first(); - $television = Account::whereName('TV company')->orderBy('id', 'DESC')->first(); - $phone = Account::whereName('Phone agency')->orderBy('id', 'DESC')->first(); - $employer = Account::whereName('Employer')->orderBy('id', 'DESC')->first(); - - - $bills = Budget::whereName('Bills')->orderBy('id', 'DESC')->first(); - $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); - - $house = Category::whereName('House')->orderBy('id', 'DESC')->first(); - - - $withdrawal = TransactionType::whereType('Withdrawal')->first(); - $deposit = TransactionType::whereType('Deposit')->first(); - $transfer = TransactionType::whereType('Transfer')->first(); - - $euro = TransactionCurrency::whereCode('EUR')->first(); - - $rentBill = Bill::where('name', 'Rent')->first(); - - - $current = clone $this->_yearAgoStartOfMonth; - while ($current <= $this->_startOfMonth) { - $cur = $current->format('Y-m-d'); - $formatted = $current->format('F Y'); - - // create expenses for rent, utilities, TV, phone on the 1st of the month. - $this->createTransaction($checking, $landLord, 800, $withdrawal, 'Rent for ' . $formatted, $cur, $euro, $bills, $house, $rentBill); - $this->createTransaction($checking, $utilities, 150, $withdrawal, 'Utilities for ' . $formatted, $cur, $euro, $bills, $house); - $this->createTransaction($checking, $television, 50, $withdrawal, 'TV for ' . $formatted, $cur, $euro, $bills, $house); - $this->createTransaction($checking, $phone, 50, $withdrawal, 'Phone bill for ' . $formatted, $cur, $euro, $bills, $house); - - // two transactions. One without a budget, one without a category. - $this->createTransaction($checking, $phone, 10, $withdrawal, 'Extra charges on phone bill for ' . $formatted, $cur, $euro, null, $house); - $this->createTransaction($checking, $television, 5, $withdrawal, 'Extra charges on TV bill for ' . $formatted, $cur, $euro, $bills, null); - - // income from job: - $this->createTransaction($employer, $checking, rand(3500, 4000), $deposit, 'Salary for ' . $formatted, $cur, $euro); - $this->createTransaction($checking, $savings, 2000, $transfer, 'Salary to savings account in ' . $formatted, $cur, $euro); - - $this->createGroceries($current); - $this->createBigExpense(clone $current); - - echo 'Created test-content for ' . $current->format('F Y') . "\n"; - $current->addMonth(); - } - - - // piggy bank event - // add money to this piggy bank - // create a piggy bank event to match: - $piggyBank = PiggyBank::whereName('New camera')->orderBy('id', 'DESC')->first(); - $intoPiggy = $this->createTransaction($checking, $savings, 100, $transfer, 'Money for piggy', $this->yaeom, $euro, $groceries, $house); - PiggyBankEvent::create( - [ - 'piggy_bank_id' => $piggyBank->id, - 'transaction_journal_id' => $intoPiggy->id, - 'date' => $this->yaeom, - 'amount' => 100 - ] - ); - } - } - - /** - * @param User $user - */ - public function createAssetAccounts(User $user) - { - $assetType = AccountType::whereType('Asset account')->first(); - $ibType = AccountType::whereType('Initial balance account')->first(); - $obType = TransactionType::whereType('Opening balance')->first(); - $euro = TransactionCurrency::whereCode('EUR')->first(); - - - $acc_a = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Checking account', 'active' => 1]); - $acc_b = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Savings account', 'active' => 1]); - $acc_c = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Delete me', 'active' => 1]); - - $acc_d = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Checking account initial balance', 'active' => 0]); - $acc_e = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Savings account initial balance', 'active' => 0]); - $acc_f = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Delete me initial balance', 'active' => 0]); - - - $this->createTransaction($acc_d, $acc_a, 4000, $obType, 'Initial Balance for Checking account', $this->yasom, $euro); - $this->createTransaction($acc_e, $acc_b, 10000, $obType, 'Initial Balance for Savings account', $this->yasom, $euro); - $this->createTransaction($acc_f, $acc_c, 100, $obType, 'Initial Balance for Delete me', $this->yasom, $euro); - } - - /** - * @param Account $from - * @param Account $to - * @param $amount - * @param TransactionType $type - * @param $description - * @param $date - * @param TransactionCurrency $currency - * - * @param Budget $budget - * @param Category $category - * @param Bill $bill - * - * @return TransactionJournal - */ - public function createTransaction( - Account $from, Account $to, $amount, TransactionType $type, $description, $date, TransactionCurrency $currency, Budget $budget = null, - Category $category = null, Bill $bill = null - ) { - $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); - - $billID = is_null($bill) ? null : $bill->id; - - - /** @var TransactionJournal $journal */ - $journal = TransactionJournal::create( - [ - 'user_id' => $user->id, 'transaction_type_id' => $type->id, 'transaction_currency_id' => $currency->id, 'bill_id' => $billID, - 'description' => $description, 'completed' => 1, 'date' => $date - ] - ); - - Transaction::create(['account_id' => $from->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount * -1]); - Transaction::create(['account_id' => $to->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount]); - - if (!is_null($budget)) { - $journal->budgets()->save($budget); - } - if (!is_null($category)) { - $journal->categories()->save($category); - } - - return $journal; - } - - /** - * @param User $user - */ - public function createBudgets(User $user) - { - - $groceries = Budget::create(['user_id' => $user->id, 'name' => 'Groceries']); - $bills = Budget::create(['user_id' => $user->id, 'name' => 'Bills']); - $deleteMe = Budget::create(['user_id' => $user->id, 'name' => 'Delete me']); - Budget::create(['user_id' => $user->id, 'name' => 'Budget without repetition']); - $groceriesLimit = BudgetLimit::create( - ['startdate' => $this->som, 'amount' => 201, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $groceries->id] - ); - $billsLimit = BudgetLimit::create( - ['startdate' => $this->som, 'amount' => 202, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $bills->id] - ); - $deleteMeLimit = BudgetLimit::create( - ['startdate' => $this->som, 'amount' => 203, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $deleteMe->id] - ); - - // and because we have no filters, some repetitions: - LimitRepetition::create(['budget_limit_id' => $groceriesLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 201]); - LimitRepetition::create(['budget_limit_id' => $billsLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 202]); - LimitRepetition::create(['budget_limit_id' => $deleteMeLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 203]); - } - - /** - * @param User $user - */ - public function createCategories(User $user) - { - Category::create(['user_id' => $user->id, 'name' => 'DailyGroceries']); - Category::create(['user_id' => $user->id, 'name' => 'Lunch']); - Category::create(['user_id' => $user->id, 'name' => 'House']); - Category::create(['user_id' => $user->id, 'name' => 'Delete me']); - - } - - /** - * @param User $user - */ - public function createPiggyBanks(User $user) - { - // account - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); - - // some dates - $endDate = clone $this->_startOfMonth; - $nextYear = clone $this->_startOfMonth; - - $endDate->addMonths(4); - $nextYear->addYear()->subDay(); - - $next = $nextYear->format('Y-m-d'); - $end = $endDate->format('Y-m-d'); - - // piggy bank - $newCamera = PiggyBank::create( - [ - 'account_id' => $savings->id, - 'name' => 'New camera', - 'targetamount' => 2000, - 'startdate' => $this->som, - 'targetdate' => null, - 'repeats' => 0, - 'rep_length' => null, - 'rep_every' => 0, - 'rep_times' => null, - 'reminder' => null, - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 0, - ] - ); - // and some events! - PiggyBankEvent::create(['piggy_bank_id' => $newCamera->id, 'date' => $this->som, 'amount' => 100]); - PiggyBankRepetition::create(['piggy_bank_id' => $newCamera->id, 'startdate' => $this->som, 'targetdate' => null, 'currentamount' => 100]); - - - $newClothes = PiggyBank::create( - [ - 'account_id' => $savings->id, - 'name' => 'New clothes', - 'targetamount' => 2000, - 'startdate' => $this->som, - 'targetdate' => $end, - 'repeats' => 0, - 'rep_length' => null, - 'rep_every' => 0, - 'rep_times' => null, - 'reminder' => null, - 'reminder_skip' => 0, - 'remind_me' => 0, - 'order' => 0, - ] - ); - - PiggyBankEvent::create(['piggy_bank_id' => $newClothes->id, 'date' => $this->som, 'amount' => 100]); - PiggyBankRepetition::create(['piggy_bank_id' => $newClothes->id, 'startdate' => $this->som, 'targetdate' => $end, 'currentamount' => 100]); - - // weekly reminder piggy bank - $weekly = PiggyBank::create( - [ - 'account_id' => $savings->id, - 'name' => 'Weekly reminder for clothes', - 'targetamount' => 2000, - 'startdate' => $this->som, - 'targetdate' => $next, - 'repeats' => 0, - 'rep_length' => null, - 'rep_every' => 0, - 'rep_times' => null, - 'reminder' => 'week', - 'reminder_skip' => 0, - 'remind_me' => 1, - 'order' => 0, - ] - ); - PiggyBankRepetition::create(['piggy_bank_id' => $weekly->id, 'startdate' => $this->som, 'targetdate' => $next, 'currentamount' => 0]); - } - - /** - * @param User $user - */ - public function createReminders(User $user) - { - // for weekly piggy bank (clothes) - $nextWeek = clone $this->_startOfMonth; - $piggyBank = PiggyBank::whereName('New clothes')->orderBy('id', 'DESC')->first(); - $nextWeek->addWeek(); - $week = $nextWeek->format('Y-m-d'); - - Reminder::create( - ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $week, 'active' => 1, 'notnow' => 0, - 'remindersable_id' => $piggyBank->id, 'remindersable_type' => 'PiggyBank'] - ); - - // a fake reminder:: - Reminder::create( - ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $week, 'active' => 0, 'notnow' => 0, 'remindersable_id' => 40, - 'remindersable_type' => 'Transaction'] - ); - } - - /** - * @param User $user - */ - public function createRecurringTransactions(User $user) - { - // account - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); - - $recurring = PiggyBank::create( - [ - 'account_id' => $savings->id, - 'name' => 'Nieuwe spullen', - 'targetamount' => 1000, - 'startdate' => $this->som, - 'targetdate' => $this->eom, - 'repeats' => 1, - 'rep_length' => 'month', - 'rep_every' => 0, - 'rep_times' => 0, - 'reminder' => 'month', - 'reminder_skip' => 0, - 'remind_me' => 1, - 'order' => 0, - ] - ); - PiggyBankRepetition::create(['piggy_bank_id' => $recurring->id, 'startdate' => $this->som, 'targetdate' => $this->eom, 'currentamount' => 0]); - PiggyBankRepetition::create( - ['piggy_bank_id' => $recurring->id, 'startdate' => $this->nsom, 'targetdate' => $this->neom, 'currentamount' => 0] - ); - Reminder::create( - ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $this->neom, 'active' => 1, 'notnow' => 0, - 'remindersable_id' => $recurring->id, 'remindersable_type' => 'PiggyBank'] - ); - } - - /** - * @param $user - */ - public function createBills($user) - { - // bill - Bill::create( - [ - 'user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,landlord', - 'amount_min' => 700, - 'amount_max' => 900, - 'date' => $this->som, - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] - ); - - // bill - Bill::create( - [ - 'user_id' => $user->id, - 'name' => 'Gas licht', - 'match' => 'no,match', - 'amount_min' => 500, - 'amount_max' => 700, - 'date' => $this->som, - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] - ); - - // bill - Bill::create( - [ - 'user_id' => $user->id, - 'name' => 'Something something', - 'match' => 'mumble,mumble', - 'amount_min' => 500, - 'amount_max' => 700, - 'date' => $this->som, - 'active' => 0, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] - ); - - } - - /** - * @param $user - */ - public function createExpenseAccounts($user) - { - //// create expenses for rent, utilities, water, TV, phone on the 1st of the month. - $expenseType = AccountType::whereType('Expense account')->first(); - - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Land lord', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Utilities company', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Water company', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'TV company', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Phone agency', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Super savers', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Groceries House', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Lunch House', 'active' => 1]); - - - Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Buy More', 'active' => 1]); - - } - - /** - * @param $user - */ - public function createRevenueAccounts($user) - { - $revenueType = AccountType::whereType('Revenue account')->first(); - - Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'Employer', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'IRS', 'active' => 1]); - Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'Second job employer', 'active' => 1]); - - } - - /** - * @param Carbon $date - */ - public function createGroceries(Carbon $date) - { - // variables we need: - $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); - $shopOne = Account::whereName('Groceries House')->orderBy('id', 'DESC')->first(); - $shopTwo = Account::whereName('Super savers')->orderBy('id', 'DESC')->first(); - $lunchHouse = Account::whereName('Lunch House')->orderBy('id', 'DESC')->first(); - $lunch = Category::whereName('Lunch')->orderBy('id', 'DESC')->first(); - $daily = Category::whereName('DailyGroceries')->orderBy('id', 'DESC')->first(); - $euro = TransactionCurrency::whereCode('EUR')->first(); - $withdrawal = TransactionType::whereType('Withdrawal')->first(); - $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); - - - $shops = [$shopOne, $shopTwo]; - - // create groceries and lunch (daily, between 5 and 10 euro). - $mStart = clone $date; - $mEnd = clone $date; - $mEnd->endOfMonth(); - while ($mStart <= $mEnd) { - $mFormat = $mStart->format('Y-m-d'); - $shop = $shops[rand(0, 1)]; - - $this->createTransaction($checking, $shop, (rand(500, 1000) / 100), $withdrawal, 'Groceries', $mFormat, $euro, $groceries, $daily); - $this->createTransaction($checking, $lunchHouse, (rand(200, 600) / 100), $withdrawal, 'Lunch', $mFormat, $euro, $groceries, $lunch); - - $mStart->addDay(); - } - } - - /** - * @param $date - */ - public function createBigExpense($date) - { - $date->addDays(12); - $dollar = TransactionCurrency::whereCode('USD')->first(); - $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); - $buyMore = Account::whereName('Buy More')->orderBy('id', 'DESC')->first(); - $withdrawal = TransactionType::whereType('Withdrawal')->first(); - $transfer = TransactionType::whereType('Transfer')->first(); - $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); - - - // create some big expenses, move some money around. - $amount = rand(500, 2000); - $one = $this->createTransaction( - $savings, $checking, $amount, $transfer, 'Money for big expense in ' . $date->format('F Y'), $date->format('Y-m-d'), $dollar - ); - $two = $this->createTransaction( - $checking, $buyMore, $amount, $withdrawal, 'Big expense in ' . $date->format('F Y'), $date->format('Y-m-d'), $dollar - ); - $group = TransactionGroup::create( - [ - 'user_id' => $user->id, - 'relation' => 'balance' - ] - ); - $group->transactionjournals()->save($one); - $group->transactionjournals()->save($two); - } -} From a7887f1e25d3ad59a98e34aeddb47c497706323e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 08:58:19 +0100 Subject: [PATCH 05/71] Added inspections for all migrations. --- .../migrations/2014_06_27_163032_create_users_table.php | 2 ++ .../2014_06_27_163145_create_account_types_table.php | 2 ++ .../migrations/2014_06_27_163259_create_accounts_table.php | 2 ++ .../migrations/2014_06_27_163817_create_components_table.php | 2 ++ .../migrations/2014_06_27_163818_create_piggybanks_table.php | 2 ++ .../2014_06_27_164042_create_transaction_currencies_table.php | 2 ++ .../2014_06_27_164512_create_transaction_types_table.php | 2 ++ .../2014_06_27_164619_create_recurring_transactions_table.php | 2 ++ .../2014_06_27_164620_create_transaction_journals_table.php | 2 ++ .../migrations/2014_06_27_164836_create_transactions_table.php | 2 ++ .../2014_06_27_165344_create_component_transaction_table.php | 2 ++ ...07_05_171326_create_component_transaction_journal_table.php | 2 ++ .../migrations/2014_07_06_123842_create_preferences_table.php | 2 ++ .../migrations/2014_07_09_204843_create_session_table.php | 2 ++ .../migrations/2014_07_17_183717_create_limits_table.php | 3 +++ .../migrations/2014_07_19_055011_create_limit_repeat_table.php | 2 ++ ..._06_044416_create_component_recurring_transaction_table.php | 2 ++ .../2014_08_12_173919_create_piggybank_repetitions_table.php | 2 ++ .../2014_08_18_100330_create_piggybank_events_table.php | 2 ++ .../migrations/2014_08_23_113221_create_reminders_table.php | 2 ++ .../migrations/2014_11_10_172053_create_account_meta_table.php | 2 ++ .../2014_11_29_135749_create_transaction_groups_table.php | 2 ++ app/database/migrations/2014_12_13_190730_changes_for_v321.php | 2 ++ app/database/migrations/2014_12_24_191544_changes_for_v322.php | 2 ++ 24 files changed, 49 insertions(+) diff --git a/app/database/migrations/2014_06_27_163032_create_users_table.php b/app/database/migrations/2014_06_27_163032_create_users_table.php index 3c72c3ab6c..cd554148db 100644 --- a/app/database/migrations/2014_06_27_163032_create_users_table.php +++ b/app/database/migrations/2014_06_27_163032_create_users_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateUsersTable */ class CreateUsersTable extends Migration 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 7d90098389..783ec4d15d 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 @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateAccountTypesTable * */ diff --git a/app/database/migrations/2014_06_27_163259_create_accounts_table.php b/app/database/migrations/2014_06_27_163259_create_accounts_table.php index 71d3cd8a37..3fb5541655 100644 --- a/app/database/migrations/2014_06_27_163259_create_accounts_table.php +++ b/app/database/migrations/2014_06_27_163259_create_accounts_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateAccountsTable * */ diff --git a/app/database/migrations/2014_06_27_163817_create_components_table.php b/app/database/migrations/2014_06_27_163817_create_components_table.php index 2c4852f02c..465b1ecaff 100644 --- a/app/database/migrations/2014_06_27_163817_create_components_table.php +++ b/app/database/migrations/2014_06_27_163817_create_components_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateComponentsTable * */ diff --git a/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php b/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php index 5ac466d43e..67d527a66d 100644 --- a/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php +++ b/app/database/migrations/2014_06_27_163818_create_piggybanks_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreatePiggybanksTable * */ diff --git a/app/database/migrations/2014_06_27_164042_create_transaction_currencies_table.php b/app/database/migrations/2014_06_27_164042_create_transaction_currencies_table.php index a6444da219..809a837bc9 100644 --- a/app/database/migrations/2014_06_27_164042_create_transaction_currencies_table.php +++ b/app/database/migrations/2014_06_27_164042_create_transaction_currencies_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateTransactionCurrenciesTable * */ diff --git a/app/database/migrations/2014_06_27_164512_create_transaction_types_table.php b/app/database/migrations/2014_06_27_164512_create_transaction_types_table.php index 77a3d9924c..746f037a84 100644 --- a/app/database/migrations/2014_06_27_164512_create_transaction_types_table.php +++ b/app/database/migrations/2014_06_27_164512_create_transaction_types_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateTransactionTypesTable * */ diff --git a/app/database/migrations/2014_06_27_164619_create_recurring_transactions_table.php b/app/database/migrations/2014_06_27_164619_create_recurring_transactions_table.php index c3e9aea81b..724059c859 100644 --- a/app/database/migrations/2014_06_27_164619_create_recurring_transactions_table.php +++ b/app/database/migrations/2014_06_27_164619_create_recurring_transactions_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateRecurringTransactionsTable * */ diff --git a/app/database/migrations/2014_06_27_164620_create_transaction_journals_table.php b/app/database/migrations/2014_06_27_164620_create_transaction_journals_table.php index b44af8769c..44ede35eb0 100644 --- a/app/database/migrations/2014_06_27_164620_create_transaction_journals_table.php +++ b/app/database/migrations/2014_06_27_164620_create_transaction_journals_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateTransactionJournalsTable * */ diff --git a/app/database/migrations/2014_06_27_164836_create_transactions_table.php b/app/database/migrations/2014_06_27_164836_create_transactions_table.php index 74765c1910..66b2772534 100644 --- a/app/database/migrations/2014_06_27_164836_create_transactions_table.php +++ b/app/database/migrations/2014_06_27_164836_create_transactions_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateTransactionsTable * */ diff --git a/app/database/migrations/2014_06_27_165344_create_component_transaction_table.php b/app/database/migrations/2014_06_27_165344_create_component_transaction_table.php index d5f73f8444..6eee751926 100644 --- a/app/database/migrations/2014_06_27_165344_create_component_transaction_table.php +++ b/app/database/migrations/2014_06_27_165344_create_component_transaction_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateComponentTransactionTable * */ diff --git a/app/database/migrations/2014_07_05_171326_create_component_transaction_journal_table.php b/app/database/migrations/2014_07_05_171326_create_component_transaction_journal_table.php index 2069e961a9..e6de376c42 100644 --- a/app/database/migrations/2014_07_05_171326_create_component_transaction_journal_table.php +++ b/app/database/migrations/2014_07_05_171326_create_component_transaction_journal_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateComponentTransactionJournalTable * */ diff --git a/app/database/migrations/2014_07_06_123842_create_preferences_table.php b/app/database/migrations/2014_07_06_123842_create_preferences_table.php index 61f12090a2..7cd7069c8e 100644 --- a/app/database/migrations/2014_07_06_123842_create_preferences_table.php +++ b/app/database/migrations/2014_07_06_123842_create_preferences_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreatePreferencesTable * */ diff --git a/app/database/migrations/2014_07_09_204843_create_session_table.php b/app/database/migrations/2014_07_09_204843_create_session_table.php index e09703979a..240f66ba88 100644 --- a/app/database/migrations/2014_07_09_204843_create_session_table.php +++ b/app/database/migrations/2014_07_09_204843_create_session_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateSessionTable * */ diff --git a/app/database/migrations/2014_07_17_183717_create_limits_table.php b/app/database/migrations/2014_07_17_183717_create_limits_table.php index fc39882c7b..f6c507216a 100644 --- a/app/database/migrations/2014_07_17_183717_create_limits_table.php +++ b/app/database/migrations/2014_07_17_183717_create_limits_table.php @@ -4,6 +4,9 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName)\ + * + * * Class CreateLimitsTable * */ diff --git a/app/database/migrations/2014_07_19_055011_create_limit_repeat_table.php b/app/database/migrations/2014_07_19_055011_create_limit_repeat_table.php index 1240cf3657..46edc87ac9 100644 --- a/app/database/migrations/2014_07_19_055011_create_limit_repeat_table.php +++ b/app/database/migrations/2014_07_19_055011_create_limit_repeat_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateLimitRepeatTable * */ diff --git a/app/database/migrations/2014_08_06_044416_create_component_recurring_transaction_table.php b/app/database/migrations/2014_08_06_044416_create_component_recurring_transaction_table.php index 512387e29b..6105817f64 100644 --- a/app/database/migrations/2014_08_06_044416_create_component_recurring_transaction_table.php +++ b/app/database/migrations/2014_08_06_044416_create_component_recurring_transaction_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateComponentRecurringTransactionTable * */ diff --git a/app/database/migrations/2014_08_12_173919_create_piggybank_repetitions_table.php b/app/database/migrations/2014_08_12_173919_create_piggybank_repetitions_table.php index d285d99487..341d36a9d8 100644 --- a/app/database/migrations/2014_08_12_173919_create_piggybank_repetitions_table.php +++ b/app/database/migrations/2014_08_12_173919_create_piggybank_repetitions_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreatePiggyInstance * */ diff --git a/app/database/migrations/2014_08_18_100330_create_piggybank_events_table.php b/app/database/migrations/2014_08_18_100330_create_piggybank_events_table.php index 480f09d658..8eebf01388 100644 --- a/app/database/migrations/2014_08_18_100330_create_piggybank_events_table.php +++ b/app/database/migrations/2014_08_18_100330_create_piggybank_events_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreatePiggybankEventsTable * */ diff --git a/app/database/migrations/2014_08_23_113221_create_reminders_table.php b/app/database/migrations/2014_08_23_113221_create_reminders_table.php index 8d231aac04..1aaa49fb0a 100644 --- a/app/database/migrations/2014_08_23_113221_create_reminders_table.php +++ b/app/database/migrations/2014_08_23_113221_create_reminders_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateRemindersTable * */ diff --git a/app/database/migrations/2014_11_10_172053_create_account_meta_table.php b/app/database/migrations/2014_11_10_172053_create_account_meta_table.php index 0cbc84ae4d..1febb05a1b 100644 --- a/app/database/migrations/2014_11_10_172053_create_account_meta_table.php +++ b/app/database/migrations/2014_11_10_172053_create_account_meta_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateAccountMetaTable * */ diff --git a/app/database/migrations/2014_11_29_135749_create_transaction_groups_table.php b/app/database/migrations/2014_11_29_135749_create_transaction_groups_table.php index acba3c84ce..c3d2e3af49 100644 --- a/app/database/migrations/2014_11_29_135749_create_transaction_groups_table.php +++ b/app/database/migrations/2014_11_29_135749_create_transaction_groups_table.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class CreateTransactionGroupsTable * */ diff --git a/app/database/migrations/2014_12_13_190730_changes_for_v321.php b/app/database/migrations/2014_12_13_190730_changes_for_v321.php index 4a9a743178..546361390b 100644 --- a/app/database/migrations/2014_12_13_190730_changes_for_v321.php +++ b/app/database/migrations/2014_12_13_190730_changes_for_v321.php @@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Down: * 1. Create new Components based on Budgets. * 2. Create new Components based on Categories diff --git a/app/database/migrations/2014_12_24_191544_changes_for_v322.php b/app/database/migrations/2014_12_24_191544_changes_for_v322.php index 0714ea25fe..5e11626130 100644 --- a/app/database/migrations/2014_12_24_191544_changes_for_v322.php +++ b/app/database/migrations/2014_12_24_191544_changes_for_v322.php @@ -4,6 +4,8 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; /** + * @SuppressWarnings(PHPMD.ShortMethodName) + * * Class ChangesForV322 */ class ChangesForV322 extends Migration From 33c830a4320c4b70ce90c570faff2e11692db926 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 08:58:30 +0100 Subject: [PATCH 06/71] Cleaned up seeders. --- app/database/seeds/AccountTypeSeeder.php | 33 ++++++------------------ app/database/seeds/DatabaseSeeder.php | 2 -- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/database/seeds/AccountTypeSeeder.php b/app/database/seeds/AccountTypeSeeder.php index f0fd27b341..93523755ac 100644 --- a/app/database/seeds/AccountTypeSeeder.php +++ b/app/database/seeds/AccountTypeSeeder.php @@ -10,31 +10,14 @@ class AccountTypeSeeder extends Seeder { DB::table('account_types')->delete(); - AccountType::create( - ['type' => 'Default account', 'editable' => true] - ); - AccountType::create( - ['type' => 'Cash account', 'editable' => false] - ); - AccountType::create( - ['type' => 'Asset account', 'editable' => true] - ); - AccountType::create( - ['type' => 'Expense account', 'editable' => true] - ); - AccountType::create( - ['type' => 'Revenue account', 'editable' => true] - ); - AccountType::create( - ['type' => 'Initial balance account', 'editable' => false] - ); - AccountType::create( - ['type' => 'Beneficiary account', 'editable' => true] - ); - - AccountType::create( - ['type' => 'Import account', 'editable' => false] - ); + AccountType::create(['type' => 'Default account', 'editable' => true]); + AccountType::create(['type' => 'Cash account', 'editable' => false]); + AccountType::create(['type' => 'Asset account', 'editable' => true]); + AccountType::create(['type' => 'Expense account', 'editable' => true]); + AccountType::create(['type' => 'Revenue account', 'editable' => true]); + AccountType::create(['type' => 'Initial balance account', 'editable' => false]); + AccountType::create(['type' => 'Beneficiary account', 'editable' => true]); + AccountType::create(['type' => 'Import account', 'editable' => false]); } diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php index 29319ca38c..ec034ef5f7 100644 --- a/app/database/seeds/DatabaseSeeder.php +++ b/app/database/seeds/DatabaseSeeder.php @@ -18,8 +18,6 @@ class DatabaseSeeder extends Seeder $this->call('AccountTypeSeeder'); $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); - $this->call('DefaultUserSeeder'); - $this->call('TestContentSeeder'); } } From 027b954b50a122d3cd61daa68280c10b418dccc2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 08:58:49 +0100 Subject: [PATCH 07/71] Cleanup various factories and libraries. --- .../FireflyIII/Database/Account/Account.php | 49 ++--- .../Database/PiggyBank/PiggyBank.php | 5 +- .../TransactionJournal/TransactionJournal.php | 178 ++++++++++-------- .../TransactionType/TransactionType.php | 3 +- .../Helper/TransactionJournal/Helper.php | 21 ++- app/lib/FireflyIII/Report/Report.php | 3 +- app/lib/FireflyIII/Report/ReportQuery.php | 9 +- app/lib/FireflyIII/Shared/Toolkit/Date.php | 124 ++++++------ app/lib/FireflyIII/Shared/Toolkit/Filter.php | 49 +++-- app/lib/FireflyIII/Shared/Toolkit/Form.php | 2 +- app/tests/factories/PiggyBank.php | 8 +- app/tests/factories/Transaction.php | 4 +- 12 files changed, 227 insertions(+), 228 deletions(-) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index 257ae7b79a..1faa84c220 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -88,51 +88,31 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte { $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\TransactionJournal $tj */ - $tj = \App::make('FireflyIII\Database\TransactionJournal\TransactionJournal'); + $balance = floatval($data['openingbalance']); + $date = new Carbon($data['openingbalancedate']); + /** @var \FireflyIII\Database\TransactionJournal\TransactionJournal $journals */ + $journals = \App::make('FireflyIII\Database\TransactionJournal\TransactionJournal'); + $fromAccount = $opposingAccount; + $toAccount = $account; 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; + $fromAccount = $account; + $toAccount = $opposingAccount; } - // data for transaction journal: $balance = $balance < 0 ? $balance * -1 : $balance; - // find the account type: /** @var \FireflyIII\Database\TransactionType\TransactionType $typeRepository */ $typeRepository = \App::make('FireflyIII\Database\TransactionType\TransactionType'); $type = $typeRepository->findByWhat('opening'); + $currency = \Amount::getDefaultCurrency(); - // find the currency. - $currency = \Amount::getDefaultCurrency(); + $opening = ['transaction_type_id' => $type->id, 'transaction_currency_id' => $currency->id, 'amount' => $balance, 'from' => $fromAccount, + 'completed' => 0, 'currency' => 'EUR', 'what' => 'opening', 'to' => $toAccount, 'date' => $date, + 'description' => 'Opening balance for new account ' . $account->name,]; - $opening = [ - 'transaction_type_id' => $type->id, - 'transaction_currency_id' => $currency->id, - 'amount' => $balance, - 'from' => $from, - 'completed' => 0, - 'currency' => 'EUR', - 'what' => 'opening', - 'to' => $to, - 'date' => $date, - 'description' => 'Opening balance for new account ' . $account->name,]; - - - $validation = $tj->validate($opening); + $validation = $journals->validate($opening); if ($validation['errors']->count() == 0) { - $tj->store($opening); + $journals->store($opening); return true; } else { @@ -141,6 +121,7 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte \App::abort(500); } + return false; } /** diff --git a/app/lib/FireflyIII/Database/PiggyBank/PiggyBank.php b/app/lib/FireflyIII/Database/PiggyBank/PiggyBank.php index b77f9246bb..5de40005e9 100644 --- a/app/lib/FireflyIII/Database/PiggyBank/PiggyBank.php +++ b/app/lib/FireflyIII/Database/PiggyBank/PiggyBank.php @@ -30,13 +30,10 @@ class PiggyBank extends PiggyBankShared implements CUDInterface, CommonDatabaseC if ($reps->count() == 1) { return $reps->first(); } - if ($reps->count() == 0) { - throw new FireflyException('Should always find a piggy bank repetition.'); - } // should filter the one we need: $repetitions = $reps->filter( function (\PiggyBankRepetition $rep) use ($date) { - if ($date >= $rep->startdate && $date <= $rep->targetdate) { + if ($date->between($rep->startdate, $rep->targetdate)) { return $rep; } diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index beb7b13df2..60f74c1415 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -117,9 +117,6 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C $this->storeBudget($data, $model); $this->storeCategory($data, $model); - /* - * Now we can update the transactions related to this journal. - */ $amount = floatval($data['amount']); /** @var \Transaction $transaction */ foreach ($model->transactions()->get() as $transaction) { @@ -161,91 +158,37 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C $journal->isValid(); $errors = $journal->getErrors(); + /* + * Is not in rules. + */ if (!isset($model['what'])) { $errors->add('description', 'Internal error: need to know type of transaction!'); } /* - * Amount + * Is not in rules. */ - if (isset($model['amount']) && floatval($model['amount']) < 0.01) { - $errors->add('amount', 'Amount must be > 0.01'); - } else { - if (!isset($model['amount'])) { - $errors->add('amount', 'Amount must be set!'); - } else { - $successes->add('amount', 'OK'); - } - } - - /* - * Budget - */ - if (isset($model['budget_id']) && !ctype_digit($model['budget_id'])) { - $errors->add('budget_id', 'Invalid budget'); - } else { - $successes->add('budget_id', 'OK'); - } - - $successes->add('category', 'OK'); - - /* - * Many checks to catch invalid or not-existing accounts. - */ - switch (true) { - // this combination is often seen in withdrawals. - case (isset($model['account_id']) && isset($model['expense_account'])): - if (intval($model['account_id']) < 1) { - $errors->add('account_id', 'Invalid account.'); - } else { - $successes->add('account_id', 'OK'); - } - $successes->add('expense_account', 'OK'); - break; - case (isset($model['account_id']) && isset($model['revenue_account'])): - if (intval($model['account_id']) < 1) { - $errors->add('account_id', 'Invalid account.'); - } else { - $successes->add('account_id', 'OK'); - } - $successes->add('revenue_account', 'OK'); - break; - case (isset($model['account_from_id']) && isset($model['account_to_id'])): - if (intval($model['account_from_id']) < 1 || intval($model['account_from_id']) < 1) { - $errors->add('account_from_id', 'Invalid account selected.'); - $errors->add('account_to_id', 'Invalid account selected.'); - - } else { - if (intval($model['account_from_id']) == intval($model['account_to_id'])) { - $errors->add('account_to_id', 'Cannot be the same as "from" account.'); - $errors->add('account_from_id', 'Cannot be the same as "to" account.'); - } else { - $successes->add('account_from_id', 'OK'); - $successes->add('account_to_id', 'OK'); - } - } - break; - - case (isset($model['to']) && isset($model['from'])): - if (is_object($model['to']) && is_object($model['from'])) { - $successes->add('from', 'OK'); - $successes->add('to', 'OK'); - } - break; - - default: - throw new FireflyException('Cannot validate accounts for transaction journal.'); - break; - } + $errors = $errors->merge($this->_validateAmount($model)); + $errors = $errors->merge($this->_validateBudget($model)); + $errors = $errors->merge($this->_validateAccount($model)); /* * Add "OK" */ - if (!$errors->has('description')) { - $successes->add('description', 'OK'); - } - if (!$errors->has('date')) { - $successes->add('date', 'OK'); + + /** + * else { + * $successes->add('account_from_id', 'OK'); + * $successes->add('account_to_id', 'OK'); + * } + * else { + */ + $list = ['date', 'description', 'amount', 'budget_id', 'from', 'to', 'account_from_id', 'account_to_id', 'category', 'account_id', 'expense_account', + 'revenue_account']; + foreach ($list as $entry) { + if (!$errors->has($entry)) { + $successes->add($entry, 'OK'); + } } return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes]; @@ -377,6 +320,85 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C return $typeRepository->findByWhat($type); } + /** + * @param array $model + * + * @return MessageBag + */ + protected function _validateAmount(array $model) + { + $errors = new MessageBag; + if (isset($model['amount']) && floatval($model['amount']) < 0.01) { + $errors->add('amount', 'Amount must be > 0.01'); + } else { + if (!isset($model['amount'])) { + $errors->add('amount', 'Amount must be set!'); + } + } + + return $errors; + } + + /** + * @param array $model + * + * @return MessageBag + */ + protected function _validateBudget(array $model) + { + /* + * Budget (is not in rules) + */ + $errors = new MessageBag; + if (isset($model['budget_id']) && !ctype_digit($model['budget_id'])) { + $errors->add('budget_id', 'Invalid budget'); + } + + return $errors; + } + + /** + * @param array $model + * + * @return MessageBag + * @throws FireflyException + */ + protected function _validateAccount(array $model) + { + $errors = new MessageBag; + switch (true) { + // this combination is often seen in withdrawals. + case (isset($model['account_id']) && isset($model['expense_account'])): + if (intval($model['account_id']) < 1) { + $errors->add('account_id', 'Invalid account.'); + } + break; + case (isset($model['account_id']) && isset($model['revenue_account'])): + if (intval($model['account_id']) < 1) { + $errors->add('account_id', 'Invalid account.'); + } + break; + case (isset($model['account_from_id']) && isset($model['account_to_id'])): + if (intval($model['account_from_id']) < 1 || intval($model['account_from_id']) < 1) { + $errors->add('account_from_id', 'Invalid account selected.'); + $errors->add('account_to_id', 'Invalid account selected.'); + + } else { + if (intval($model['account_from_id']) == intval($model['account_to_id'])) { + $errors->add('account_to_id', 'Cannot be the same as "from" account.'); + $errors->add('account_from_id', 'Cannot be the same as "to" account.'); + } + } + break; + + default: + throw new FireflyException('Cannot validate accounts for transaction journal.'); + break; + } + + return $errors; + } + /** * Returns an object with id $id. * diff --git a/app/lib/FireflyIII/Database/TransactionType/TransactionType.php b/app/lib/FireflyIII/Database/TransactionType/TransactionType.php index b3b5191de4..2159e5d62a 100644 --- a/app/lib/FireflyIII/Database/TransactionType/TransactionType.php +++ b/app/lib/FireflyIII/Database/TransactionType/TransactionType.php @@ -96,9 +96,10 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface 'withdrawal' => 'Withdrawal', 'deposit' => 'Deposit', ]; - if(!isset($translation[$what])) { + if (!isset($translation[$what])) { throw new FireflyException('Cannot find transaction type described as "' . e($what) . '".'); } + return \TransactionType::whereType($translation[$what])->first(); } diff --git a/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php b/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php index 304cef0c5d..f2f9b0f313 100644 --- a/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php +++ b/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php @@ -12,15 +12,6 @@ use Illuminate\Support\Collection; class Helper implements HelperInterface { - /** - * @param $what - * - * @return int - */ - public function getTransactionTypeIdByWhat($what) { - - } - /** * * Get the account_id, which is the asset account that paid for the transaction. @@ -49,7 +40,7 @@ class Helper implements HelperInterface /** @var \FireflyIII\Database\Account\Account $accountRepository */ $accountRepository = \App::make('FireflyIII\Database\Account\Account'); - return $accountRepository->getAssetAccounts(); + return $accountRepository->getAccountsByType(['Default account', 'Asset account']); } /** @@ -90,4 +81,14 @@ class Helper implements HelperInterface } + /** + * @param $what + * + * @return int + */ + public function getTransactionTypeIdByWhat($what) + { + + } + } diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index f3040fe30f..748b2f5f49 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -11,8 +11,9 @@ use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; /** - * Class Report + * @SuppressWarnings("CamelCase") // I'm fine with this. * + * Class Report * * @package FireflyIII\Report */ diff --git a/app/lib/FireflyIII/Report/ReportQuery.php b/app/lib/FireflyIII/Report/ReportQuery.php index 1a143cbb44..a53618adcd 100644 --- a/app/lib/FireflyIII/Report/ReportQuery.php +++ b/app/lib/FireflyIII/Report/ReportQuery.php @@ -68,16 +68,13 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'otherJournals.id') - ->before($end) - ->after($start) + ->before($end)->after($start) ->where('transaction_types.type', 'Withdrawal') ->where('transaction_journals.user_id', \Auth::user()->id) - ->whereNull('budget_transaction_journal.budget_id') - ->whereNull('transaction_journals.deleted_at') + ->whereNull('budget_transaction_journal.budget_id')->whereNull('transaction_journals.deleted_at') ->whereNull('otherJournals.deleted_at') ->where('transactions.account_id', $account->id) - ->whereNotNull('transaction_group_transaction_journal.transaction_group_id') - ->groupBy('transaction_journals.id') + ->whereNotNull('transaction_group_transaction_journal.transaction_group_id')->groupBy('transaction_journals.id') ->get( [ 'transaction_journals.id as transferId', diff --git a/app/lib/FireflyIII/Shared/Toolkit/Date.php b/app/lib/FireflyIII/Shared/Toolkit/Date.php index 3f7b321216..7b41d9d273 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Date.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Date.php @@ -13,7 +13,7 @@ use FireflyIII\Exception\FireflyException; class Date { /** - * @param Carbon $theDate + * @param Carbon $theDate * @param $repeatFreq * @param $skip * @@ -25,41 +25,37 @@ class Date $date = clone $theDate; $add = ($skip + 1); - switch ($repeatFreq) { - default: - throw new FireflyException('Cannot do addPeriod for $repeat_freq "' . $repeatFreq . '"'); - break; - case 'daily': - $date->addDays($add); - break; - case 'week': - case 'weekly': - $date->addWeeks($add); - break; - case 'month': - case 'monthly': - $date->addMonths($add); - break; - case 'quarter': - case 'quarterly': - $months = $add * 3; - $date->addMonths($months); - break; - case 'half-year': - $months = $add * 6; - $date->addMonths($months); - break; - case 'year': - case 'yearly': - $date->addYears($add); - break; + $functionMap = [ + 'daily' => 'addDays', + 'weekly' => 'addWeeks', + 'week' => 'addWeeks', + 'month' => 'addMonths', + 'monthly' => 'addMonths', + 'quarter' => 'addMonths', + 'quarterly' => 'addMonths', + 'half-year' => 'addMonths', + 'year' => 'addYears', + 'yearly' => 'addYears', + ]; + $modifierMap = [ + 'quarter' => 3, + 'quarterly' => 3, + 'half-year' => 6, + ]; + if (!isset($functionMap[$repeatFreq])) { + throw new FireflyException('Cannot do addPeriod for $repeat_freq "' . $repeatFreq . '"'); } + if (isset($modifierMap[$repeatFreq])) { + $add = $add * $modifierMap[$repeatFreq]; + } + $function = $functionMap[$repeatFreq]; + $date->$function($add); return $date; } /** - * @param Carbon $theCurrentEnd + * @param Carbon $theCurrentEnd * @param $repeatFreq * * @return Carbon @@ -68,41 +64,47 @@ class Date public function endOfPeriod(Carbon $theCurrentEnd, $repeatFreq) { $currentEnd = clone $theCurrentEnd; - switch ($repeatFreq) { - default: - throw new FireflyException('Cannot do endOfPeriod for $repeat_freq ' . $repeatFreq); - break; - case 'daily': - $currentEnd->addDay(); - break; - case 'week': - case 'weekly': - $currentEnd->addWeek()->subDay(); - break; - case 'month': - case 'monthly': - $currentEnd->addMonth()->subDay(); - break; - case 'quarter': - case 'quarterly': - $currentEnd->addMonths(3)->subDay(); - break; - case 'half-year': - $currentEnd->addMonths(6)->subDay(); - break; - case 'year': - case 'yearly': - $currentEnd->addYear()->subDay(); - break; + + $functionMap = [ + 'daily' => 'addDay', + 'week' => 'addWeek', + 'weekly' => 'addWeek', + 'month' => 'addMonth', + 'monthly' => 'addMonth', + 'quarter' => 'addMonths', + 'quarterly' => 'addMonths', + 'half-year' => 'addMonths', + 'year' => 'addYear', + 'yearly' => 'addYear', + ]; + $modifierMap = [ + 'quarter' => 3, + 'quarterly' => 3, + 'half-year' => 6, + ]; + + $subDay = ['week', 'weekly', 'month', 'monthly', 'quarter', 'quarterly', 'half-year', 'year', 'yearly']; + + if (!isset($functionMap[$repeatFreq])) { + throw new FireflyException('Cannot do endOfPeriod for $repeat_freq ' . $repeatFreq); + } + $function = $functionMap[$repeatFreq]; + if (isset($modifierMap[$repeatFreq])) { + $currentEnd->$function($modifierMap[$repeatFreq]); + } else { + $currentEnd->$function(); + } + if (in_array($repeatFreq, $subDay)) { + $currentEnd->subDay(); } return $currentEnd; } /** - * @param Carbon $theCurrentEnd + * @param Carbon $theCurrentEnd * @param $repeatFreq - * @param Carbon $maxDate + * @param Carbon $maxDate * * @return Carbon * @throws FireflyException @@ -149,7 +151,7 @@ class Date } /** - * @param Carbon $date + * @param Carbon $date * @param $repeatFrequency * * @return string @@ -183,7 +185,7 @@ class Date } /** - * @param Carbon $theDate + * @param Carbon $theDate * @param $repeatFreq * * @return Carbon @@ -228,7 +230,7 @@ class Date } /** - * @param Carbon $theDate + * @param Carbon $theDate * @param $repeatFreq * @param int $subtract * diff --git a/app/lib/FireflyIII/Shared/Toolkit/Filter.php b/app/lib/FireflyIII/Shared/Toolkit/Filter.php index 9be82e313a..f8fdf42037 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Filter.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Filter.php @@ -69,36 +69,29 @@ class Filter */ protected function updateStartDate($range, Carbon $start) { - switch ($range) { - default: - throw new FireflyException('updateStartDate cannot handle $range ' . $range); - break; - case '1D': - $start->startOfDay(); - break; - case '1W': - $start->startOfWeek(); - break; - case '1M': - $start->startOfMonth(); - break; - case '3M': - $start->firstOfQuarter(); - break; - case '6M': - if (intval($start->format('m')) >= 7) { - $start->startOfYear()->addMonths(6); - } else { - $start->startOfYear(); - } - break; - case '1Y': - $start->startOfYear(); - break; + $functionMap = [ + '1D' => 'startOfDay', + '1W' => 'startOfWeek', + '1M' => 'startOfMonth', + '3M' => 'firstOfQuarter', + '1Y' => 'startOfYear', + ]; + if (isset($functionMap[$range])) { + $function = $functionMap[$range]; + $start->$function(); + + return $start; } + if ($range == '6M') { + if (intval($start->format('m')) >= 7) { + $start->startOfYear()->addMonths(6); + } else { + $start->startOfYear(); + } - return $start; - + return $start; + } + throw new FireflyException('updateStartDate cannot handle $range ' . $range); } /** diff --git a/app/lib/FireflyIII/Shared/Toolkit/Form.php b/app/lib/FireflyIII/Shared/Toolkit/Form.php index 55457dc7d5..a5475a0456 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Form.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Form.php @@ -32,7 +32,7 @@ class Form $title = null; foreach ($fields as $field) { - if (is_null($title) && isset($entry->$field)) { + if (isset($entry->$field)) { $title = $entry->$field; } } diff --git a/app/tests/factories/PiggyBank.php b/app/tests/factories/PiggyBank.php index 36f89debf8..aaab88cd71 100644 --- a/app/tests/factories/PiggyBank.php +++ b/app/tests/factories/PiggyBank.php @@ -15,8 +15,12 @@ League\FactoryMuffin\Facade::define( return $set[rand(0, count($set) - 1)]; }, - 'rep_every' => function() {return rand(0,3);}, - 'rep_times' => function() {return rand(0,3);}, + 'rep_every' => function () { + return rand(0, 3); + }, + 'rep_times' => function () { + return rand(0, 3); + }, 'reminder' => function () { $set = ['day', 'week', 'quarter', 'month', 'year']; diff --git a/app/tests/factories/Transaction.php b/app/tests/factories/Transaction.php index 9f812b8ce0..39f50f7ca5 100644 --- a/app/tests/factories/Transaction.php +++ b/app/tests/factories/Transaction.php @@ -5,8 +5,8 @@ League\FactoryMuffin\Facade::define( 'account_id' => 'factory|Account', 'transaction_journal_id' => 'factory|TransactionJournal', 'description' => 'sentence', - 'amount' => function() { - return round(rand(100,10000) / 100,2); + 'amount' => function () { + return round(rand(100, 10000) / 100, 2); } ] ); From fc0ef4b79de59803871fab228c936422c41834bd Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 10:05:43 +0100 Subject: [PATCH 08/71] Small optimizations. --- app/controllers/BudgetController.php | 8 ++++++-- app/controllers/CategoryController.php | 8 ++++++-- app/controllers/CurrencyController.php | 5 ++++- app/controllers/GoogleChartController.php | 2 +- app/controllers/HelpController.php | 2 ++ app/controllers/PiggybankController.php | 2 ++ app/controllers/ProfileController.php | 4 +++- app/controllers/RelatedController.php | 4 ++++ app/controllers/RepeatedExpenseController.php | 3 ++- 9 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/controllers/BudgetController.php b/app/controllers/BudgetController.php index 9365d67d2c..53ae58aef2 100644 --- a/app/controllers/BudgetController.php +++ b/app/controllers/BudgetController.php @@ -145,6 +145,8 @@ class BudgetController extends BaseController } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param Budget $budget * @param LimitRepetition $repetition * @@ -181,10 +183,11 @@ class BudgetController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not validate budget: ' . $messages['errors']->first()); + return Redirect::route('budgets.create')->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('budgets.create')->withInput(); } @@ -219,10 +222,11 @@ class BudgetController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update budget: ' . $messages['errors']->first()); + return Redirect::route('budgets.edit', $budget->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('budgets.edit', $budget->id)->withInput(); } diff --git a/app/controllers/CategoryController.php b/app/controllers/CategoryController.php index 915562cfc2..bc5d0846f1 100644 --- a/app/controllers/CategoryController.php +++ b/app/controllers/CategoryController.php @@ -105,6 +105,7 @@ class CategoryController extends BaseController } /** + * * @return $this * @throws FireflyException */ @@ -122,10 +123,11 @@ class CategoryController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not store category: ' . $messages['errors']->first()); + return Redirect::route('categories.create')->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('categories.create')->withInput(); } @@ -140,6 +142,7 @@ class CategoryController extends BaseController } /** + * * @param Category $category * * @return $this @@ -159,10 +162,11 @@ class CategoryController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update category: ' . $messages['errors']->first()); + return Redirect::route('categories.edit', $category->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('categories.edit', $category->id)->withInput(); } diff --git a/app/controllers/CurrencyController.php b/app/controllers/CurrencyController.php index 5e79791af3..9b55132333 100644 --- a/app/controllers/CurrencyController.php +++ b/app/controllers/CurrencyController.php @@ -122,6 +122,8 @@ class CurrencyController extends BaseController } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @return $this|\Illuminate\Http\RedirectResponse */ public function store() @@ -173,10 +175,11 @@ class CurrencyController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update currency: ' . $messages['errors']->first()); + return Redirect::route('currency.edit', $currency->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('currency.edit', $currency->id)->withInput(); } diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index f2cfb3a0cf..99d5662b12 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -72,7 +72,7 @@ class GoogleChartController extends BaseController } /** - * This method renders the b + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. */ public function allAccountsBalanceChart() { diff --git a/app/controllers/HelpController.php b/app/controllers/HelpController.php index 356f86b397..dbb9f7a2aa 100644 --- a/app/controllers/HelpController.php +++ b/app/controllers/HelpController.php @@ -1,8 +1,10 @@ count() > 0) { Session::flash('error', 'Could not store repeated expense: ' . $messages['errors']->first()); + return Redirect::route('repeated.create')->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('repeated.create')->withInput(); } From 0faebc290fe5f6e197c4902b53d389b0ee809deb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 10:05:51 +0100 Subject: [PATCH 09/71] Suppress warnings. --- app/database/migrations/2014_12_13_190730_changes_for_v321.php | 3 ++- app/database/migrations/2014_12_24_191544_changes_for_v322.php | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/database/migrations/2014_12_13_190730_changes_for_v321.php b/app/database/migrations/2014_12_13_190730_changes_for_v321.php index 546361390b..9d5242189d 100644 --- a/app/database/migrations/2014_12_13_190730_changes_for_v321.php +++ b/app/database/migrations/2014_12_13_190730_changes_for_v321.php @@ -5,7 +5,8 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; /** - * @SuppressWarnings(PHPMD.ShortMethodName) + * @SuppressWarnings(PHPMD.ShortMethodName) // method names are mandated by laravel. + * @SuppressWarnings("TooManyMethods") // I'm fine with this * * Down: * 1. Create new Components based on Budgets. diff --git a/app/database/migrations/2014_12_24_191544_changes_for_v322.php b/app/database/migrations/2014_12_24_191544_changes_for_v322.php index 5e11626130..5b5e7abff2 100644 --- a/app/database/migrations/2014_12_24_191544_changes_for_v322.php +++ b/app/database/migrations/2014_12_24_191544_changes_for_v322.php @@ -5,6 +5,8 @@ use Illuminate\Database\Schema\Blueprint; /** * @SuppressWarnings(PHPMD.ShortMethodName) + * @SuppressWarnings("MethodLength") // I don't mind this in case of migrations. + * * Class ChangesForV322 */ From 9e2b34bc12dc57560e9d5ad8b6e7889e52bb0245 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 10:06:12 +0100 Subject: [PATCH 10/71] Various cleanup. --- .../FireflyIII/Database/Account/Account.php | 16 ++++---- .../Database/AccountType/AccountType.php | 41 ++++++++++--------- app/lib/FireflyIII/Database/Bill/Bill.php | 14 +++---- app/lib/FireflyIII/Database/Budget/Budget.php | 4 ++ .../FireflyIII/Database/Category/Category.php | 8 ++++ .../Database/PiggyBank/PiggyBankShared.php | 4 ++ .../Database/PiggyBank/RepeatedExpense.php | 2 + .../Database/Transaction/Transaction.php | 9 ++++ .../TransactionCurrency.php | 6 +++ .../TransactionJournal/TransactionJournal.php | 10 +++++ .../TransactionType/TransactionType.php | 14 +++++++ app/lib/FireflyIII/Event/Piggybank.php | 4 ++ .../Helper/TransactionJournal/Helper.php | 9 ---- .../TransactionJournal/HelperInterface.php | 7 ---- app/lib/FireflyIII/Report/Report.php | 4 ++ app/lib/FireflyIII/Search/Search.php | 2 + .../FireflyIII/Shared/Toolkit/Reminders.php | 5 +++ .../Shared/Validation/FireflyValidator.php | 2 + app/models/Bill.php | 1 - app/models/BudgetLimit.php | 3 -- app/models/LimitRepetition.php | 1 - app/tests/TestCase.php | 2 + 22 files changed, 113 insertions(+), 55 deletions(-) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index 1faa84c220..9fe77ea498 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -406,6 +406,8 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param $what * * @throws NotImplementedException @@ -445,14 +447,14 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte */ public function firstExpenseAccountOrCreate($name) { - /** @var \FireflyIII\Database\AccountType\AccountType $accountTypeRepository */ - $accountTypeRepository = \App::make('FireflyIII\Database\AccountType\AccountType'); + /** @var \FireflyIII\Database\AccountType\AccountType $typeRepository */ + $typeRepository = \App::make('FireflyIII\Database\AccountType\AccountType'); - $accountType = $accountTypeRepository->findByWhat('expense'); + $accountType = $typeRepository->findByWhat('expense'); // if name is "", find cash account: if (strlen($name) == 0) { - $cashAccountType = $accountTypeRepository->findByWhat('cash'); + $cashAccountType = $typeRepository->findByWhat('cash'); // find or create cash account: return \Account::firstOrCreate( @@ -474,10 +476,10 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte */ public function firstRevenueAccountOrCreate($name) { - /** @var \FireflyIII\Database\AccountType\AccountType $accountTypeRepository */ - $accountTypeRepository = \App::make('FireflyIII\Database\AccountType\AccountType'); + /** @var \FireflyIII\Database\AccountType\AccountType $typeRepository */ + $typeRepository = \App::make('FireflyIII\Database\AccountType\AccountType'); - $accountType = $accountTypeRepository->findByWhat('revenue'); + $accountType = $typeRepository->findByWhat('revenue'); $data = ['user_id' => $this->getUser()->id, 'account_type_id' => $accountType->id, 'name' => $name, 'active' => 1]; diff --git a/app/lib/FireflyIII/Database/AccountType/AccountType.php b/app/lib/FireflyIII/Database/AccountType/AccountType.php index 58f6cebb13..30a9c2bcc6 100644 --- a/app/lib/FireflyIII/Database/AccountType/AccountType.php +++ b/app/lib/FireflyIII/Database/AccountType/AccountType.php @@ -19,6 +19,7 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface /** * @param Eloquent $model + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @return bool * @throws NotImplementedException @@ -30,6 +31,7 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface /** * @param array $data + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @return \Eloquent * @throws NotImplementedException @@ -42,6 +44,7 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface /** * @param Eloquent $model * @param array $data + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @return bool * @throws NotImplementedException @@ -52,6 +55,8 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Validates an array. Returns an array containing MessageBags * errors/warnings/successes. * @@ -66,6 +71,8 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -88,30 +95,22 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface */ public function findByWhat($what) { - switch ($what) { - case 'expense': - return \AccountType::whereType('Expense account')->first(); - break; - case 'asset': - return \AccountType::whereType('Asset account')->first(); - break; - case 'revenue': - return \AccountType::whereType('Revenue account')->first(); - break; - case 'cash': - return \AccountType::whereType('Cash 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; - + $typeMap = [ + 'expense' => 'Expense account', + 'asset' => 'Asset account', + 'revenue' => 'Revenue account', + 'cash' => 'Cash account', + 'initial' => 'Initial balance account', + ]; + if (isset($typeMap[$what])) { + return \AccountType::whereType($typeMap[$what])->first(); } + throw new FireflyException('Cannot find account type described as "' . e($what) . '".'); } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns all objects. * * @return Collection @@ -123,6 +122,8 @@ class AccountType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $ids * * @return Collection diff --git a/app/lib/FireflyIII/Database/Bill/Bill.php b/app/lib/FireflyIII/Database/Bill/Bill.php index 9c9683b7cf..e987d4abc7 100644 --- a/app/lib/FireflyIII/Database/Bill/Bill.php +++ b/app/lib/FireflyIII/Database/Bill/Bill.php @@ -133,6 +133,8 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -146,6 +148,8 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -169,6 +173,7 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param array $ids * * @return Collection @@ -205,15 +210,14 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param \Bill $bill * * @return Carbon|null */ public function nextExpectedMatch(\Bill $bill) { - /* - * The date Firefly tries to find. If this stays null, it's "unknown". - */ $finalDate = null; if ($bill->active == 0) { return $finalDate; @@ -225,10 +229,6 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface */ $today = \DateKit::addPeriod(new Carbon, $bill->repeat_freq, 0); - /* - * FF3 loops from the $start of the bill, and to make sure - * $skip works, it adds one (for modulo). - */ $skip = $bill->skip + 1; $start = \DateKit::startOfPeriod(new Carbon, $bill->repeat_freq); /* diff --git a/app/lib/FireflyIII/Database/Budget/Budget.php b/app/lib/FireflyIII/Database/Budget/Budget.php index aaf6eabe3c..f437879e87 100644 --- a/app/lib/FireflyIII/Database/Budget/Budget.php +++ b/app/lib/FireflyIII/Database/Budget/Budget.php @@ -175,6 +175,8 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -200,6 +202,8 @@ class Budget implements CUDInterface, CommonDatabaseCallsInterface, BudgetInterf } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $ids * * @return Collection diff --git a/app/lib/FireflyIII/Database/Category/Category.php b/app/lib/FireflyIII/Database/Category/Category.php index 14d8b64da3..a4a8dc0137 100644 --- a/app/lib/FireflyIII/Database/Category/Category.php +++ b/app/lib/FireflyIII/Database/Category/Category.php @@ -99,6 +99,8 @@ class Category implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -112,6 +114,8 @@ class Category implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -125,6 +129,8 @@ class Category implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns all objects. * * @return Collection @@ -135,6 +141,8 @@ class Category implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $ids * * @return Collection diff --git a/app/lib/FireflyIII/Database/PiggyBank/PiggyBankShared.php b/app/lib/FireflyIII/Database/PiggyBank/PiggyBankShared.php index 5a878e37f4..1d5fa492aa 100644 --- a/app/lib/FireflyIII/Database/PiggyBank/PiggyBankShared.php +++ b/app/lib/FireflyIII/Database/PiggyBank/PiggyBankShared.php @@ -58,6 +58,8 @@ class PiggyBankShared } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -123,6 +125,8 @@ class PiggyBankShared /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param Eloquent $model * @param array $data * diff --git a/app/lib/FireflyIII/Database/PiggyBank/RepeatedExpense.php b/app/lib/FireflyIII/Database/PiggyBank/RepeatedExpense.php index 171b4c7a88..bf22ca509c 100644 --- a/app/lib/FireflyIII/Database/PiggyBank/RepeatedExpense.php +++ b/app/lib/FireflyIII/Database/PiggyBank/RepeatedExpense.php @@ -17,6 +17,8 @@ class RepeatedExpense extends PiggyBankShared implements CUDInterface, CommonDat { /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * Based on the piggy bank, the reminder-setting and * other variables this method tries to divide the piggy bank into equal parts. Each is * accommodated by a reminder (if everything goes to plan). diff --git a/app/lib/FireflyIII/Database/Transaction/Transaction.php b/app/lib/FireflyIII/Database/Transaction/Transaction.php index a92f110d2e..6880999fb7 100644 --- a/app/lib/FireflyIII/Database/Transaction/Transaction.php +++ b/app/lib/FireflyIII/Database/Transaction/Transaction.php @@ -22,6 +22,7 @@ class Transaction implements CUDInterface, CommonDatabaseCallsInterface /** * @param Eloquent $model + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @return bool * @throws NotImplementedException @@ -59,6 +60,8 @@ class Transaction implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Eloquent $model * @param array $data * @@ -92,6 +95,8 @@ class Transaction implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -105,6 +110,8 @@ class Transaction implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -129,6 +136,8 @@ class Transaction implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $ids * * @return Collection diff --git a/app/lib/FireflyIII/Database/TransactionCurrency/TransactionCurrency.php b/app/lib/FireflyIII/Database/TransactionCurrency/TransactionCurrency.php index 55b7c5f502..fc72379da4 100644 --- a/app/lib/FireflyIII/Database/TransactionCurrency/TransactionCurrency.php +++ b/app/lib/FireflyIII/Database/TransactionCurrency/TransactionCurrency.php @@ -89,6 +89,8 @@ class TransactionCurrency implements TransactionCurrencyInterface, CommonDatabas } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -102,6 +104,8 @@ class TransactionCurrency implements TransactionCurrencyInterface, CommonDatabas } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what @@ -125,6 +129,8 @@ class TransactionCurrency implements TransactionCurrencyInterface, CommonDatabas } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $objectIds * @throws NotImplementedException * diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index 60f74c1415..10015a1afb 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -210,6 +210,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param array $data * * @return array @@ -321,6 +323,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } /** + * @SuppressWarnings("CamelCase") // I'm fine with this. + * * @param array $model * * @return MessageBag @@ -340,6 +344,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } /** + * @SuppressWarnings("CamelCase") // I'm fine with this. + * * @param array $model * * @return MessageBag @@ -358,6 +364,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } /** + * @SuppressWarnings("CamelCase") // I'm fine with this. + * * @param array $model * * @return MessageBag @@ -412,6 +420,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Finds an account type using one of the "$what"'s: expense, asset, revenue, opening, etc. * * @param $what diff --git a/app/lib/FireflyIII/Database/TransactionType/TransactionType.php b/app/lib/FireflyIII/Database/TransactionType/TransactionType.php index 2159e5d62a..ca5b678587 100644 --- a/app/lib/FireflyIII/Database/TransactionType/TransactionType.php +++ b/app/lib/FireflyIII/Database/TransactionType/TransactionType.php @@ -20,6 +20,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface { /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Eloquent $model * * @return bool @@ -31,6 +33,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $data * * @return \Eloquent @@ -42,6 +46,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Eloquent $model * @param array $data * @@ -54,6 +60,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Validates an array. Returns an array containing MessageBags * errors/warnings/successes. * @@ -68,6 +76,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns an object with id $id. * * @param int $objectId @@ -104,6 +114,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * Returns all objects. * * @return Collection @@ -115,6 +127,8 @@ class TransactionType implements CUDInterface, CommonDatabaseCallsInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $ids * * @return Collection diff --git a/app/lib/FireflyIII/Event/Piggybank.php b/app/lib/FireflyIII/Event/Piggybank.php index 5c1960a170..645f48432b 100644 --- a/app/lib/FireflyIII/Event/Piggybank.php +++ b/app/lib/FireflyIII/Event/Piggybank.php @@ -34,6 +34,8 @@ class PiggyBank } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param \TransactionJournal $journal * * @throws \FireflyIII\Exception\FireflyException @@ -109,6 +111,8 @@ class PiggyBank */ /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param \TransactionJournal $journal * @param int $piggyBankId */ diff --git a/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php b/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php index f2f9b0f313..90ef11060d 100644 --- a/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php +++ b/app/lib/FireflyIII/Helper/TransactionJournal/Helper.php @@ -81,14 +81,5 @@ class Helper implements HelperInterface } - /** - * @param $what - * - * @return int - */ - public function getTransactionTypeIdByWhat($what) - { - - } } diff --git a/app/lib/FireflyIII/Helper/TransactionJournal/HelperInterface.php b/app/lib/FireflyIII/Helper/TransactionJournal/HelperInterface.php index f49046a42a..06bcac0de7 100644 --- a/app/lib/FireflyIII/Helper/TransactionJournal/HelperInterface.php +++ b/app/lib/FireflyIII/Helper/TransactionJournal/HelperInterface.php @@ -22,13 +22,6 @@ interface HelperInterface */ public function getAssetAccount($what, Collection $transactions); - /** - * @param $what - * - * @return int - */ - public function getTransactionTypeIdByWhat($what); - /** * @return Collection */ diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index 748b2f5f49..91a3caae95 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -227,6 +227,8 @@ class Report implements ReportInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Carbon $date * @param bool $shared * @@ -354,6 +356,8 @@ class Report implements ReportInterface } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Carbon $start * @param Carbon $end * @param int $limit diff --git a/app/lib/FireflyIII/Search/Search.php b/app/lib/FireflyIII/Search/Search.php index 859d83b7b4..4a5d429634 100644 --- a/app/lib/FireflyIII/Search/Search.php +++ b/app/lib/FireflyIII/Search/Search.php @@ -80,6 +80,8 @@ class Search } /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param array $words * * @return Collection diff --git a/app/lib/FireflyIII/Shared/Toolkit/Reminders.php b/app/lib/FireflyIII/Shared/Toolkit/Reminders.php index fe3b40ed9b..11b75259e1 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Reminders.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Reminders.php @@ -15,6 +15,8 @@ class Reminders { /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param \Reminder $reminder * * @return int @@ -62,6 +64,9 @@ class Reminders return $reminders; } + /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + */ public function updateReminders() { /** @var Collection $set */ diff --git a/app/lib/FireflyIII/Shared/Validation/FireflyValidator.php b/app/lib/FireflyIII/Shared/Validation/FireflyValidator.php index 544174a1c5..33795d563b 100644 --- a/app/lib/FireflyIII/Shared/Validation/FireflyValidator.php +++ b/app/lib/FireflyIII/Shared/Validation/FireflyValidator.php @@ -11,6 +11,8 @@ use Illuminate\Validation\Validator; class FireflyValidator extends Validator { /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param $attribute * @param $value * @param $parameters diff --git a/app/models/Bill.php b/app/models/Bill.php index 00d7d2c1e4..bc5500ebe2 100644 --- a/app/models/Bill.php +++ b/app/models/Bill.php @@ -1,5 +1,4 @@ Date: Sat, 17 Jan 2015 10:41:29 +0100 Subject: [PATCH 11/71] Reinstated test data seeder, fixed the tests. --- app/controllers/UserController.php | 6 +- app/database/seeds/DatabaseSeeder.php | 4 + app/database/seeds/TestDataSeeder.php | 562 ++++++++++++++++++++++++++ 3 files changed, 570 insertions(+), 2 deletions(-) create mode 100644 app/database/seeds/TestDataSeeder.php diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php index 74be723b18..702ffe991b 100644 --- a/app/controllers/UserController.php +++ b/app/controllers/UserController.php @@ -78,7 +78,7 @@ class UserController extends BaseController if ($user) { $result = $email->sendVerificationMail($user); - if ($result === false) { + if ($result === false && Config::get('mail.pretend') === false) { $user->delete(); return View::make('error')->with('message', 'The email message could not be send. See the log files.'); @@ -126,7 +126,9 @@ class UserController extends BaseController */ public function register() { - if (Config::get('mail.from.address') == '@gmail.com' || Config::get('mail.from.address') == '') { + if ((Config::get('mail.from.address') == '@gmail.com' || Config::get('mail.from.address') == '') + && Config::get('mail.pretend') === false + ) { return View::make('error')->with('message', 'Configuration error in app/config/' . App::environment() . '/mail.php'); } diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php index ec034ef5f7..f357dfd6ec 100644 --- a/app/database/seeds/DatabaseSeeder.php +++ b/app/database/seeds/DatabaseSeeder.php @@ -18,6 +18,10 @@ class DatabaseSeeder extends Seeder $this->call('AccountTypeSeeder'); $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); + + if (App::environment() == 'testing') { + $this->call('TestDataSeeder'); + } } } diff --git a/app/database/seeds/TestDataSeeder.php b/app/database/seeds/TestDataSeeder.php new file mode 100644 index 0000000000..bfa8e63de1 --- /dev/null +++ b/app/database/seeds/TestDataSeeder.php @@ -0,0 +1,562 @@ +_startOfMonth = Carbon::now()->startOfMonth(); + $this->som = $this->_startOfMonth->format('Y-m-d'); + + $this->_endOfMonth = Carbon::now()->endOfMonth(); + $this->eom = $this->_endOfMonth->format('Y-m-d'); + + $this->_nextStartOfMonth = Carbon::now()->addMonth()->startOfMonth(); + $this->nsom = $this->_nextStartOfMonth->format('Y-m-d'); + + $this->_nextEndOfMonth = Carbon::now()->addMonth()->endOfMonth(); + $this->neom = $this->_nextEndOfMonth->format('Y-m-d'); + + $this->_yearAgoStartOfMonth = Carbon::now()->subYear()->startOfMonth(); + $this->yasom = $this->_yearAgoStartOfMonth->format('Y-m-d'); + + $this->_yearAgoEndOfMonth = Carbon::now()->subYear()->startOfMonth(); + $this->yaeom = $this->_yearAgoEndOfMonth->format('Y-m-d'); + + + $this->_today = Carbon::now(); + $this->today = $this->_today->format('Y-m-d'); + } + + /** + * Dates are always this month, the start of this month or earlier. + */ + public function run() + { + User::create(['email' => 'reset@example.com', 'password' => 'functional', 'reset' => 'okokokokokokokokokokokokokokokok', 'remember_token' => null]); + User::create(['email' => 'functional@example.com', 'password' => 'functional', 'reset' => null, 'remember_token' => null]); + + + $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => 'james', 'reset' => null, 'remember_token' => null]); + + // create initial accounts and various other stuff: + $this->createAssetAccounts($user); + $this->createBudgets($user); + $this->createCategories($user); + $this->createPiggyBanks($user); + $this->createReminders($user); + $this->createRecurringTransactions($user); + $this->createBills($user); + $this->createExpenseAccounts($user); + $this->createRevenueAccounts($user); + + // get some objects from the database: + $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); + $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + $landLord = Account::whereName('Land lord')->orderBy('id', 'DESC')->first(); + $utilities = Account::whereName('Utilities company')->orderBy('id', 'DESC')->first(); + $television = Account::whereName('TV company')->orderBy('id', 'DESC')->first(); + $phone = Account::whereName('Phone agency')->orderBy('id', 'DESC')->first(); + $employer = Account::whereName('Employer')->orderBy('id', 'DESC')->first(); + + + $bills = Budget::whereName('Bills')->orderBy('id', 'DESC')->first(); + $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); + + $house = Category::whereName('House')->orderBy('id', 'DESC')->first(); + + + $withdrawal = TransactionType::whereType('Withdrawal')->first(); + $deposit = TransactionType::whereType('Deposit')->first(); + $transfer = TransactionType::whereType('Transfer')->first(); + + $euro = TransactionCurrency::whereCode('EUR')->first(); + + $rentBill = Bill::where('name', 'Rent')->first(); + + + $current = clone $this->_yearAgoStartOfMonth; + while ($current <= $this->_startOfMonth) { + $cur = $current->format('Y-m-d'); + $formatted = $current->format('F Y'); + + // create expenses for rent, utilities, TV, phone on the 1st of the month. + $this->createTransaction($checking, $landLord, 800, $withdrawal, 'Rent for ' . $formatted, $cur, $euro, $bills, $house, $rentBill); + $this->createTransaction($checking, $utilities, 150, $withdrawal, 'Utilities for ' . $formatted, $cur, $euro, $bills, $house); + $this->createTransaction($checking, $television, 50, $withdrawal, 'TV for ' . $formatted, $cur, $euro, $bills, $house); + $this->createTransaction($checking, $phone, 50, $withdrawal, 'Phone bill for ' . $formatted, $cur, $euro, $bills, $house); + + // two transactions. One without a budget, one without a category. + $this->createTransaction($checking, $phone, 10, $withdrawal, 'Extra charges on phone bill for ' . $formatted, $cur, $euro, null, $house); + $this->createTransaction($checking, $television, 5, $withdrawal, 'Extra charges on TV bill for ' . $formatted, $cur, $euro, $bills, null); + + // income from job: + $this->createTransaction($employer, $checking, rand(3500, 4000), $deposit, 'Salary for ' . $formatted, $cur, $euro); + $this->createTransaction($checking, $savings, 2000, $transfer, 'Salary to savings account in ' . $formatted, $cur, $euro); + + $this->createGroceries($current); + $this->createBigExpense(clone $current); + + echo 'Created test-content for ' . $current->format('F Y') . "\n"; + $current->addMonth(); + } + + + // piggy bank event + // add money to this piggy bank + // create a piggy bank event to match: + $piggyBank = PiggyBank::whereName('New camera')->orderBy('id', 'DESC')->first(); + $intoPiggy = $this->createTransaction($checking, $savings, 100, $transfer, 'Money for piggy', $this->yaeom, $euro, $groceries, $house); + PiggyBankEvent::create( + [ + 'piggy_bank_id' => $piggyBank->id, + 'transaction_journal_id' => $intoPiggy->id, + 'date' => $this->yaeom, + 'amount' => 100 + ] + ); + } + + /** + * @param User $user + */ + public function createAssetAccounts(User $user) + { + $assetType = AccountType::whereType('Asset account')->first(); + $ibType = AccountType::whereType('Initial balance account')->first(); + $obType = TransactionType::whereType('Opening balance')->first(); + $euro = TransactionCurrency::whereCode('EUR')->first(); + + + $acc_a = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Checking account', 'active' => 1]); + $acc_b = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Savings account', 'active' => 1]); + $acc_c = Account::create(['user_id' => $user->id, 'account_type_id' => $assetType->id, 'name' => 'Delete me', 'active' => 1]); + + $acc_d = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Checking account initial balance', 'active' => 0]); + $acc_e = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Savings account initial balance', 'active' => 0]); + $acc_f = Account::create(['user_id' => $user->id, 'account_type_id' => $ibType->id, 'name' => 'Delete me initial balance', 'active' => 0]); + + + $this->createTransaction($acc_d, $acc_a, 4000, $obType, 'Initial Balance for Checking account', $this->yasom, $euro); + $this->createTransaction($acc_e, $acc_b, 10000, $obType, 'Initial Balance for Savings account', $this->yasom, $euro); + $this->createTransaction($acc_f, $acc_c, 100, $obType, 'Initial Balance for Delete me', $this->yasom, $euro); + } + + /** + * @param Account $from + * @param Account $to + * @param $amount + * @param TransactionType $type + * @param $description + * @param $date + * @param TransactionCurrency $currency + * + * @param Budget $budget + * @param Category $category + * @param Bill $bill + * + * @return TransactionJournal + */ + public function createTransaction( + Account $from, Account $to, $amount, TransactionType $type, $description, $date, TransactionCurrency $currency, Budget $budget = null, + Category $category = null, Bill $bill = null + ) { + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + + $billID = is_null($bill) ? null : $bill->id; + + + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::create( + [ + 'user_id' => $user->id, 'transaction_type_id' => $type->id, 'transaction_currency_id' => $currency->id, 'bill_id' => $billID, + 'description' => $description, 'completed' => 1, 'date' => $date + ] + ); + + Transaction::create(['account_id' => $from->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount * -1]); + Transaction::create(['account_id' => $to->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount]); + + if (!is_null($budget)) { + $journal->budgets()->save($budget); + } + if (!is_null($category)) { + $journal->categories()->save($category); + } + + return $journal; + } + + /** + * @param User $user + */ + public function createBudgets(User $user) + { + + $groceries = Budget::create(['user_id' => $user->id, 'name' => 'Groceries']); + $bills = Budget::create(['user_id' => $user->id, 'name' => 'Bills']); + $deleteMe = Budget::create(['user_id' => $user->id, 'name' => 'Delete me']); + Budget::create(['user_id' => $user->id, 'name' => 'Budget without repetition']); + $groceriesLimit = BudgetLimit::create( + ['startdate' => $this->som, 'amount' => 201, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $groceries->id] + ); + $billsLimit = BudgetLimit::create( + ['startdate' => $this->som, 'amount' => 202, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $bills->id] + ); + $deleteMeLimit = BudgetLimit::create( + ['startdate' => $this->som, 'amount' => 203, 'repeats' => 0, 'repeat_freq' => 'monthly', 'budget_id' => $deleteMe->id] + ); + + // and because we have no filters, some repetitions: + LimitRepetition::create(['budget_limit_id' => $groceriesLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 201]); + LimitRepetition::create(['budget_limit_id' => $billsLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 202]); + LimitRepetition::create(['budget_limit_id' => $deleteMeLimit->id, 'startdate' => $this->som, 'enddate' => $this->eom, 'amount' => 203]); + } + + /** + * @param User $user + */ + public function createCategories(User $user) + { + Category::create(['user_id' => $user->id, 'name' => 'DailyGroceries']); + Category::create(['user_id' => $user->id, 'name' => 'Lunch']); + Category::create(['user_id' => $user->id, 'name' => 'House']); + Category::create(['user_id' => $user->id, 'name' => 'Delete me']); + + } + + /** + * @param User $user + */ + public function createPiggyBanks(User $user) + { + // account + $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + + // some dates + $endDate = clone $this->_startOfMonth; + $nextYear = clone $this->_startOfMonth; + + $endDate->addMonths(4); + $nextYear->addYear()->subDay(); + + $next = $nextYear->format('Y-m-d'); + $end = $endDate->format('Y-m-d'); + + // piggy bank + $newCamera = PiggyBank::create( + [ + 'account_id' => $savings->id, + 'name' => 'New camera', + 'targetamount' => 2000, + 'startdate' => $this->som, + 'targetdate' => null, + 'repeats' => 0, + 'rep_length' => null, + 'rep_every' => 0, + 'rep_times' => null, + 'reminder' => null, + 'reminder_skip' => 0, + 'remind_me' => 0, + 'order' => 0, + ] + ); + // and some events! + PiggyBankEvent::create(['piggy_bank_id' => $newCamera->id, 'date' => $this->som, 'amount' => 100]); + PiggyBankRepetition::create(['piggy_bank_id' => $newCamera->id, 'startdate' => $this->som, 'targetdate' => null, 'currentamount' => 100]); + + + $newClothes = PiggyBank::create( + [ + 'account_id' => $savings->id, + 'name' => 'New clothes', + 'targetamount' => 2000, + 'startdate' => $this->som, + 'targetdate' => $end, + 'repeats' => 0, + 'rep_length' => null, + 'rep_every' => 0, + 'rep_times' => null, + 'reminder' => null, + 'reminder_skip' => 0, + 'remind_me' => 0, + 'order' => 0, + ] + ); + + PiggyBankEvent::create(['piggy_bank_id' => $newClothes->id, 'date' => $this->som, 'amount' => 100]); + PiggyBankRepetition::create(['piggy_bank_id' => $newClothes->id, 'startdate' => $this->som, 'targetdate' => $end, 'currentamount' => 100]); + + // weekly reminder piggy bank + $weekly = PiggyBank::create( + [ + 'account_id' => $savings->id, + 'name' => 'Weekly reminder for clothes', + 'targetamount' => 2000, + 'startdate' => $this->som, + 'targetdate' => $next, + 'repeats' => 0, + 'rep_length' => null, + 'rep_every' => 0, + 'rep_times' => null, + 'reminder' => 'week', + 'reminder_skip' => 0, + 'remind_me' => 1, + 'order' => 0, + ] + ); + PiggyBankRepetition::create(['piggy_bank_id' => $weekly->id, 'startdate' => $this->som, 'targetdate' => $next, 'currentamount' => 0]); + } + + /** + * @param User $user + */ + public function createReminders(User $user) + { + // for weekly piggy bank (clothes) + $nextWeek = clone $this->_startOfMonth; + $piggyBank = PiggyBank::whereName('New clothes')->orderBy('id', 'DESC')->first(); + $nextWeek->addWeek(); + $week = $nextWeek->format('Y-m-d'); + + Reminder::create( + ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $week, 'active' => 1, 'notnow' => 0, + 'remindersable_id' => $piggyBank->id, 'remindersable_type' => 'PiggyBank'] + ); + + // a fake reminder:: + Reminder::create( + ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $week, 'active' => 0, 'notnow' => 0, 'remindersable_id' => 40, + 'remindersable_type' => 'Transaction'] + ); + } + + /** + * @param User $user + */ + public function createRecurringTransactions(User $user) + { + // account + $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + + $recurring = PiggyBank::create( + [ + 'account_id' => $savings->id, + 'name' => 'Nieuwe spullen', + 'targetamount' => 1000, + 'startdate' => $this->som, + 'targetdate' => $this->eom, + 'repeats' => 1, + 'rep_length' => 'month', + 'rep_every' => 0, + 'rep_times' => 0, + 'reminder' => 'month', + 'reminder_skip' => 0, + 'remind_me' => 1, + 'order' => 0, + ] + ); + PiggyBankRepetition::create(['piggy_bank_id' => $recurring->id, 'startdate' => $this->som, 'targetdate' => $this->eom, 'currentamount' => 0]); + PiggyBankRepetition::create( + ['piggy_bank_id' => $recurring->id, 'startdate' => $this->nsom, 'targetdate' => $this->neom, 'currentamount' => 0] + ); + Reminder::create( + ['user_id' => $user->id, 'startdate' => $this->som, 'enddate' => $this->neom, 'active' => 1, 'notnow' => 0, + 'remindersable_id' => $recurring->id, 'remindersable_type' => 'PiggyBank'] + ); + } + + /** + * @param $user + */ + public function createBills($user) + { + // bill + Bill::create( + [ + 'user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,landlord', + 'amount_min' => 700, + 'amount_max' => 900, + 'date' => $this->som, + 'active' => 1, + 'automatch' => 1, + 'repeat_freq' => 'monthly', + 'skip' => 0, + ] + ); + + // bill + Bill::create( + [ + 'user_id' => $user->id, + 'name' => 'Gas licht', + 'match' => 'no,match', + 'amount_min' => 500, + 'amount_max' => 700, + 'date' => $this->som, + 'active' => 1, + 'automatch' => 1, + 'repeat_freq' => 'monthly', + 'skip' => 0, + ] + ); + + // bill + Bill::create( + [ + 'user_id' => $user->id, + 'name' => 'Something something', + 'match' => 'mumble,mumble', + 'amount_min' => 500, + 'amount_max' => 700, + 'date' => $this->som, + 'active' => 0, + 'automatch' => 1, + 'repeat_freq' => 'monthly', + 'skip' => 0, + ] + ); + + } + + /** + * @param $user + */ + public function createExpenseAccounts($user) + { + //// create expenses for rent, utilities, water, TV, phone on the 1st of the month. + $expenseType = AccountType::whereType('Expense account')->first(); + + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Land lord', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Utilities company', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Water company', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'TV company', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Phone agency', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Super savers', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Groceries House', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Lunch House', 'active' => 1]); + + + Account::create(['user_id' => $user->id, 'account_type_id' => $expenseType->id, 'name' => 'Buy More', 'active' => 1]); + + } + + /** + * @param $user + */ + public function createRevenueAccounts($user) + { + $revenueType = AccountType::whereType('Revenue account')->first(); + + Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'Employer', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'IRS', 'active' => 1]); + Account::create(['user_id' => $user->id, 'account_type_id' => $revenueType->id, 'name' => 'Second job employer', 'active' => 1]); + + } + + /** + * @param Carbon $date + */ + public function createGroceries(Carbon $date) + { + // variables we need: + $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); + $shopOne = Account::whereName('Groceries House')->orderBy('id', 'DESC')->first(); + $shopTwo = Account::whereName('Super savers')->orderBy('id', 'DESC')->first(); + $lunchHouse = Account::whereName('Lunch House')->orderBy('id', 'DESC')->first(); + $lunch = Category::whereName('Lunch')->orderBy('id', 'DESC')->first(); + $daily = Category::whereName('DailyGroceries')->orderBy('id', 'DESC')->first(); + $euro = TransactionCurrency::whereCode('EUR')->first(); + $withdrawal = TransactionType::whereType('Withdrawal')->first(); + $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); + + + $shops = [$shopOne, $shopTwo]; + + // create groceries and lunch (daily, between 5 and 10 euro). + $mStart = clone $date; + $mEnd = clone $date; + $mEnd->endOfMonth(); + while ($mStart <= $mEnd) { + $mFormat = $mStart->format('Y-m-d'); + $shop = $shops[rand(0, 1)]; + + $this->createTransaction($checking, $shop, (rand(500, 1000) / 100), $withdrawal, 'Groceries', $mFormat, $euro, $groceries, $daily); + $this->createTransaction($checking, $lunchHouse, (rand(200, 600) / 100), $withdrawal, 'Lunch', $mFormat, $euro, $groceries, $lunch); + + $mStart->addDay(); + } + } + + /** + * @param $date + */ + public function createBigExpense($date) + { + $date->addDays(12); + $dollar = TransactionCurrency::whereCode('USD')->first(); + $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); + $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + $buyMore = Account::whereName('Buy More')->orderBy('id', 'DESC')->first(); + $withdrawal = TransactionType::whereType('Withdrawal')->first(); + $transfer = TransactionType::whereType('Transfer')->first(); + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + + + // create some big expenses, move some money around. + $amount = rand(500, 2000); + $one = $this->createTransaction( + $savings, $checking, $amount, $transfer, 'Money for big expense in ' . $date->format('F Y'), $date->format('Y-m-d'), $dollar + ); + $two = $this->createTransaction( + $checking, $buyMore, $amount, $withdrawal, 'Big expense in ' . $date->format('F Y'), $date->format('Y-m-d'), $dollar + ); + $group = TransactionGroup::create( + [ + 'user_id' => $user->id, + 'relation' => 'balance' + ] + ); + $group->transactionjournals()->save($one); + $group->transactionjournals()->save($two); + } + + +} \ No newline at end of file From 21e89c3b6479cabd59bfc31303023e1028263193 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 17 Jan 2015 11:31:12 +0100 Subject: [PATCH 12/71] Remove composer.lock when running Travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1f82f07b02..3da09d1ad1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 5.6 install: + - rm composer.lock - composer install script: From 037135e764b5578e84215c9dde3e908decc8d02a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 00:10:57 +0100 Subject: [PATCH 13/71] A complete gamble on my side to fix a bug where transfers FROM shared accounts were not counted as income. --- app/lib/FireflyIII/Report/Report.php | 1 + app/lib/FireflyIII/Report/ReportQuery.php | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index 91a3caae95..d704cfb31a 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -46,6 +46,7 @@ class Report implements ReportInterface } /** + * This methods fails to take in account transfers FROM shared accounts. * @param Carbon $start * @param Carbon $end * @param int $limit diff --git a/app/lib/FireflyIII/Report/ReportQuery.php b/app/lib/FireflyIII/Report/ReportQuery.php index a53618adcd..a83a544d95 100644 --- a/app/lib/FireflyIII/Report/ReportQuery.php +++ b/app/lib/FireflyIII/Report/ReportQuery.php @@ -306,8 +306,23 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_types.type', 'Withdrawal') - ->where('acm_from.data', '!=', '"sharedExpense"') + // not shared, withdrawal + ->where( + function ($q) { + $q->where( + function ($q) { + $q->where('transaction_types.type', 'Withdrawal'); + $q->where('acm_from.data', '!=', '"sharedExpense"'); + } + )->orWhere( + function ($q) { + $q->where('transaction_types.type', 'Transfer'); + $q->where('acm_from.data', '=', '"sharedExpense"'); + } + ); + } + ) + // shared, transfer? ->before($end) ->after($start) ->where('transaction_journals.user_id', \Auth::user()->id) From 8a0f76ab6875a56823f385fe78075f21b78dc44d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:48:24 +0100 Subject: [PATCH 14/71] Code cleanup. --- app/controllers/GoogleChartController.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/controllers/GoogleChartController.php b/app/controllers/GoogleChartController.php index 99d5662b12..af1400bfb1 100644 --- a/app/controllers/GoogleChartController.php +++ b/app/controllers/GoogleChartController.php @@ -117,8 +117,6 @@ class GoogleChartController extends BaseController $this->_chart->addColumn('Budgeted', 'number'); $this->_chart->addColumn('Spent', 'number'); - Log::debug('Now in allBudgetsHomeChart()'); - /** @var \FireflyIII\Database\Budget\Budget $bdt */ $bdt = App::make('FireflyIII\Database\Budget\Budget'); $budgets = $bdt->get(); @@ -126,18 +124,13 @@ class GoogleChartController extends BaseController /** @var Budget $budget */ foreach ($budgets as $budget) { - Log::debug('Now working budget #' . $budget->id . ', ' . $budget->name); - /** @var \LimitRepetition $repetition */ $repetition = $bdt->repetitionOnStartingOnDate($budget, $this->_start); - if (is_null($repetition)) { - \Log::debug('Budget #' . $budget->id . ' has no repetition on ' . $this->_start->format('Y-m-d')); - // use the session start and end for our search query + if (is_null($repetition)) { // use the session start and end for our search query $searchStart = $this->_start; $searchEnd = $this->_end; $limit = 0; // the limit is zero: } else { - \Log::debug('Budget #' . $budget->id . ' has a repetition on ' . $this->_start->format('Y-m-d') . '!'); // use the limit's start and end for our search query $searchStart = $repetition->startdate; $searchEnd = $repetition->enddate; @@ -145,7 +138,6 @@ class GoogleChartController extends BaseController } $expenses = floatval($budget->transactionjournals()->before($searchEnd)->after($searchStart)->lessThan(0)->sum('amount')) * -1; - \Log::debug('Expenses in budget ' . $budget->name . ' before ' . $searchEnd->format('Y-m-d') . ' and after ' . $searchStart . ' are: ' . $expenses); if ($expenses > 0) { $this->_chart->addRow($budget->name, $limit, $expenses); } @@ -314,18 +306,15 @@ class GoogleChartController extends BaseController if ($repetition) { $budgeted = floatval($repetition->amount); - \Log::debug('Found a repetition on ' . $start->format('Y-m-d'). ' for budget ' . $budget->name.'!'); + \Log::debug('Found a repetition on ' . $start->format('Y-m-d') . ' for budget ' . $budget->name . '!'); } else { - \Log::debug('No repetition on ' . $start->format('Y-m-d'). ' for budget ' . $budget->name); + \Log::debug('No repetition on ' . $start->format('Y-m-d') . ' for budget ' . $budget->name); $budgeted = null; } - $this->_chart->addRow(clone $start, $budgeted, $spent); - $start->addMonth(); } - $this->_chart->generate(); return Response::json($this->_chart->getData()); From 45447646fa01794913d76a62dd95ee7caecc4410 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:48:29 +0100 Subject: [PATCH 15/71] Code cleanup. --- app/controllers/PiggybankController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 051a5d2ca5..9f279598c1 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -334,10 +334,11 @@ class PiggyBankController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update piggy bank: ' . $messages['errors']->first()); + return Redirect::route('piggy_banks.edit', $piggyBank->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('piggy_banks.edit', $piggyBank->id)->withInput(); } From a36cab969fcf5b1f6cabd899e6f8578d822faf7a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:48:36 +0100 Subject: [PATCH 16/71] Code cleanup. --- app/controllers/RepeatedExpenseController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/RepeatedExpenseController.php b/app/controllers/RepeatedExpenseController.php index 61abf4a133..32dbc7c0ce 100644 --- a/app/controllers/RepeatedExpenseController.php +++ b/app/controllers/RepeatedExpenseController.php @@ -135,7 +135,7 @@ class RepeatedExpenseController extends BaseController } /** - * + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. */ public function store() { @@ -150,7 +150,6 @@ class RepeatedExpenseController extends BaseController $data['remind_me'] = isset($data['remind_me']) ? 1 : 0; $data['order'] = 0; - // always validate: $messages = $this->_repository->validate($data); Session::flash('warnings', $messages['warnings']); From 79ff67852fc3bb741cedc8738118e2cd5d4550e8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:48:48 +0100 Subject: [PATCH 17/71] Deleted an old unique index. --- .../2015_01_18_082406_changes_for_325.php | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 app/database/migrations/2015_01_18_082406_changes_for_325.php diff --git a/app/database/migrations/2015_01_18_082406_changes_for_325.php b/app/database/migrations/2015_01_18_082406_changes_for_325.php new file mode 100644 index 0000000000..7c86443bb9 --- /dev/null +++ b/app/database/migrations/2015_01_18_082406_changes_for_325.php @@ -0,0 +1,43 @@ +dropIndex('unique_ci_combo'); + $table->dropUnique('unique_ci_combi'); + }); + + } + +} From 10a93df6539749679122cc37354b571eb749a547 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:48:58 +0100 Subject: [PATCH 18/71] Clean up test data seeder --- app/database/seeds/TestDataSeeder.php | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/app/database/seeds/TestDataSeeder.php b/app/database/seeds/TestDataSeeder.php index bfa8e63de1..13028277d4 100644 --- a/app/database/seeds/TestDataSeeder.php +++ b/app/database/seeds/TestDataSeeder.php @@ -3,6 +3,10 @@ use Carbon\Carbon; /** * @SuppressWarnings("CamelCase") // I'm fine with this. + * @SuppressWarnings("TooManyMethods") // I'm fine with this + * @SuppressWarnings("CouplingBetweenObjects") // I'm fine with this + * @SuppressWarnings("MethodLength") // I'm fine with this + * * Class TestDataSeeder */ @@ -410,16 +414,8 @@ class TestDataSeeder extends Seeder { // bill Bill::create( - [ - 'user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,landlord', - 'amount_min' => 700, - 'amount_max' => 900, - 'date' => $this->som, - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, - ] + ['user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,landlord', 'amount_min' => 700, 'amount_max' => 900, 'date' => $this->som, + 'active' => 1, 'automatch' => 1, 'repeat_freq' => 'monthly', 'skip' => 0,] ); // bill @@ -428,13 +424,10 @@ class TestDataSeeder extends Seeder 'user_id' => $user->id, 'name' => 'Gas licht', 'match' => 'no,match', - 'amount_min' => 500, - 'amount_max' => 700, + 'amount_min' => 500, 'amount_max' => 700, 'date' => $this->som, - 'active' => 1, - 'automatch' => 1, - 'repeat_freq' => 'monthly', - 'skip' => 0, + 'active' => 1, 'automatch' => 1, + 'repeat_freq' => 'monthly', 'skip' => 0, ] ); From 1068dcb8a437efb243e404c750b62dd8cc2bc427 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:49:32 +0100 Subject: [PATCH 19/71] Cleanup and refactor --- .../FireflyIII/Database/Account/Account.php | 16 +-- app/lib/FireflyIII/Database/Bill/Bill.php | 2 +- .../TransactionJournal/TransactionJournal.php | 18 ---- app/lib/FireflyIII/Event/Piggybank.php | 4 - app/lib/FireflyIII/Report/Report.php | 5 +- app/lib/FireflyIII/Shared/Toolkit/Date.php | 93 +++++++--------- app/lib/FireflyIII/Shared/Toolkit/Filter.php | 101 ++++++++---------- app/lib/FireflyIII/Shared/Toolkit/Form.php | 2 + 8 files changed, 91 insertions(+), 150 deletions(-) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index 9fe77ea498..cb921b0baa 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -156,10 +156,7 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte */ public function destroy(Eloquent $model) { - - // delete piggy banks - // delete journals: - $journals = \TransactionJournal::whereIn( + $journals = \TransactionJournal::whereIn( 'id', function (QueryBuilder $query) use ($model) { $query->select('transaction_journal_id') ->from('transactions')->whereIn( @@ -183,9 +180,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte )->get(); } )->get(); - /* - * Get all transactions. - */ $transactions = []; /** @var \TransactionJournal $journal */ foreach ($journals as $journal) { @@ -195,18 +189,10 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } $journal->delete(); } - // also delete transactions. if (count($transactions) > 0) { \Transaction::whereIn('id', $transactions)->delete(); } - - - /* - * Trigger deletion: - */ \Event::fire('account.destroy', [$model]); - - // delete accounts: \Account::where( function (EloquentBuilder $q) use ($model) { $q->where('id', $model->id); diff --git a/app/lib/FireflyIII/Database/Bill/Bill.php b/app/lib/FireflyIII/Database/Bill/Bill.php index e987d4abc7..2110180426 100644 --- a/app/lib/FireflyIII/Database/Bill/Bill.php +++ b/app/lib/FireflyIII/Database/Bill/Bill.php @@ -114,7 +114,7 @@ class Bill implements CUDInterface, CommonDatabaseCallsInterface, BillInterface $warnings = new MessageBag; $successes = new MessageBag; $errors = new MessageBag; - if (isset($model['amount_min']) && isset($model['amount_max']) && floatval($model['amount_min']) > floatval($model['amount_max'])) { + if (floatval($model['amount_min']) > floatval($model['amount_max'])) { $errors->add('amount_max', 'Maximum amount can not be less than minimum amount.'); $errors->add('amount_min', 'Minimum amount can not be more than maximum amount.'); } diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index 10015a1afb..ba0b70bf27 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -158,31 +158,13 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C $journal->isValid(); $errors = $journal->getErrors(); - /* - * Is not in rules. - */ if (!isset($model['what'])) { $errors->add('description', 'Internal error: need to know type of transaction!'); } - /* - * Is not in rules. - */ $errors = $errors->merge($this->_validateAmount($model)); $errors = $errors->merge($this->_validateBudget($model)); $errors = $errors->merge($this->_validateAccount($model)); - - /* - * Add "OK" - */ - - /** - * else { - * $successes->add('account_from_id', 'OK'); - * $successes->add('account_to_id', 'OK'); - * } - * else { - */ $list = ['date', 'description', 'amount', 'budget_id', 'from', 'to', 'account_from_id', 'account_to_id', 'category', 'account_id', 'expense_account', 'revenue_account']; foreach ($list as $entry) { diff --git a/app/lib/FireflyIII/Event/Piggybank.php b/app/lib/FireflyIII/Event/Piggybank.php index 645f48432b..1e0b62c053 100644 --- a/app/lib/FireflyIII/Event/Piggybank.php +++ b/app/lib/FireflyIII/Event/Piggybank.php @@ -197,7 +197,6 @@ class PiggyBank foreach ($list as $entry) { $start = $entry->startdate; $target = $entry->targetdate; - // find a repetition on this date: $count = $entry->piggyBankrepetitions()->starts($start)->targets($target)->count(); if ($count == 0) { $repetition = new \PiggyBankRepetition; @@ -207,14 +206,11 @@ class PiggyBank $repetition->currentamount = 0; $repetition->save(); } - // then continue and do something in the current relevant time frame. - $currentTarget = clone $target; $currentStart = null; while ($currentTarget < $today) { $currentStart = \DateKit::subtractPeriod($currentTarget, $entry->rep_length, 0); $currentTarget = \DateKit::addPeriod($currentTarget, $entry->rep_length, 0); - // create if not exists: $count = $entry->piggyBankRepetitions()->starts($currentStart)->targets($currentTarget)->count(); if ($count == 0) { $repetition = new \PiggyBankRepetition; diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index d704cfb31a..b926e6662c 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -228,6 +228,8 @@ class Report implements ReportInterface } /** + * This method gets all incomes (journals) in a list. + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @param Carbon $date @@ -357,7 +359,6 @@ class Report implements ReportInterface } /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) * * @param Carbon $start * @param Carbon $end @@ -367,7 +368,7 @@ class Report implements ReportInterface */ public function revenueGroupedByAccount(Carbon $start, Carbon $end, $limit = 15) { - return $this->_queries->journalsByRevenueAccount($start, $end); + return $this->_queries->journalsByRevenueAccount($start, $end, $limit); diff --git a/app/lib/FireflyIII/Shared/Toolkit/Date.php b/app/lib/FireflyIII/Shared/Toolkit/Date.php index 7b41d9d273..443e343654 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Date.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Date.php @@ -102,6 +102,8 @@ class Date } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param Carbon $theCurrentEnd * @param $repeatFreq * @param Carbon $maxDate @@ -111,37 +113,32 @@ class Date */ public function endOfX(Carbon $theCurrentEnd, $repeatFreq, Carbon $maxDate) { + $functionMap = [ + 'daily' => 'endOfDay', + 'week' => 'endOfWeek', + 'weekly' => 'endOfWeek', + 'month' => 'endOfMonth', + 'monthly' => 'endOfMonth', + 'quarter' => 'lastOfQuarter', + 'quarterly' => 'lastOfQuarter', + 'year' => 'endOfYear', + 'yearly' => 'endOfYear', + ]; + $specials = ['mont', 'monthly']; + $currentEnd = clone $theCurrentEnd; - switch ($repeatFreq) { - default: - throw new FireflyException('Cannot do endOfPeriod for $repeat_freq ' . $repeatFreq); - break; - case 'daily': - $currentEnd->endOfDay(); - break; - case 'week': - case 'weekly': - $currentEnd->endOfWeek(); - break; - case 'month': - case 'monthly': - $currentEnd->endOfMonth(); - break; - case 'quarter': - case 'quarterly': - $currentEnd->lastOfQuarter(); - break; - case 'half-year': - $month = intval($theCurrentEnd->format('m')); - $currentEnd->endOfYear(); - if ($month <= 6) { - $currentEnd->subMonths(6); - } - break; - case 'year': - case 'yearly': - $currentEnd->endOfYear(); - break; + + if (isset($functionMap[$repeatFreq])) { + $function = $functionMap[$repeatFreq]; + $currentEnd->$function(); + + } + if (isset($specials[$repeatFreq])) { + $month = intval($theCurrentEnd->format('m')); + $currentEnd->endOfYear(); + if ($month <= 6) { + $currentEnd->subMonths(6); + } } if ($currentEnd > $maxDate) { return clone $maxDate; @@ -159,29 +156,21 @@ class Date */ public function periodShow(Carbon $date, $repeatFrequency) { - switch ($repeatFrequency) { - default: - throw new FireflyException('No date formats for frequency "' . $repeatFrequency . '"!'); - break; - case 'daily': - return $date->format('j F Y'); - break; - case 'week': - case 'weekly': - return $date->format('\W\e\e\k W, Y'); - break; - case 'quarter': - return $date->format('F Y'); - break; - case 'monthly': - case 'month': - return $date->format('F Y'); - break; - case 'year': - case 'yearly': - return $date->format('Y'); - break; + $formatMap = [ + 'daily' => 'j F Y', + 'week' => '\W\e\e\k W, Y', + 'weekly' => '\W\e\e\k W, Y', + 'quarter' => 'F Y', + 'month' => 'F Y', + 'monthly' => 'F Y', + 'year' => 'Y', + 'yearly' => 'Y', + + ]; + if (isset($formatMap[$repeatFrequency])) { + return $date->format($formatMap[$repeatFrequency]); } + throw new FireflyException('No date formats for frequency "' . $repeatFrequency . '"!'); } /** diff --git a/app/lib/FireflyIII/Shared/Toolkit/Filter.php b/app/lib/FireflyIII/Shared/Toolkit/Filter.php index f8fdf42037..ae04ae6062 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Filter.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Filter.php @@ -103,37 +103,31 @@ class Filter */ protected function updateEndDate($range, Carbon $start) { - $end = clone $start; - switch ($range) { - default: - throw new FireflyException('updateEndDate cannot handle $range ' . $range); - break; - case '1D': - $end->endOfDay(); - break; - case '1W': - $end->endOfWeek(); - break; - case '1M': - $end->endOfMonth(); - break; - case '3M': - $end->lastOfQuarter(); - break; - case '6M': - if (intval($start->format('m')) >= 7) { - $end->endOfYear(); - } else { - $end->startOfYear()->addMonths(6); - } - break; - case '1Y': - $end->endOfYear(); - break; + $functionMap = [ + '1D' => 'endOfDay', + '1W' => 'endOfWeek', + '1M' => 'endOfMonth', + '3M' => 'lastOfQuarter', + '1Y' => 'endOfYear', + ]; + $end = clone $start; + if (isset($functionMap[$range])) { + $function = $functionMap[$range]; + $end->$function(); + + return $end; } + if ($range == '6M') { + if (intval($start->format('m')) >= 7) { + $end->endOfYear(); + } else { + $end->startOfYear()->addMonths(6); + } - return $end; + return $end; + } + throw new FireflyException('updateEndDate cannot handle $range ' . $range); } /** @@ -145,37 +139,28 @@ class Filter */ protected function periodName($range, Carbon $date) { - switch ($range) { - default: - throw new FireflyException('No _periodName() for range "' . $range . '"'); - break; - case '1D': - return $date->format('jS F Y'); - break; - case '1W': - return 'week ' . $date->format('W, Y'); - break; - case '1M': - return $date->format('F Y'); - break; - case '3M': - $month = intval($date->format('m')); - - return 'Q' . ceil(($month / 12) * 4) . ' ' . $date->format('Y'); - break; - case '6M': - $month = intval($date->format('m')); - $half = ceil(($month / 12) * 2); - $halfName = $half == 1 ? 'first' : 'second'; - - return $halfName . ' half of ' . $date->format('d-m-Y'); - break; - case '1Y': - return $date->format('Y'); - break; - - + $formatMap = [ + '1D' => 'jS F Y', + '1W' => '\w\e\ek W, Y', + '1M' => 'F Y', + '1Y' => 'Y', + ]; + if (isset($formatMap[$range])) { + return $date->format($formatMap[$range]); } + if ($range == '3M') { + $month = intval($date->format('m')); + + return 'Q' . ceil(($month / 12) * 4) . ' ' . $date->format('Y'); + } + if ($range == '6M') { + $month = intval($date->format('m')); + $half = ceil(($month / 12) * 2); + $halfName = $half == 1 ? 'first' : 'second'; + + return $halfName . ' half of ' . $date->format('d-m-Y'); + } + throw new FireflyException('No _periodName() for range "' . $range . '"'); } /** diff --git a/app/lib/FireflyIII/Shared/Toolkit/Form.php b/app/lib/FireflyIII/Shared/Toolkit/Form.php index a5475a0456..1f51fe357a 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Form.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Form.php @@ -12,6 +12,8 @@ use Illuminate\Support\Collection; class Form { /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * Takes any collection and tries to make a sensible select list compatible array of it. * * @param Collection $set From 03e0510c4f9942d8198ad6c5984cb3581685a229 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:49:53 +0100 Subject: [PATCH 20/71] Fixed a bug where certain reports would not show incomes from shared accounts. --- app/controllers/TransactionController.php | 3 +- app/lib/FireflyIII/Report/ReportQuery.php | 42 ++++++++++++----------- app/views/list/journals-small.blade.php | 20 ++++++----- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index 5cef244661..c56d7ae08a 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -254,10 +254,11 @@ class TransactionController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not store transaction: ' . $messages['errors']->first()); + return Redirect::route('transactions.create', $data['what'])->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('transactions.create', $data['what'])->withInput(); } diff --git a/app/lib/FireflyIII/Report/ReportQuery.php b/app/lib/FireflyIII/Report/ReportQuery.php index a83a544d95..7ac2cd18bd 100644 --- a/app/lib/FireflyIII/Report/ReportQuery.php +++ b/app/lib/FireflyIII/Report/ReportQuery.php @@ -306,23 +306,8 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - // not shared, withdrawal - ->where( - function ($q) { - $q->where( - function ($q) { - $q->where('transaction_types.type', 'Withdrawal'); - $q->where('acm_from.data', '!=', '"sharedExpense"'); - } - )->orWhere( - function ($q) { - $q->where('transaction_types.type', 'Transfer'); - $q->where('acm_from.data', '=', '"sharedExpense"'); - } - ); - } - ) - // shared, transfer? + ->where('transaction_types.type', 'Withdrawal') + ->where('acm_from.data', '!=', '"sharedExpense"') ->before($end) ->after($start) ->where('transaction_journals.user_id', \Auth::user()->id) @@ -336,10 +321,11 @@ class ReportQuery implements ReportQueryInterface * * @param Carbon $start * @param Carbon $end + * @param int $limit * * @return Collection */ - public function journalsByRevenueAccount(Carbon $start, Carbon $end) + public function journalsByRevenueAccount(Carbon $start, Carbon $end, $limit = 15) { return \TransactionJournal:: leftJoin( @@ -365,8 +351,24 @@ class ReportQuery implements ReportQueryInterface } ) ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_types.type', 'Deposit') - ->where('acm_to.data', '!=', '"sharedExpense"') + ->where( + function ($query) { + $query->where( + function ($q) { + $q->where('transaction_types.type', 'Deposit'); + $q->where('acm_to.data', '!=', '"sharedExpense"'); + } + ); + $query->orWhere( + function ($q) { + $q->where('transaction_types.type', 'Transfer'); + $q->where('acm_from.data', '=', '"sharedExpense"'); + } + ); + } + ) + // ->where('transaction_types.type', 'Deposit') + // ->where('acm_to.data', '!=', '"sharedExpense"') ->before($end)->after($start) ->where('transaction_journals.user_id', \Auth::user()->id) ->groupBy('t_from.account_id')->orderBy('amount') diff --git a/app/views/list/journals-small.blade.php b/app/views/list/journals-small.blade.php index 9bf33f4b0b..b1ba57dacf 100644 --- a/app/views/list/journals-small.blade.php +++ b/app/views/list/journals-small.blade.php @@ -9,24 +9,28 @@ transactions[1]->amount);?> @if($journal->transactiontype->type == 'Withdrawal') - {{Amount::formatTransaction($journal->transactions[1],false)}} + {{Amount::formatTransaction($journal->transactions[1],false)}} @endif @if($journal->transactiontype->type == 'Deposit') - {{Amount::formatTransaction($journal->transactions[1],false)}} + {{Amount::formatTransaction($journal->transactions[1],false)}} @endif @if($journal->transactiontype->type == 'Transfer') - {{Amount::formatTransaction($journal->transactions[1],false)}} + {{Amount::formatTransaction($journal->transactions[1],false)}} @endif {{$journal->date->format('j F Y')}} - @if($journal->transactions[1]->account->accounttype->description == 'Cash account') - (cash) - @else - {{{$journal->transactions[1]->account->name}}} - @endif + @foreach($journal->transactions as $t) + @if(floatval($t->amount < 0)) + @if($t->account->accounttype->description == 'Cash account') + (cash) + @else + {{{$t->account->name}}} + @endif + @endif + @endforeach @endforeach From 0afe2a680e98d67195f8879cd8549bb9d0287ade Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 09:50:51 +0100 Subject: [PATCH 21/71] Gave report a subtitle. --- app/controllers/ReportController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php index 29be01240f..e88f69279c 100644 --- a/app/controllers/ReportController.php +++ b/app/controllers/ReportController.php @@ -50,11 +50,13 @@ class ReportController extends BaseController } $date = new Carbon($year . '-' . $month . '-01'); $dayEarly = clone $date; + $subTitle = 'Budget report for ' . $date->format('F Y'); + $subTitleIcon = 'fa-calendar'; $dayEarly = $dayEarly->subDay(); $accounts = $this->_repository->getAccountListBudgetOverview($date); $budgets = $this->_repository->getBudgetsForMonth($date); - return View::make('reports.budget', compact('date', 'accounts', 'budgets', 'dayEarly')); + return View::make('reports.budget', compact('subTitle','subTitleIcon','date', 'accounts', 'budgets', 'dayEarly')); } From c5a3de09cdf096d209ac07e9d3999cf32b4d3e0b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 10:33:01 +0100 Subject: [PATCH 22/71] Catch a sqlite error. --- .../migrations/2015_01_18_082406_changes_for_325.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/database/migrations/2015_01_18_082406_changes_for_325.php b/app/database/migrations/2015_01_18_082406_changes_for_325.php index 7c86443bb9..5db32ba627 100644 --- a/app/database/migrations/2015_01_18_082406_changes_for_325.php +++ b/app/database/migrations/2015_01_18_082406_changes_for_325.php @@ -1,6 +1,7 @@ dropIndex('unique_ci_combo'); - $table->dropUnique('unique_ci_combi'); - }); + try { + $table->dropUnique('unique_ci_combi'); + } catch (QueryException $e) { + // don't care. + } + } + ); } From 02b6191d47e5f383665fbb97e311970d7a010d95 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 11:12:17 +0100 Subject: [PATCH 23/71] Another attempt at catching the sqlite error. --- app/database/migrations/2015_01_18_082406_changes_for_325.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/database/migrations/2015_01_18_082406_changes_for_325.php b/app/database/migrations/2015_01_18_082406_changes_for_325.php index 5db32ba627..a00985b8bf 100644 --- a/app/database/migrations/2015_01_18_082406_changes_for_325.php +++ b/app/database/migrations/2015_01_18_082406_changes_for_325.php @@ -40,6 +40,8 @@ class ChangesFor325 extends Migration $table->dropUnique('unique_ci_combi'); } catch (QueryException $e) { // don't care. + } catch (\Exception $e) { + // don't care either. } } ); From bba1ee12646cbca103a773c2b2c0aea8d73bb09d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 11:18:06 +0100 Subject: [PATCH 24/71] Last attempt. --- .../2015_01_18_082406_changes_for_325.php | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/database/migrations/2015_01_18_082406_changes_for_325.php b/app/database/migrations/2015_01_18_082406_changes_for_325.php index a00985b8bf..55abb999c5 100644 --- a/app/database/migrations/2015_01_18_082406_changes_for_325.php +++ b/app/database/migrations/2015_01_18_082406_changes_for_325.php @@ -33,18 +33,21 @@ class ChangesFor325 extends Migration // // delete an old index: - Schema::table( - 'budget_limits', function (Blueprint $table) { - //$table->dropIndex('unique_ci_combo'); - try { + try { + Schema::table( + 'budget_limits', function (Blueprint $table) { + //$table->dropIndex('unique_ci_combo'); $table->dropUnique('unique_ci_combi'); - } catch (QueryException $e) { - // don't care. - } catch (\Exception $e) { - // don't care either. + } + ); + } catch (QueryException $e) { + // don't care. + } catch (PDOException $e) { + // don't care. + } catch (\Exception $e) { + // don't care either. } - ); } From 406b658801b75b37e9e86a920bb9c42ede78578c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 21:07:40 +0100 Subject: [PATCH 25/71] Code cleanup. --- app/controllers/PiggybankController.php | 3 +- app/controllers/RepeatedExpenseController.php | 6 +- app/controllers/ReportController.php | 12 +- app/controllers/TransactionController.php | 13 +- app/database/seeds/TestDataSeeder.php | 3 + .../FireflyIII/Database/Account/Account.php | 43 +++---- .../TransactionJournal/TransactionJournal.php | 2 + app/lib/FireflyIII/Event/Piggybank.php | 13 +- app/lib/FireflyIII/Report/Report.php | 3 +- app/lib/FireflyIII/Report/ReportQuery.php | 2 - app/lib/FireflyIII/Shared/Toolkit/Date.php | 116 ++++++++---------- app/lib/FireflyIII/Shared/Toolkit/Filter.php | 2 + 12 files changed, 105 insertions(+), 113 deletions(-) diff --git a/app/controllers/PiggybankController.php b/app/controllers/PiggybankController.php index 9f279598c1..673a2c31f0 100644 --- a/app/controllers/PiggybankController.php +++ b/app/controllers/PiggybankController.php @@ -290,11 +290,12 @@ class PiggyBankController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not store piggy bank: ' . $messages['errors']->first()); + return Redirect::route('piggy_banks.create')->withInput(); } // return to create screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('piggy_banks.create')->withInput(); } diff --git a/app/controllers/RepeatedExpenseController.php b/app/controllers/RepeatedExpenseController.php index 32dbc7c0ce..73494b0377 100644 --- a/app/controllers/RepeatedExpenseController.php +++ b/app/controllers/RepeatedExpenseController.php @@ -178,6 +178,8 @@ class RepeatedExpenseController extends BaseController } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param PiggyBank $repeatedExpense * * @return $this @@ -194,7 +196,6 @@ class RepeatedExpenseController extends BaseController $data['remind_me'] = isset($data['remind_me']) ? 1 : 0; $data['user_id'] = Auth::user()->id; - // always validate: $messages = $this->_repository->validate($data); Session::flash('warnings', $messages['warnings']); @@ -202,10 +203,11 @@ class RepeatedExpenseController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update repeated expense: ' . $messages['errors']->first()); + return Redirect::route('repeated.edit', $repeatedExpense->id)->withInput(); } // return to update screen: - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('repeated.edit', $repeatedExpense->id)->withInput(); } diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php index e88f69279c..9d2f730003 100644 --- a/app/controllers/ReportController.php +++ b/app/controllers/ReportController.php @@ -48,15 +48,15 @@ class ReportController extends BaseController } catch (Exception $e) { return View::make('error')->with('message', 'Invalid date'); } - $date = new Carbon($year . '-' . $month . '-01'); - $dayEarly = clone $date; + $date = new Carbon($year . '-' . $month . '-01'); + $dayEarly = clone $date; $subTitle = 'Budget report for ' . $date->format('F Y'); $subTitleIcon = 'fa-calendar'; - $dayEarly = $dayEarly->subDay(); - $accounts = $this->_repository->getAccountListBudgetOverview($date); - $budgets = $this->_repository->getBudgetsForMonth($date); + $dayEarly = $dayEarly->subDay(); + $accounts = $this->_repository->getAccountListBudgetOverview($date); + $budgets = $this->_repository->getBudgetsForMonth($date); - return View::make('reports.budget', compact('subTitle','subTitleIcon','date', 'accounts', 'budgets', 'dayEarly')); + return View::make('reports.budget', compact('subTitle', 'subTitleIcon', 'date', 'accounts', 'budgets', 'dayEarly')); } diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index c56d7ae08a..b987272c45 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -230,6 +230,8 @@ class TransactionController extends BaseController } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param $what * * @return $this|\Illuminate\Http\RedirectResponse @@ -245,8 +247,6 @@ class TransactionController extends BaseController $data['completed'] = 0; $data['what'] = $what; $data['currency'] = 'EUR'; - - // always validate: $messages = $this->_repository->validate($data); Session::flash('warnings', $messages['warnings']); @@ -257,17 +257,13 @@ class TransactionController extends BaseController return Redirect::route('transactions.create', $data['what'])->withInput(); } - // return to create screen: if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('transactions.create', $data['what'])->withInput(); } - // store $journal = $this->_repository->store($data); Event::fire('transactionJournal.store', [$journal, Input::get('piggy_bank_id')]); // new and used. - /* - * Also trigger on both transactions. - */ + /** @var Transaction $transaction */ foreach ($journal->transactions as $transaction) { Event::fire('transaction.store', [$transaction]); @@ -303,8 +299,9 @@ class TransactionController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update transaction: ' . $messages['errors']->first()); + return Redirect::route('transactions.edit', $journal->id)->withInput(); } - if ($data['post_submit_action'] == 'validate_only' || $messages['errors']->count() > 0) { + if ($data['post_submit_action'] == 'validate_only') { return Redirect::route('transactions.edit', $journal->id)->withInput(); } $this->_repository->update($journal, $data); diff --git a/app/database/seeds/TestDataSeeder.php b/app/database/seeds/TestDataSeeder.php index 13028277d4..fa6cb958d0 100644 --- a/app/database/seeds/TestDataSeeder.php +++ b/app/database/seeds/TestDataSeeder.php @@ -184,6 +184,9 @@ class TestDataSeeder extends Seeder } /** + * @SuppressWarnings(PHPMD.ShortVariable) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * * @param Account $from * @param Account $to * @param $amount diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index cb921b0baa..33a8c85ace 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -150,6 +150,7 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot make it shorter because of query. * @param Eloquent $model * * @return bool @@ -159,25 +160,25 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte $journals = \TransactionJournal::whereIn( 'id', function (QueryBuilder $query) use ($model) { $query->select('transaction_journal_id') - ->from('transactions')->whereIn( - 'account_id', function (QueryBuilder $query) use ($model) { - $query - ->select('id') - ->from('accounts') - ->where( - function (QueryBuilder $q) use ($model) { - $q->where('id', $model->id); - $q->orWhere( - function (QueryBuilder $q) use ($model) { - $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); - $q->where('accounts.account_type_id', 3); - $q->where('accounts.active', 0); - } - ); - } - )->where('accounts.user_id', $this->getUser()->id); - } - )->get(); + ->from('transactions') + ->whereIn( + 'account_id', function (QueryBuilder $query) use ($model) { + $query + ->select('id')->from('accounts') + ->where( + function (QueryBuilder $q) use ($model) { + $q->where('id', $model->id); + $q->orWhere( + function (QueryBuilder $q) use ($model) { + $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); + $q->where('accounts.account_type_id', 3); + $q->where('accounts.active', 0); + } + ); + } + )->where('accounts.user_id', $this->getUser()->id); + } + )->get(); } )->get(); $transactions = []; @@ -218,9 +219,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte public function store(array $data) { - /* - * Find account type. - */ /** @var \FireflyIII\Database\AccountType\AccountType $acctType */ $acctType = \App::make('FireflyIII\Database\AccountType\AccountType'); @@ -230,7 +228,6 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte $data['account_type_id'] = $accountType->id; $data['active'] = isset($data['active']) && $data['active'] === '1' ? 1 : 0; - $data = array_except($data, ['_token', 'what']); $account = new \Account($data); if (!$account->isValid()) { diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index ba0b70bf27..ce0cfbafcc 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -363,11 +363,13 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C $errors->add('account_id', 'Invalid account.'); } break; + // often seen in deposits case (isset($model['account_id']) && isset($model['revenue_account'])): if (intval($model['account_id']) < 1) { $errors->add('account_id', 'Invalid account.'); } break; + // often seen in transfers case (isset($model['account_from_id']) && isset($model['account_to_id'])): if (intval($model['account_from_id']) < 1 || intval($model['account_from_id']) < 1) { $errors->add('account_from_id', 'Invalid account selected.'); diff --git a/app/lib/FireflyIII/Event/Piggybank.php b/app/lib/FireflyIII/Event/Piggybank.php index 1e0b62c053..5188658a2a 100644 --- a/app/lib/FireflyIII/Event/Piggybank.php +++ b/app/lib/FireflyIII/Event/Piggybank.php @@ -180,6 +180,7 @@ class PiggyBank } /** + * * Validates the presence of repetitions for all repeated expenses! */ public function validateRepeatedExpenses() @@ -189,24 +190,20 @@ class PiggyBank } /** @var \FireflyIII\Database\PiggyBank\RepeatedExpense $repository */ $repository = \App::make('FireflyIII\Database\PiggyBank\RepeatedExpense'); - $list = $repository->get(); $today = Carbon::now(); - /** @var \PiggyBank $entry */ foreach ($list as $entry) { - $start = $entry->startdate; - $target = $entry->targetdate; - $count = $entry->piggyBankrepetitions()->starts($start)->targets($target)->count(); + $count = $entry->piggyBankrepetitions()->starts($entry->startdate)->targets($entry->targetdate)->count(); if ($count == 0) { $repetition = new \PiggyBankRepetition; $repetition->piggyBank()->associate($entry); - $repetition->startdate = $start; - $repetition->targetdate = $target; + $repetition->startdate = $entry->startdate; + $repetition->targetdate = $entry->targetdate; $repetition->currentamount = 0; $repetition->save(); } - $currentTarget = clone $target; + $currentTarget = clone $entry->startdate; $currentStart = null; while ($currentTarget < $today) { $currentStart = \DateKit::subtractPeriod($currentTarget, $entry->rep_length, 0); diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index b926e6662c..70b651eb74 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -232,6 +232,8 @@ class Report implements ReportInterface * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * + * TODO: This method runs two queries which are only marginally different. Try and combine these. + * * @param Carbon $date * @param bool $shared * @@ -245,7 +247,6 @@ class Report implements ReportInterface $end->endOfMonth(); $userId = $this->_accounts->getUser()->id; - $list = \TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') ->leftJoin( diff --git a/app/lib/FireflyIII/Report/ReportQuery.php b/app/lib/FireflyIII/Report/ReportQuery.php index 7ac2cd18bd..4f8fd1a663 100644 --- a/app/lib/FireflyIII/Report/ReportQuery.php +++ b/app/lib/FireflyIII/Report/ReportQuery.php @@ -367,8 +367,6 @@ class ReportQuery implements ReportQueryInterface ); } ) - // ->where('transaction_types.type', 'Deposit') - // ->where('acm_to.data', '!=', '"sharedExpense"') ->before($end)->after($start) ->where('transaction_journals.user_id', \Auth::user()->id) ->groupBy('t_from.account_id')->orderBy('amount') diff --git a/app/lib/FireflyIII/Shared/Toolkit/Date.php b/app/lib/FireflyIII/Shared/Toolkit/Date.php index 443e343654..65d1d49ab9 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Date.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Date.php @@ -183,39 +183,34 @@ class Date public function startOfPeriod(Carbon $theDate, $repeatFreq) { $date = clone $theDate; - switch ($repeatFreq) { - default: - throw new FireflyException('Cannot do startOfPeriod for $repeat_freq ' . $repeatFreq); - break; - case 'daily': - $date->startOfDay(); - break; - case 'week': - case 'weekly': - $date->startOfWeek(); - break; - case 'month': - case 'monthly': - $date->startOfMonth(); - break; - case 'quarter': - case 'quarterly': - $date->firstOfQuarter(); - break; - case 'half-year': - $month = intval($date->format('m')); - $date->startOfYear(); - if ($month >= 7) { - $date->addMonths(6); - } - break; - case 'year': - case 'yearly': - $date->startOfYear(); - break; - } - return $date; + $functionMap = [ + 'daily' => 'startOfDay', + 'week' => 'startOfWeek', + 'weekly' => 'startOfWeek', + 'month' => 'startOfMonth', + 'monthly' => 'startOfMonth', + 'quarter' => 'firstOfQuarter', + 'quartly' => 'firstOfQuarter', + 'year' => 'startOfYear', + 'yearly' => 'startOfYear', + ]; + if (isset($functionMap[$repeatFreq])) { + $function = $functionMap[$repeatFreq]; + $date->$function(); + + return $date; + } + if ($repeatFreq == 'half-year') { + $month = intval($date->format('m')); + $date->startOfYear(); + if ($month >= 7) { + $date->addMonths(6); + } + + return $date; + } + throw new FireflyException('Cannot do startOfPeriod for $repeat_freq ' . $repeatFreq); } /** @@ -229,38 +224,35 @@ class Date public function subtractPeriod(Carbon $theDate, $repeatFreq, $subtract = 1) { $date = clone $theDate; - switch ($repeatFreq) { - default: - throw new FireflyException('Cannot do subtractPeriod for $repeat_freq ' . $repeatFreq); - break; - case 'day': - case 'daily': - $date->subDays($subtract); - break; - case 'week': - case 'weekly': - $date->subWeeks($subtract); - break; - case 'month': - case 'monthly': - $date->subMonths($subtract); - break; - case 'quarter': - case 'quarterly': - $months = $subtract * 3; - $date->subMonths($months); - break; - case 'half-year': - $months = $subtract * 6; - $date->subMonths($months); - break; - case 'year': - case 'yearly': - $date->subYears($subtract); - break; + + $functionMap = [ + 'daily' => 'subDays', + 'week' => 'subWeeks', + 'weekly' => 'subWeeks', + 'month' => 'subMonths', + 'monthly' => 'subMonths', + 'year' => 'subYears', + 'yearly' => 'subYears', + ]; + $modifierMap = [ + 'quarter' => 3, + 'quarterly' => 3, + 'half-year' => 6, + ]; + if (isset($functionMap[$repeatFreq])) { + $function = $functionMap[$repeatFreq]; + $date->$function($subtract); + + return $date; + } + if (isset($modifierMap[$repeatFreq])) { + $subtract = $subtract * $modifierMap[$repeatFreq]; + $date->subMonths($subtract); + + return $date; } - return $date; + throw new FireflyException('Cannot do subtractPeriod for $repeat_freq ' . $repeatFreq); } } diff --git a/app/lib/FireflyIII/Shared/Toolkit/Filter.php b/app/lib/FireflyIII/Shared/Toolkit/Filter.php index ae04ae6062..73ca317790 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Filter.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Filter.php @@ -131,6 +131,8 @@ class Filter } /** + * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. + * * @param $range * @param Carbon $date * From 1b685da3e3900a1c9a7465a171422e8940756437 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Jan 2015 21:40:00 +0100 Subject: [PATCH 26/71] Code cleanup and query optimisation. --- app/controllers/TransactionController.php | 2 + app/lib/FireflyIII/Event/Piggybank.php | 1 + app/lib/FireflyIII/Report/Report.php | 114 +++++++++--------- app/lib/FireflyIII/Report/ReportQuery.php | 64 ++++++++++ .../Report/ReportQueryInterface.php | 12 ++ app/lib/FireflyIII/Shared/Toolkit/Filter.php | 54 ++++----- app/views/reports/month.blade.php | 36 +++++- 7 files changed, 197 insertions(+), 86 deletions(-) diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index b987272c45..5be29c9855 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -162,6 +162,8 @@ class TransactionController extends BaseController } /** + * @SuppressWarnings("CyclomaticComplexity") // It's 7. More than 5 but alright. + * * @param $what * * @return $this diff --git a/app/lib/FireflyIII/Event/Piggybank.php b/app/lib/FireflyIII/Event/Piggybank.php index 5188658a2a..9273f5c600 100644 --- a/app/lib/FireflyIII/Event/Piggybank.php +++ b/app/lib/FireflyIII/Event/Piggybank.php @@ -180,6 +180,7 @@ class PiggyBank } /** + * @SuppressWarnings("CyclomaticComplexity") // It's 6. More than 5 but alright. * * Validates the presence of repetitions for all repeated expenses! */ diff --git a/app/lib/FireflyIII/Report/Report.php b/app/lib/FireflyIII/Report/Report.php index 70b651eb74..0dfce025fc 100644 --- a/app/lib/FireflyIII/Report/Report.php +++ b/app/lib/FireflyIII/Report/Report.php @@ -7,7 +7,6 @@ use FireflyIII\Database\Account\Account as AccountRepository; use FireflyIII\Database\SwitchUser; use FireflyIII\Database\TransactionJournal\TransactionJournal as JournalRepository; use Illuminate\Database\Query\Builder; -use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; /** @@ -47,6 +46,7 @@ class Report implements ReportInterface /** * This methods fails to take in account transfers FROM shared accounts. + * * @param Carbon $start * @param Carbon $end * @param int $limit @@ -117,7 +117,7 @@ class Report implements ReportInterface $accounts = []; /** @var \Account $account */ foreach ($list as $account) { - $id = intval($account->id); + $id = intval($account->id); /** @noinspection PhpParamsInspection */ $accounts[$id] = [ 'name' => $account->name, @@ -232,8 +232,6 @@ class Report implements ReportInterface * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * - * TODO: This method runs two queries which are only marginally different. Try and combine these. - * * @param Carbon $date * @param bool $shared * @@ -247,45 +245,48 @@ class Report implements ReportInterface $end->endOfMonth(); $userId = $this->_accounts->getUser()->id; - $list = \TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin( - 'account_meta', function (JoinClause $join) { - $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); - } - ) - ->transactionTypes(['Deposit']) - ->where('transaction_journals.user_id', $userId) - ->where('transactions.amount', '>', 0) - ->where('transaction_journals.user_id', \Auth::user()->id) - ->where('account_meta.data', '!=', '"sharedExpense"') - ->orderBy('date', 'ASC') - ->before($end)->after($start)->get(['transaction_journals.*']); + return $this->_queries->incomeByPeriod($start, $end); - // incoming from a shared account: it's profit (income): - $transfers = \TransactionJournal::withRelevantData() - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin( - 'account_meta', function (JoinClause $join) { - $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); - } - ) - ->transactionTypes(['Transfer']) - ->where('transaction_journals.user_id', $userId) - ->where('transactions.amount', '<', 0) - ->where('account_meta.data', '=', '"sharedExpense"') - ->orderBy('date', 'ASC') - ->before($end)->after($start)->get(['transaction_journals.*']); + // $list = \TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + // ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + // ->leftJoin( + // 'account_meta', function (JoinClause $join) { + // $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + // } + // ) + // ->transactionTypes(['Deposit']) + // ->where('transaction_journals.user_id', $userId) + // ->where('transactions.amount', '>', 0) + // ->where('transaction_journals.user_id', \Auth::user()->id) + // ->where('account_meta.data', '!=', '"sharedExpense"') + // ->orderBy('date', 'ASC') + // ->before($end)->after($start)->get(['transaction_journals.*']); + // + // // incoming from a shared account: it's profit (income): + // $transfers = \TransactionJournal::withRelevantData() + // ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + // ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + // ->leftJoin( + // 'account_meta', function (JoinClause $join) { + // $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + // } + // ) + // ->transactionTypes(['Transfer']) + // ->where('transaction_journals.user_id', $userId) + // ->where('transactions.amount', '<', 0) + // ->where('account_meta.data', '=', '"sharedExpense"') + // ->orderBy('date', 'ASC') + // ->before($end)->after($start)->get(['transaction_journals.*']); + // + // $list = $list->merge($transfers); + // $list->sort( + // function (\TransactionJournal $journal) { + // return $journal->date->format('U'); + // } + // ); + // + // return $list; - $list = $list->merge($transfers); - $list->sort( - function (\TransactionJournal $journal) { - return $journal->date->format('U'); - } - ); - - return $list; } /** @@ -302,21 +303,21 @@ class Report implements ReportInterface \PiggyBank:: leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') - ->where('accounts.user_id', \Auth::user()->id) - ->where('repeats', 0) - ->where( - function (Builder $query) use ($start, $end) { - $query->whereNull('piggy_banks.deleted_at'); - $query->orWhere( - function (Builder $query) use ($start, $end) { - $query->whereNotNull('piggy_banks.deleted_at'); - $query->where('piggy_banks.deleted_at', '>=', $start->format('Y-m-d 00:00:00')); - $query->where('piggy_banks.deleted_at', '<=', $end->format('Y-m-d 00:00:00')); - } - ); - } - ) - ->get(['piggy_banks.*']); + ->where('accounts.user_id', \Auth::user()->id) + ->where('repeats', 0) + ->where( + function (Builder $query) use ($start, $end) { + $query->whereNull('piggy_banks.deleted_at'); + $query->orWhere( + function (Builder $query) use ($start, $end) { + $query->whereNotNull('piggy_banks.deleted_at'); + $query->where('piggy_banks.deleted_at', '>=', $start->format('Y-m-d 00:00:00')); + $query->where('piggy_banks.deleted_at', '<=', $end->format('Y-m-d 00:00:00')); + } + ); + } + ) + ->get(['piggy_banks.*']); } @@ -372,7 +373,6 @@ class Report implements ReportInterface return $this->_queries->journalsByRevenueAccount($start, $end, $limit); - } /** diff --git a/app/lib/FireflyIII/Report/ReportQuery.php b/app/lib/FireflyIII/Report/ReportQuery.php index 4f8fd1a663..2591b858a6 100644 --- a/app/lib/FireflyIII/Report/ReportQuery.php +++ b/app/lib/FireflyIII/Report/ReportQuery.php @@ -199,6 +199,70 @@ class ReportQuery implements ReportQueryInterface } + /** + * This method returns all "income" journals in a certain period, which are both transfers from a shared account + * and "ordinary" deposits. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does + * not group and returns different fields. + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function incomeByPeriod(Carbon $start, Carbon $end) + { + return \TransactionJournal:: + leftJoin( + 'transactions as t_from', function (JoinClause $join) { + $join->on('t_from.transaction_journal_id', '=', 'transaction_journals.id')->where('t_from.amount', '<', 0); + } + ) + ->leftJoin('accounts as ac_from', 't_from.account_id', '=', 'ac_from.id') + ->leftJoin( + 'account_meta as acm_from', function (JoinClause $join) { + $join->on('ac_from.id', '=', 'acm_from.account_id')->where('acm_from.name', '=', 'accountRole'); + } + ) + ->leftJoin( + 'transactions as t_to', function (JoinClause $join) { + $join->on('t_to.transaction_journal_id', '=', 'transaction_journals.id')->where('t_to.amount', '>', 0); + } + ) + ->leftJoin('accounts as ac_to', 't_to.account_id', '=', 'ac_to.id') + ->leftJoin( + 'account_meta as acm_to', function (JoinClause $join) { + $join->on('ac_to.id', '=', 'acm_to.account_id')->where('acm_to.name', '=', 'accountRole'); + } + ) + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->where( + function ($query) { + $query->where( + function ($q) { + $q->where('transaction_types.type', 'Deposit'); + $q->where('acm_to.data', '!=', '"sharedExpense"'); + } + ); + $query->orWhere( + function ($q) { + $q->where('transaction_types.type', 'Transfer'); + $q->where('acm_from.data', '=', '"sharedExpense"'); + } + ); + } + ) + ->before($end)->after($start) + ->where('transaction_journals.user_id', \Auth::user()->id) + ->groupBy('t_from.account_id')->orderBy('transaction_journals.date') + ->get( + ['transaction_journals.id', + 'transaction_journals.description', + 'transaction_types.type', + 't_to.amount', 'transaction_journals.date', 't_from.account_id as account_id', + 'ac_from.name as name'] + ); + } + /** * Gets a list of expenses grouped by the budget they were filed under. * diff --git a/app/lib/FireflyIII/Report/ReportQueryInterface.php b/app/lib/FireflyIII/Report/ReportQueryInterface.php index a2097c6635..046835b843 100644 --- a/app/lib/FireflyIII/Report/ReportQueryInterface.php +++ b/app/lib/FireflyIII/Report/ReportQueryInterface.php @@ -117,6 +117,18 @@ interface ReportQueryInterface */ public function journalsByRevenueAccount(Carbon $start, Carbon $end); + /** + * This method returns all "income" journals in a certain period, which are both transfers from a shared account + * and "ordinary" deposits. The query used is almost equal to ReportQueryInterface::journalsByRevenueAccount but it does + * not group and returns different fields. + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function incomeByPeriod(Carbon $start, Carbon $end); + /** * With an equally misleading name, this query returns are transfers to shared accounts. These are considered * expenses. diff --git a/app/lib/FireflyIII/Shared/Toolkit/Filter.php b/app/lib/FireflyIII/Shared/Toolkit/Filter.php index 73ca317790..70f0aef677 100644 --- a/app/lib/FireflyIII/Shared/Toolkit/Filter.php +++ b/app/lib/FireflyIII/Shared/Toolkit/Filter.php @@ -174,37 +174,35 @@ class Filter */ public function previous($range, Carbon $date) { - switch ($range) { - default: - throw new FireflyException('Cannot do _previous() on ' . $range); - break; - case '1D': - $date->startOfDay()->subDay(); - break; - case '1W': - $date->startOfWeek()->subWeek(); - break; - case '1M': - $date->startOfMonth()->subMonth(); - break; - case '3M': - $date->firstOfQuarter()->subMonths(3)->firstOfQuarter(); - break; - case '6M': - $month = intval($date->format('m')); - if ($month <= 6) { - $date->startOfYear()->subMonths(6); - } else { - $date->startOfYear(); - } - break; - case '1Y': - $date->startOfYear()->subYear(); - break; + $functionMap = [ + '1D' => 'Day', + '1W' => 'Week', + '1M' => 'Month', + '1Y' => 'Year' + ]; + if (isset($functionMap[$range])) { + $startFunction = 'startOf' . $functionMap[$range]; + $subFunction = 'sub' . $functionMap[$range]; + $date->$startFunction()->$subFunction(); + + return $date; } + if ($range == '3M') { + $date->firstOfQuarter()->subMonths(3)->firstOfQuarter(); - return $date; + return $date; + } + if ($range == '6M') { + $month = intval($date->format('m')); + $date->startOfYear(); + if ($month <= 6) { + $date->subMonths(6); + } + + return $date; + } + throw new FireflyException('Cannot do _previous() on ' . $range); } /** diff --git a/app/views/reports/month.blade.php b/app/views/reports/month.blade.php index c60d473d62..6943a09ba8 100644 --- a/app/views/reports/month.blade.php +++ b/app/views/reports/month.blade.php @@ -5,7 +5,41 @@
Income
- @include('list.journals-small',['journals' => $income]) + + + @foreach($income as $entry) + + + + + + + @endforeach + @if(isset($displaySum) && $displaySum === true) + + + + + + @endif +
+ {{{$entry->description}}} + + amount);?> + @if($entry->type == 'Withdrawal') + {{Amount::format($entry->amount,false)}} + @endif + @if($entry->type == 'Deposit') + {{Amount::format($entry->amount,false)}} + @endif + @if($entry->type == 'Transfer') + {{Amount::format($entry->amount,false)}} + @endif + + {{$entry->date->format('j F Y')}} + + {{{$entry->name}}} +
Sum{{Amount::format($tableSum)}}
From 8eb84acf4fe0deeae2b373a0b961181ab3cdb7e0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 19 Jan 2015 06:33:30 +0100 Subject: [PATCH 27/71] Show the view for transactions without a category. --- app/routes.php | 2 +- app/views/list/categories.blade.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/routes.php b/app/routes.php index 94f89f51b5..2bc973242b 100644 --- a/app/routes.php +++ b/app/routes.php @@ -204,7 +204,7 @@ Route::group( Route::get('/categories/edit/{category}', ['uses' => 'CategoryController@edit', 'as' => 'categories.edit']); Route::get('/categories/delete/{category}', ['uses' => 'CategoryController@delete', 'as' => 'categories.delete']); Route::get('/categories/show/{category}', ['uses' => 'CategoryController@show', 'as' => 'categories.show']); - Route::get('/categories/list/noCategory', ['uses' => 'CategoryController@noCategory', 'as' => 'categories.noBudget']); + Route::get('/categories/list/noCategory', ['uses' => 'CategoryController@noCategory', 'as' => 'categories.noCategory']); // currency controller Route::get('/currency', ['uses' => 'CurrencyController@index', 'as' => 'currency.index']); diff --git a/app/views/list/categories.blade.php b/app/views/list/categories.blade.php index 29e6d7e27f..c12a88762c 100644 --- a/app/views/list/categories.blade.php +++ b/app/views/list/categories.blade.php @@ -4,6 +4,11 @@ Name Last activity + +   + Without a category +   + @foreach($categories as $category) From 1887977b929c99adf6477ec746109975b3204cb5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 19 Jan 2015 07:21:44 +0100 Subject: [PATCH 28/71] Small experimental cleaning up. --- app/controllers/TransactionController.php | 5 +-- .../TransactionJournal/TransactionJournal.php | 32 +++++++++---------- app/lib/FireflyIII/Form/Form.php | 20 ++++++++++-- app/views/partials/menu.blade.php | 2 +- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/app/controllers/TransactionController.php b/app/controllers/TransactionController.php index 5be29c9855..91cd99c644 100644 --- a/app/controllers/TransactionController.php +++ b/app/controllers/TransactionController.php @@ -248,14 +248,14 @@ class TransactionController extends BaseController $data['transaction_currency_id'] = $transactionCurrency->id; $data['completed'] = 0; $data['what'] = $what; - $data['currency'] = 'EUR'; - $messages = $this->_repository->validate($data); + $messages = $this->_repository->validate($data); Session::flash('warnings', $messages['warnings']); Session::flash('successes', $messages['successes']); Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not store transaction: ' . $messages['errors']->first()); + return Redirect::route('transactions.create', $data['what'])->withInput(); } @@ -301,6 +301,7 @@ class TransactionController extends BaseController Session::flash('errors', $messages['errors']); if ($messages['errors']->count() > 0) { Session::flash('error', 'Could not update transaction: ' . $messages['errors']->first()); + return Redirect::route('transactions.edit', $journal->id)->withInput(); } if ($data['post_submit_action'] == 'validate_only') { diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index ce0cfbafcc..65b842871d 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -63,11 +63,11 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C */ public function store(array $data) { - $currency = $this->getJournalCurrency($data['currency']); - $journal = new \TransactionJournal( + $journal = new \TransactionJournal( [ 'transaction_type_id' => $data['transaction_type_id'], - 'transaction_currency_id' => $currency->id, 'user_id' => $this->getUser()->id, + 'transaction_currency_id' => $data['transaction_currency_id'], + 'user_id' => $this->getUser()->id, 'description' => $data['description'], 'date' => $data['date'], 'completed' => 0] ); $journal->save(); @@ -178,19 +178,6 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C } - /** - * @param $currency - * - * @return null|\TransactionCurrency - */ - public function getJournalCurrency($currency) - { - /** @var \FireflyIII\Database\TransactionCurrency\TransactionCurrency $currencyRepository */ - $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency\TransactionCurrency'); - - return $currencyRepository->findByCode($currency); - } - /** * @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind. * @@ -304,6 +291,19 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C return $typeRepository->findByWhat($type); } + /** + * @param $currency + * + * @return null|\TransactionCurrency + */ + public function getJournalCurrency($currency) + { + /** @var \FireflyIII\Database\TransactionCurrency\TransactionCurrency $currencyRepository */ + $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency\TransactionCurrency'); + + return $currencyRepository->findByCode($currency); + } + /** * @SuppressWarnings("CamelCase") // I'm fine with this. * diff --git a/app/lib/FireflyIII/Form/Form.php b/app/lib/FireflyIII/Form/Form.php index 68409ab2df..2548b2d511 100644 --- a/app/lib/FireflyIII/Form/Form.php +++ b/app/lib/FireflyIII/Form/Form.php @@ -54,7 +54,7 @@ class Form /* * Make label and placeholder look nice. */ - $options['placeholder'] = ucfirst($name); + $options['placeholder'] = ucfirst($label); /* * Get pre filled value: @@ -123,7 +123,19 @@ class Form $html .= \Form::input('text', $name, $value, $options); break; case 'amount': - $html .= '
' . \Amount::getCurrencySymbol() . '
'; + // currency button: + $defaultCurrency = \Amount::getDefaultCurrency(); + $html .= + '
'; + // all currencies: + $list = \TransactionCurrency::where('name', '!=', $defaultCurrency->name)->get(); + $html .= '
'; + //$html .= '
' . \Amount::getCurrencySymbol() . '
'; $html .= \Form::input('number', $name, $value, $options); $html .= '
'; break; @@ -204,7 +216,9 @@ class Form return $options['label']; } $labels = ['amount_min' => 'Amount (min)', 'amount_max' => 'Amount (max)', 'match' => 'Matches on', 'repeat_freq' => 'Repetition', - 'account_from_id' => 'Account from', 'account_to_id' => 'Account to', 'account_id' => 'Asset account']; + 'account_from_id' => 'Account from', 'account_to_id' => 'Account to', 'account_id' => 'Asset account','budget_id' => 'Budget' + ,'piggy_bank_id' => 'Piggy bank']; + return isset($labels[$name]) ? $labels[$name] : str_replace('_', ' ', ucfirst($name)); diff --git a/app/views/partials/menu.blade.php b/app/views/partials/menu.blade.php index 05ed6c3bbd..07c825d9e3 100644 --- a/app/views/partials/menu.blade.php +++ b/app/views/partials/menu.blade.php @@ -177,7 +177,7 @@ Transfer
  • - Bills + Bill
  • From f4b68d26d68151e65040ea6c184b57ab363093de Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 23 Jan 2015 06:45:20 +0100 Subject: [PATCH 29/71] Fixed a bug where a deposit would not get linked to a cash account. --- app/lib/FireflyIII/Database/Account/Account.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index 33a8c85ace..a6ae29c5ed 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -464,6 +464,16 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte $accountType = $typeRepository->findByWhat('revenue'); + // if name is "", find cash account: + if (strlen($name) == 0) { + $cashAccountType = $typeRepository->findByWhat('cash'); + + // find or create cash account: + return \Account::firstOrCreate( + ['name' => 'Cash account', 'account_type_id' => $cashAccountType->id, 'active' => 0, 'user_id' => $this->getUser()->id,] + ); + } + $data = ['user_id' => $this->getUser()->id, 'account_type_id' => $accountType->id, 'name' => $name, 'active' => 1]; return \Account::firstOrCreate($data); From 8dc3e3ec93c45b412c3650d94f6e71beb79a8203 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 23 Jan 2015 06:50:40 +0100 Subject: [PATCH 30/71] Add an actions menu to the account view. --- app/views/accounts/show.blade.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/views/accounts/show.blade.php b/app/views/accounts/show.blade.php index e5b4fc9e04..69d93d900b 100644 --- a/app/views/accounts/show.blade.php +++ b/app/views/accounts/show.blade.php @@ -6,6 +6,21 @@
    {{{$account->name}}} + + + +
    +
    + + +
    +
    From 4af041e015494158855b87f3efc1721b0940673f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 06:52:22 +0100 Subject: [PATCH 31/71] Let's see what happens when we run Hack. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3da09d1ad1..319e36e88d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: php php: - 5.5 - 5.6 + - hhvm install: - rm composer.lock From 4bd79c880c8274bba2816f43451595e163460b1b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 06:55:42 +0100 Subject: [PATCH 32/71] Build in hack. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 319e36e88d..d788453a8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php php: - 5.5 - 5.6 - - hhvm + - hhvm install: - rm composer.lock From 2fbf83735494323970168875a8b986abc5e93ffe Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 06:56:02 +0100 Subject: [PATCH 33/71] Closed #40 [skip ci] --- app/lib/FireflyIII/Database/Account/Account.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index a6ae29c5ed..b794db802b 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -164,14 +164,16 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte ->whereIn( 'account_id', function (QueryBuilder $query) use ($model) { $query - ->select('id')->from('accounts') + ->select('accounts.id') + ->from('accounts') + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->where( function (QueryBuilder $q) use ($model) { $q->where('id', $model->id); $q->orWhere( function (QueryBuilder $q) use ($model) { $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); - $q->where('accounts.account_type_id', 3); + $q->where('account_types.type', 'Initial balance account'); $q->where('accounts.active', 0); } ); @@ -194,13 +196,15 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte \Transaction::whereIn('id', $transactions)->delete(); } \Event::fire('account.destroy', [$model]); - \Account::where( + \Account:: + leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->where( function (EloquentBuilder $q) use ($model) { $q->where('id', $model->id); $q->orWhere( function (EloquentBuilder $q) use ($model) { $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); - $q->where('accounts.account_type_id', 3); + $q->where('account_types.type', 'Initial balance account'); $q->where('accounts.active', 0); } ); From 0905ceb1d57bfd295a60780c93fe5ef7ceb89186 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 07:00:58 +0100 Subject: [PATCH 34/71] Codeception will not run in hhvm [skip ci] --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d788453a8c..3da09d1ad1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: php php: - 5.5 - 5.6 - - hhvm install: - rm composer.lock From b766d93d9af982f99750e4bda5e8d35d1eb98767 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 07:15:03 +0100 Subject: [PATCH 35/71] Fixed the account role view. --- .../FireflyIII/Database/Account/Account.php | 25 ++++++++++--------- app/models/Account.php | 24 ++++++++++-------- app/views/accounts/create.blade.php | 2 +- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/app/lib/FireflyIII/Database/Account/Account.php b/app/lib/FireflyIII/Database/Account/Account.php index b794db802b..4edfed9116 100644 --- a/app/lib/FireflyIII/Database/Account/Account.php +++ b/app/lib/FireflyIII/Database/Account/Account.php @@ -52,7 +52,7 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte * Basic query: */ $query = $this->getUser()->accounts()->accountTypeIn($types)->withMeta()->orderBy('name', 'ASC');; - $set = $query->get(['accounts.*']); + $set = $query->get(['accounts.*', 'account_meta.data as accountRole']); $set->each( function (\Account $account) { @@ -60,6 +60,7 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte * Get last activity date. */ $account->lastActivityDate = $this->getLastActivity($account); + $account->accountRole = \Config::get('firefly.accountRoles.' . json_decode($account->accountRole)); } ); @@ -197,19 +198,19 @@ class Account implements CUDInterface, CommonDatabaseCallsInterface, AccountInte } \Event::fire('account.destroy', [$model]); \Account:: - leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where( - function (EloquentBuilder $q) use ($model) { - $q->where('id', $model->id); - $q->orWhere( + leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->where( function (EloquentBuilder $q) use ($model) { - $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); - $q->where('account_types.type', 'Initial balance account'); - $q->where('accounts.active', 0); + $q->where('id', $model->id); + $q->orWhere( + function (EloquentBuilder $q) use ($model) { + $q->where('accounts.name', 'LIKE', '%' . $model->name . '%'); + $q->where('account_types.type', 'Initial balance account'); + $q->where('accounts.active', 0); + } + ); } - ); - } - )->delete(); + )->delete(); return true; diff --git a/app/models/Account.php b/app/models/Account.php index 667aae069e..ad2f43d875 100644 --- a/app/models/Account.php +++ b/app/models/Account.php @@ -1,10 +1,10 @@ ['required', 'between:1,100'], 'user_id' => 'required|exists:users,id', 'account_type_id' => 'required|exists:account_types,id', 'active' => 'required|boolean' ]; - protected $dates = ['deleted_at', 'created_at', 'updated_at']; - protected $fillable = ['name', 'user_id', 'account_type_id', 'active']; + protected $dates = ['deleted_at', 'created_at', 'updated_at']; + protected $fillable = ['name', 'user_id', 'account_type_id', 'active']; /** * Account type. @@ -67,7 +67,7 @@ class Account extends Eloquent /** * * @param EloquentBuilder $query - * @param array $types + * @param array $types */ public function scopeAccountTypeIn(EloquentBuilder $query, array $types) { @@ -82,9 +82,13 @@ class Account extends Eloquent * * @param EloquentBuilder $query */ - public function scopeWithMeta(EloquentBuilder $query) + public function scopeWithMeta(EloquentBuilder $query, $field = 'accountRole') { - $query->with(['accountmeta']); + $query->leftJoin( + 'account_meta', function (JoinClause $join) use ($field) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', $field); + } + ); } /** diff --git a/app/views/accounts/create.blade.php b/app/views/accounts/create.blade.php index 8fc3f78c5a..1a65eee009 100644 --- a/app/views/accounts/create.blade.php +++ b/app/views/accounts/create.blade.php @@ -31,7 +31,7 @@ @if($what == 'asset') {{Form::ffBalance('openingbalance')}} {{Form::ffDate('openingbalancedate', date('Y-m-d'))}} - @endif + @endif {{Form::ffCheckbox('active','1',true)}} {{Form::ffSelect('account_role',Config::get('firefly.accountRoles'))}}
    From 4ad67a87f18e621a375e44598619a6cf35191b5a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 24 Jan 2015 07:46:57 +0100 Subject: [PATCH 36/71] Fixed budget charts. --- app/views/budgets/show.blade.php | 5 +++-- public/assets/javascript/firefly/budgets.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/budgets/show.blade.php b/app/views/budgets/show.blade.php index b6c35c646d..7378d42146 100644 --- a/app/views/budgets/show.blade.php +++ b/app/views/budgets/show.blade.php @@ -8,7 +8,7 @@ Overview
    -
    +
    @@ -71,7 +71,8 @@ @stop @section('scripts') + + + + + + + + + + + + + + + + + + diff --git a/app/views/layouts/guest.blade.php b/app/views/layouts/guest.blade.php index 6b5a7d023b..006619c34c 100644 --- a/app/views/layouts/guest.blade.php +++ b/app/views/layouts/guest.blade.php @@ -18,7 +18,23 @@ - + + + + + + + + + + + + + + + + + diff --git a/favicon-full.png b/favicon-full.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ff22c6fe5db7ad4a1dafe7e75f119d83f703d2 GIT binary patch literal 48420 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
    jJ)!dt9B0;Ep!2Y~5Jo3pz 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 WxHUX&-TM=$fx*+&&t;ucLK6UC`v*z@ literal 0 HcmV?d00001 diff --git a/public/android-chrome-72x72.png b/public/android-chrome-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..c65517d0af38aba091ef3595580f1264d6094048 GIT binary patch literal 1693 zcmV;O24eY%P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru-vJ&KBm>YE+`#|< z1&~QZK~!ko?V4$9R8nlqyj`B?2PuAz)$@2~i9IMfoE!28BqJ z_|s@iR6=50{~<1kAwq&7L8Z7wY6wOOR$7)}DP`Iz-DW7AndgrlANRg)%zK?jOg!&L z@0@va?)T2!&N;8}zlE|*D+St!#X1MAOO{(~e zJ`BrslBcc>(lquCf*H6@F(cQVhp1pOMaq*`uxA(?gEUWC4fw|pCc-inGX)>#*~KTc z0^@jtMZ^BWODI>K39yPrGUl^q_=a-)Y^Hx${}>oNlN%f_FVH<`IQFxXPV;k$vqUX; zF&5?qXc4gy{a_GfJvBsFOC3o8Jjil+YvPc2DM)A+P*$j+x(izN?R*Dle(}s_ERGZ(QD1PYXbt=fi zgNIohQvBnp1lcVezehmPe=OQA?sFGtI;Sx(XknJ3Eig~71C(TE(CZLrCN}`^JAWzK zNz_QIo(^|`?v!P5fIdZA%k|Pal&wK$KtW~$kmjhSpG`ghNt)aRDr2g2+@fgx+$ODF zV(tP(WCmTLQ_+UGSy~Bt+y%PTjAztPw2~X8Wng4$(#cL;&ZE*QWd&VifFRRY$#~J1 zv6{np0fTy?jvjs#39^JANSpg{A5o5ToU>fg&an(BQlv0=KX(DWkGuJju2GIu>s6oa z7J0SeVcn?V*w7(bBsV~X>>t!whLjRd4|S#Zh~U+j&wf((u!SI{lu#;~2DTC-NCD%S z%~HmRzMlpT(n%gk4)QIRa^oTJP)s=sB*aMbwDMgZpPF9T&IETe(|07mn5StUmZUku zb9Rh6*%$|grq^1dM)~a$Z;>1-36+k z41l<0_0-}4k~G>gYaeKm9IWm1E7~A)#IDVp)ILy={5Gzg=vB-FAfsWr0OaFd*<<3G zey$|QJ+!$CRK{Fssf*sjLb2GEeXQsVC?HX@p#c?{$w(KSkD{}Q@Do;E>OoY?cl8D6;{cxBzfDCDEqkGYJgrj7CYnfNkfDinW;jvI8vazuPajx{PkAioy!JkR4?Eb#t5iC{spM-?irGH) zv4JO8$3E@<66(w|3n0itd@*dr@lE?Ug=`q`BBtRdh`JSB|r0qR1>0;C^0f)-Q|Bw{sHC0$4+~mf_DG_03~!qSaf7zbY(hYa%Ew3WdJfT zF*PkPF)cGNR5CF-Gd4OfGb=DLIxsMl*abxZ001R)MObuXVRU6WZEs|0W_bWIFflbP nFflDNFjO)zIx{vpFf%JKF*-0X<%PZZ00000NkvXXu0mjf<5~Xj literal 0 HcmV?d00001 diff --git a/public/android-chrome-96x96.png b/public/android-chrome-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..6268a69d57fa706d921c5040a4b1a61a6421f878 GIT binary patch literal 2209 zcmV;S2wwMzP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru-vJ&KBm>YE+`#|< z2b4)fK~#9!?VMX|R7Duaf7@-h?Y6}hT4=4cB2WtmDn;W3#7h(tuknfoj1n)2#tRSL zqYp-7BvD_CQ8Zp2jMr!|5m0MHMLcJVMpZUa2J36P_0&C3am1K+~p0sSj;CkXg-E}(&_#AxMvwh$Gz%UMln3hzkenJmd1USY4) zK_h%fov>|Bq4q5-o^c0!F3Z`if%5~W0_Cht5x&LKZotpsKKjN0^c^qJ$gnbK4r`x| zahn?iuAw{S8n)=>JyviNYuHD^X6ff;N+uqDElp{_|AWhuLyDg{JWF%3*YwlK_2j!z z|IeiXzlk$!zx7eWy?n$=EM<}#;Gv$Dl;O9rNc#@In_kcpDZ?KgYm#zP%Gi=Jfo~`~ z_JXoC6E9?*bT`OO{^36kQNqQXZfh8Wyg_IJsx(bmE!A){w=xBP(o)06jw!9wZLq48 z@v{Kfs&vprge3y2GAk-4D+KB}P3Uj1$zjbEUjsU6^df^j@Wl25nwvh?w^XR54xX5v2=H zW2ST$A>lm&b(9M|Pnxn0ORZ#u+|1o&)6QHz*Gj9Wv$%*2SOEB$#0*wSlNg-IBD#_! ze1!Nf{Q}J{7^+#teH2Q6*~exE@L^HNOr}$6E72m%P*TsKndO>MYrm>Hqng1TUe}3KL%~06EEXVLshXY{rwmd1R@M+<2Et+ zNRUe&egfmx?IFJ4_4ErjLEu9wSVj?rR7x;Ac$%#w$S0qCN}0_a(i{yI9sEoa5d!$> zQV%8HZIh0Y!bRMT)wC87)r*5?Mlpx*1fgK`N74C%>2~(ky{`p>=EK)GD#q@74AY*9ioeA@qaERe&t> zm@1w$w(o9|fF*%GsIe~+6PKl-=K%uc(rQ?3B9orMW9lQGjW<6 zwmh*?gNW8o@&JK)F_Cq$&0+INmN4ncnIbS>Y@Q}GA@XY5w%<_Z z=>_HqG>G@aHOT|jwo;;+u&>Jm@}-T7eoX_JYP$=YP4P29U=k+@y+gCpTVcBkDa%dQ z3Cy5M=$n-mo+kpnhvh(~Ia;O(l<=T{*w5!m%q`$ZYwgTDL&YS40FQB#xZA36BMKdP zq5I8+6-^LW$tp1i5An99eHTmdBH&mOIi6krMKv>8Nkh8d!p4U!XRNb1?BDKNnv-r`Xn;AQ@GoG`M8a<}rHd@kYraZgBE zJYp7pC0`#ymd|TaNDIKvdAvX7`7wLNJY<%@Q3oE2CW%wh!tF+$&zS7YAht(|1-cdri z2@SA;#RO9LRhZX!kzU7N6JoI#r;k=z>EjpH(TY+3(=PT;TEvyCWQk)~JR%lh!bI4? zyEJMeoHMzd0z&NNZ(0cx&n9#o0e}*&004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru-vJ&KBm>YE+`#|< z2^L92K~#9!?VMY1RaY6uf9K?KPLhU%+<>G=S_rfd652o|21cn2Rdmp?ok8u3Rz*9E zopx+G+82G$Q93?W<2Z~XtvVyL(!nC32v(ewSndKPBwRwd3t-aSi zoRjR8o!#NTUiLcsJ8OUcz4p3&>-!em$9;GPKDQN!6MlRcjD9jn`pBn()f5n9h;BOR z;6qN)h2gepP9cv<&NGZ5r5--yHP#VuTfOPS#|Hkxh>752>flc-cU#HnA&G5-YzdC1 zGd%9LqEkmQFVk~RiUtOM=K;5sof;CUq%S6l1|2-%wz_ukkwz|gq~llI`!3xvQZ$J0 zbhNkS7P6^eBc){Hql-HBbBJ!?{ulWL=`ok)pM8DoTz)_;VW}>{?4x8tVkH-1u0;cb zQJ!_1l#Iuzwv2gW>C|1CIqZ*%qQNNNbeool4b(+Z_6VEV!W&V>MRf|Z*==HzDC6Qi zY9!2%lEg<;bAkceAtcLfYU9Y^V4O8HM!bft%qN*_zQP{bP4~O0;z75mP2wkUe})DT zcCtVyBR~be;|8M`7__m6O|z+IEv9h_wOGX*OGybl$}7CV%ajpxo0bS4zn;RUx{sVF zCC)l5;Y!>TFH`Ea+VNrg-BuJ&Yka|NrQ<^?)yft(NFRMR-BC@HPc<&g)ZBFiYoz=T zC;2xGgy;cQ@*HKRe?<5LZ8M~uL#UMMsD&RgSGr%wVN*Sxl?DzEAcX^h`n`Nx(KCL7 zh(z+|&OP2g{UTyOq0~$6d$AHkQ<@aNd;ztWFZevTo2i(zf|f=+qvj9k|n}$(N4Wm zrc|0}-{!X0NIoV!rHN*RM1sjU{IO)x@&L&ciiKPgeF~YmCPV)%hP_2HkHtc+nIVNt zAq%B^Gmb(($%ln85;~MJ#biqPPOp(HCRNDwD`i%hrg{B13f&}qtdjC+3Yh>)O~0gQ z*2-ZNaY>qXpQlK=?PO^;G*kbyvhIi<_d!0csB@|g| zZiq$(gcniAi_VUmP9dA^rdphO8fDvbRMBG@$Y2Z4v&ON~eXP`?=aiAiQ~Z-bhw5-V z9Wz-HTtyN;rqf}91_swy(}z^H|Obk}Yy0w9!bIWD<$cOf7G7c{)ltKTal7&I*ELu#vSwy|%KI ze@T*lGI^CBnf_nPPHMS91V4TZ!p^QpoW=qU39afak`yvaP5aKBtaDxwopaYF-Vt*3 z${ML>kCYE_#akrh)r6hNKP2Uj)8aLflbhUz?Lt`W6(anD5pR+73ueY4+Zqyk*K5@1 zQA8ge5DXs!wA)CDURO2T^cu-r;dAY?HEHBcT#axjUdL1o<3X7#3=#B{kmuEt`!ClxT)MHnbjmW z3abu7IZFCjC$++Bx)xQsNctqt#fXw*mdUz)Ns}%bYf`MEVdWP3gVJ8s01aLv>66C1 zuyRv1UkX^c!*#EbOpr(pDErMU$yRVv$4XK!Nxww0U)gV_N}qItu1THFniM;puyTJn zRmxpNTO_0|l8Gjgs==X9xQkhwjqX{j2<1PNWCNj zl4IuwG5FC$FI_~xZ@>~DN$17(A-PzprFNxNJlnKjhSur$>C8OIWm3O+Rr$H*SiE1V>z9wQb3Bs8k-*Hb@JNH1H9} z_#W^8a){Gd8s%5Yc0Px%bJ+HTD{I=WlVX6^rxm|ZO|GKMFLTgzN?r8S&)0O4+#KTp zmFjfdV!MIG?2NO_W?s>IDAaGJLGiTW0MZSdilc;4n%G6T)7_5Tev+mU5p{;|S^QNu zQ)Mw(nK8%!U9=FQnbREQlJm=FnWS&Z!GQned5eYhA$CNi*vn}Sa*`JA&`JMH1-T^h zw<&$7ho!CYal8=av%F3fPtMegRj#?nlpNiw*{-Q0iKO( z>m|&a{LCia_?9FW;u@`V@;-<4xh1J#_7zU#^L&R*HgR*47$HI*=lDH)ZHQN*dpN;+)YC&NSMTA@;3t6*_tqHENCKo&L?*cu zv63=!No15B8u@^CIjM7VW+q8aB9VON6C^?#A$s`49Fu<^-oyU^1(@7cYcbcx0000b zbVXQnWMOn=I%9HWVRU5xGB7bUEif@HGcZ&#F*-9gIxsUUFflqXFqGH@MF0Q*C3Hnt zbYx+4WjbwdWNBu305UK!H7zhPEi*7wGBG+cHaajfD=;xSFfiqXz4-tD002ovPDHLk FV1l0ow}b!y literal 0 HcmV?d00001 diff --git a/public/apple-touch-icon-120x120.png b/public/apple-touch-icon-120x120.png new file mode 100644 index 0000000000000000000000000000000000000000..666bb9ea9906bf6e4dcf84536469b66125489f85 GIT binary patch literal 2711 zcmZ`*S3KJd8~qcTgxc%XQn7-D7^P~)h!KP+ifV~eL+lD&S|MsyZ9Cku) zX;F$$d(Pu&% zqXqjVJ8LBHmy*%jain-dZ9F0XfM4XlU;_%Nl1C0)sm%_Cc5l4DW(jB2@BG%NI^q2^d~(ZNIh$3jM)4(@2W` z!CHoe%`?+)7+o`Fwj=O;y;5!R`m>3DJlK6??j?54^*+wkZdiocl6mMnex%@hxr%w^ z*Nzgrsa3cnx%73ND#;(sVQ4!DVTVRm%Qc>u;m(c?E{(wSg)CuchaIian3?8BeO<__ zLLHE`oS)j3J~BlwTz`!w`GN&vJ(ikZ6xedsrb-UeChPsv(l!bcfjRr9h1X--Oi$)% zh}g@UEKFp3s{fWoX~>Ij5TuiY>Ap#l;Gj#7Q74NW1WE@xxNfaW+d-pVn^G7o4sLgN zb%aT#XqdHXs2;)KiOvqZ6Mw5)BiDYgz*XpdtH+0;f+bd=bake#g(?{-XHo1r=%G%Ee6x9+`DE%0;g`0qFA{- zDn4IM*;US~us|7ncDuo$=SpflO@ONIN(-(r%k=akEj}sufl1vGA zmHNd~vQb}b>53^504|G;SAxI7`hc5c#;4flZE{|y}3`G*#WV1aymcWZbkPYn$Q z`M2B(_xZe39+CqyuVCR8&mJs@bgt*m;NUZ})czj5lA zd@WC8V`?$ps$7i#^|tPpGHPiagS=5YptF4qUHXfguyuE$`1iAz=cru3%ly#-X!44& z#^9Vxp>AsP*W8SmX~;TX%HK%+EOlx5D=(N0ntXNSFj0(-*H1)-&ywIR&K_Bb;L;u&63BzR~uykAF)b8)JCWr!HYCG0C+BzEx?$HtWQ&rl}%V$*S~u&-Li zXKKJFZ!B8-Edl-_b%9RBl(eBbA{%-MItA+ff)?rTeN;_0rTa9+WCuA2pP1*g5omgo ztA_ekaZc+FaWwzl%Ai zNZYtDenM%L!K>pw@x}3v5~q(vsdCC?{oB>&EV?R?4qY?6zn$W4)l2Ag*=1Z+l(p}8Ygg4#s1zk zF=vMh026+;|JX}0PtG#zr-yI2N5Q@kq2=+$32ss`?;kKXLT=12a%l=}?~f%s`d3|d z7IN#bOQ&WJ(&m*E(EP--iK|BZu}7YmXQw&xrBgZfvAD+y4!Lz5xc?DfHKLul;uk4-c z@>A!>S$KnM!{qs8)kyXQenb$g9BOEp`(9}DPBalX$49e%q4-(qq<}~K%WuBzwB~nV z??D)f`Sm#Ns8#(okXHy_`$sELPe+yb>&5_tuZlImC#dFlItO7zch+f*gC$&Vh)*UB zVWyJfy?V^woula&IZdx*eJ8ik8;%)|chFi3_p9MxOtEU&o@|ZH^R-!1Z<>L+_7^m4 ze%Me=!=$3&&$l;hHNq2SN% zA|LP?S-S7GhPhQ@ubf#KuVOjzGWbCAB2hFWIVoI|@XCc&0+g$K4D zlhW;><1~-!lrj4)#J=65z>e|xUP@l)EH_PhBW=Ui<8PTtDus+4) z&+Q67%uRc3k*^XgJB5<37+0SQD*dueYNiSDxtC6BUyD24g&Ud=TVLn^Lqwj=T?$Ti`vcdA%0gssPpgpxsgyT8_KJ zy)}bgFp(3yuw3Lztf~ee)Gh732qJDbbwZRXd_V2fdo|m469!QKcPg0K& zQ-6$w_p+0-B`%6^9)!w z?bX_>b4l|*4xVp+kmEAE1vDd48_5PeG4GlpoC{>8+OES^C)#^s?>0L1o=5$yEV{9u zWT?CrV&mwB$QLKVe%m?tXPSEk)?*IZ6VVrw73DWr5t%)K1fYg;^H9eR1p`gH7Yf$%hPv5bOPnH9rn zf@tb)LJzat3SXF(x55;#iKL{b*E2?DU68kNEWOM4IXZ~f2t!{H#G|CY@deoqimhO@M z7b9_oe&M(y0<_^;x*BjT4Fud(TgwokX9!2Y;97=oc=lIL%l`=khxi8u#QgujTtfTK PQ2;<$qLDS`zIXlu`5WvS literal 0 HcmV?d00001 diff --git a/public/apple-touch-icon-144x144.png b/public/apple-touch-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..cb899b91b98c17555ead31bf066045a2ee95048b GIT binary patch literal 3316 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 JPDHLkV1fxvUSa?M literal 0 HcmV?d00001 diff --git a/public/apple-touch-icon-60x60.png b/public/apple-touch-icon-60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..4d45fcbed0b456933bf3662b78a29dc735a5ddeb GIT binary patch literal 1442 zcmV;T1zq}yP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 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 literal 0 HcmV?d00001 diff --git a/public/apple-touch-icon-76x76.png b/public/apple-touch-icon-76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..13639d6ee626420d364e37ef2a065d34c03cdb7b GIT binary patch literal 1786 zcmZ{lc|6p6AH~0uL}MS@B-trzzn{H%G+na0mSOD4jIj)cj8Sq4_u5Hh?AuL3_m(cB zXt6JmeY*{kWkMp$;1R8N?w`*e&mZS=UgvyX@4ru)gPnx{QVIzG5U{c|b>#TZ|BCl0 z=Z2IQ-Qn2L0ApKY0P3^&z7n}PS&n4sXbZqEDgeYM0`Qqr#jgTz6#`(5005>C0Lie@ z=QtzI)vk~P@INyo!1-Dv>uiT*1DC@w{FN}dQSTQi<{gp9Zn*Qms72LRj~ zR;I>y^4vyoFvZCa-5dAzdF=~$^TEP>ev6~}!p5WW!y1cRQmNT!#k92aVqsgzyp0!D zQ4vmHCoCq!!Z=iBzcSAB1lst0r}Cw4%4~1#$2qU5nI~S%cPTB==e9nOnY0-hQp|7) z_Qr5k8+Pm451E7c936|7aj>L;Q0Nv44DqV8vY1Y9{tu7XY>&@%jHz-tbtxjYSMhtd`K<+u%957JoHA^k-(%(WbOv4F z?I}^WA(i~9CLvm#4zX1b)-2;Cy`el(Y2U$>%qlufWS#Hf>g1yqH#|nMh_6@EH!Z&R z#k7{jDDI38c;=>(1wGLc>{u5+89pP1T;!RJ2l?@-Oq*G%rwg)Of_edj#Prl@VO<-a zD(+1q_6?tb(xn`fw*vN}uF?;Ob;2QP!BF0-IZ zmzzNFhIyuq1f*}z@J=CIRR6r*?$9AQAhD*H69w^PBpR5yNsi>{-HiS#kyZ3}d0h3w z&-6c7#qmP{s=Ld2%eiU(B2|&i(+!Lm+P1Llc4>^cPMgS)o%;l=)AChfsJ2pjBl}4Y zUP)KTGtkbk29lYtqHS_(>5pH}>T&y8DH4h4(C@=R)9>zmG({CY>z93#uXvqJx6z8q zMf+Dt5p|{csYOEUDp87oJKm|AG$?X=WU52XE}XhZR99EVpN}Rhq|;*`jS|;BQQmYvABp34c$-V;8PUQ4X`I=-Zk+UxrJhGfD$>0KU++YXgyn`P0CXek6RI$ zgiYAVR`e&gNb7$g2NT4_z-ZM?x`PP}Y;|Y2^zCD=-Bo3WI zn}X^D!F;~6ea_MC=DpK*#`lp+2h*fRW)o81zjrKE)l9}ahslD=!)olYF*wK^ZOJ`_f|t)FxJnM?ci~!}grFM;cGGRl4CnpS zsQV(9f6cAXMy^O?+(g)^vdtJ)hG!bMDCJ;vF(2hEr+&d753ds{JvC#yM&>nJ&x925 z4BTeXXV%}t7eC9ZD^|JJrQvz)^I$KGp8|9lmdhhdByA#V@-Dai)HF6~Z(rRZ{ZO1X zi4{}}Kwf=eael?s(m$lkK|?ndTE*C`-u`XEct<WhUmX!hd)GF-G}w!*FV9O)X)0_nTqf`{3{( zuDaY7s@_K^x;uCLp7R*2_3-4w{7=4YBjy&>D-q#wkW`Q}f9=?f2O2WW2PcM_UybZ| zYX08e5jdIrv~%myog@j&wnxcVU}O^6NzqeHD(opp-o5d)J7dtYhc7u&=Nf$%-}}t5 z^4F$w74ex7HC=mDK3U1BA?o@GDfnAlN$&tfczoO|W88%Atu)bf(3*+}PF+}5Fkp+? zE!$)VjW@XU(VrGwR1TG%t+@S7y1Fb|yX94Urq@YEk-4hiFJyzJ3twa#;{e_D%YOvK z@;o?Opd4Z57U4^b@WYV8{Wu1+Ay`inf;Dv@yf%!{(Z@hK>JW^9pxh~Lv;P4?uJ{J} XNBw_bIjQB40{|;CJJUxdgx~%Fu5=qh literal 0 HcmV?d00001 diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..699993e810c4d8a4ce570f900105aefc7200840d GIT binary patch literal 4130 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 + + + + + + + + #2b5797 + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..08abefd08664e509d04b7d1d54106b6f422db6e6 GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L90|U1(2s1Lwnj--eWH0gbb!ETLC?~4Id_nBaL7>nJPZ!4!jq|1F4{A3v${hQ6 zeoykuGp@qiGc9&%SYGyPvB)?*_kYW3X?6AbL;sl7pQsc`s=8Fp)Chc5I^kR1+4nzc z_S^2C=kR)V(3G;ryCs+CXw58E3^H`u!3_v~wm|Bs)7!7u{<&w|kbf+u7Zh)U~hNtGctyl=)QASB-lD5+YI`m)?rHOUjhr;Dwz zDo(gvBCEbcMD&iPwaLocnKdf*oRdP`(kYX@0Ff`RQ zFw`|R2r)9WGB&d^FxECOv@$T5a)Z?sMMG|WN@iLmZVgXc_x=QGVDNPHb6Mw<&;$VU C0kWw8 literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..fddcf169efd91c68f892175810ae0250d6ebdb80 GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SSr1Gg{;GcwGYBLNg-FY)wsWxvlTC#u4HLF~>!pe7|x7sn8f<5RDAdrS$GIQH>= zdX`^bhuAZVn`iTcBE?)qL|k;ET%*fxsx^tZc28@3-L%M6dSPgZVw9`n>IF((9*ZW) zyzq*fB(r`=X7MeaCx>P}s}@TyteF45@_aR)zsfwNsXui8-0oQX)XzfY(>-0={|f(u zr$%{Sc(KmDN#VujeI~_A`TD=S*yL7zPliSK+cY+d+$e!FugtYy&+`7&yL;_Ik?QU< zO~L7N0@XI1lRFu7p5JKQj#upu7S4)L4u81M(>>tn#s?`j)@SW1)jGHI1l|_c*3xFW zvB2|~NYoNu>8Ld;_HZO@_Y!?o7~qsLwMFpq(yjB04wfIBVt#L?z!_l-B+bke_gZS<7_B#7=IF>ltUv zRhFiR|37w=L3_)q&w`$&MMZ^-v)6vRq_QnDXsw=J!gQ_pgKDC6zeV{2(%K&wXB^q_ zv7+H{U)5{ZI;oynzvLI(npShpjc>=AbEV848ON49x|qDWx@~XKCA(NQR`aV14n{^E zoS~i<5qcu-t)tzJ=RQA`D^{@{-(#eIV&=!ovMZe$rv4IJQvbn4yG%;Nr~2I<=O}Jf z$CjeGR;H_`UR@o&{d~mg!xMVS-Ma7EomyXdX%^25bM0yWm>68weQk5_Jr7JkswJ)w zB`Jv|saDBFsX&Us$iUE4*T7KM*dWBn(8}1%%D`CLz|hLTV9E_vR}>Ao`6-!cmAExL TZQc75sDZ)L)z4*}Q$iB}Hqb{+ literal 0 HcmV?d00001 diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..6268a69d57fa706d921c5040a4b1a61a6421f878 GIT binary patch literal 2209 zcmV;S2wwMzP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 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 literal 0 HcmV?d00001 diff --git a/public/mstile-310x150.png b/public/mstile-310x150.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7fa3a4853e765d02ef5069b4c8114cc14b8a4c GIT binary patch literal 3674 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 literal 0 HcmV?d00001 diff --git a/public/mstile-310x310.png b/public/mstile-310x310.png new file mode 100644 index 0000000000000000000000000000000000000000..42bafce4b633a8e092e048c99bbf2ea593dd8242 GIT binary patch literal 7457 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 W|8DS+mQeb)%LclpI`y|*68;N#xyM5Q literal 0 HcmV?d00001 diff --git a/public/mstile-70x70.png b/public/mstile-70x70.png new file mode 100644 index 0000000000000000000000000000000000000000..d0766d378b5258c5090447fa6c0437df9e0c5b7d GIT binary patch literal 2393 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 literal 0 HcmV?d00001 From 9da69358e24496705abec68efd6078301d339540 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 30 Jan 2015 22:09:47 +0100 Subject: [PATCH 62/71] Included code for code climate. --- .travis.yml | 6 ++++++ README.md | 2 ++ build/logs/.gitignore | 1 + composer.json | 5 +++-- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 build/logs/.gitignore diff --git a/.travis.yml b/.travis.yml index 3da09d1ad1..9620d44551 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ php: - 5.5 - 5.6 +addons: + code_climate: + repo_token: 26489f9e854fcdf7e7660ba29c1455694685465b1f90329a79f7d2bf448acb61 + install: - rm composer.lock - composer install @@ -14,4 +18,6 @@ script: - php vendor/bin/codecept run --coverage --coverage-xml after_script: + - cp tests/output/coverage.xml build/logs/clover.xml - php vendor/bin/coveralls + - vendor/bin/test-reporter \ No newline at end of file diff --git a/README.md b/README.md index 2a83d210a2..cda3921a13 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Firefly III [![Project Status](http://stillmaintained.com/JC5/firefly-iii.png?a=b)](http://stillmaintained.com/JC5/firefly-iii) [![Coverage Status](https://coveralls.io/repos/JC5/firefly-iii/badge.png?branch=master)](https://coveralls.io/r/JC5/firefly-iii?branch=master) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/d44c7012-5f50-41ad-add8-8445330e4102/mini.png)](https://insight.sensiolabs.com/projects/d44c7012-5f50-41ad-add8-8445330e4102) +[![Code Climate](https://codeclimate.com/github/JC5/firefly-iii/badges/gpa.svg)](https://codeclimate.com/github/JC5/firefly-iii) +[![Test Coverage](https://codeclimate.com/github/JC5/firefly-iii/badges/coverage.svg)](https://codeclimate.com/github/JC5/firefly-iii) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable.svg)](https://packagist.org/packages/grumpydictator/firefly-iii) [![Total Downloads](https://poser.pugx.org/grumpydictator/firefly-iii/downloads.svg)](https://packagist.org/packages/grumpydictator/firefly-iii) diff --git a/build/logs/.gitignore b/build/logs/.gitignore new file mode 100644 index 0000000000..b81c7954b7 --- /dev/null +++ b/build/logs/.gitignore @@ -0,0 +1 @@ +*.xml \ No newline at end of file diff --git a/composer.json b/composer.json index cac222a065..f06c862101 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require-dev": { "barryvdh/laravel-debugbar": "@stable", "barryvdh/laravel-ide-helper": "@stable", - "satooshi/php-coveralls": "dev-master", + "satooshi/php-coveralls": "*", "mockery/mockery": "@stable", "league/factory-muffin": "~2.1", "codeception/codeception": "*", @@ -41,7 +41,8 @@ "codeception/phpbuiltinserver": "*", "codeception/specify": "*", "codeception/verify": "*", - "fzaninotto/faker": "1.*" + "fzaninotto/faker": "1.*", + "codeclimate/php-test-reporter": "dev-master" }, From fc0e76f43185bd76fc3274100cff8d0715ff3234 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 30 Jan 2015 22:24:02 +0100 Subject: [PATCH 63/71] Some new seeds and the ability to search encrypted transaction journals. --- app/database/seeds/DatabaseSeeder.php | 2 +- app/database/seeds/TestDataSeeder.php | 78 +++++++++++++++---- app/lib/FireflyIII/Helper/Related/Related.php | 38 +++++++-- 3 files changed, 92 insertions(+), 26 deletions(-) diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php index f357dfd6ec..9912f3c281 100644 --- a/app/database/seeds/DatabaseSeeder.php +++ b/app/database/seeds/DatabaseSeeder.php @@ -19,7 +19,7 @@ class DatabaseSeeder extends Seeder $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); - if (App::environment() == 'testing') { + if (App::environment() == 'testing' || App::environment() == 'homestead') { $this->call('TestDataSeeder'); } } diff --git a/app/database/seeds/TestDataSeeder.php b/app/database/seeds/TestDataSeeder.php index fa6cb958d0..6ef7c64842 100644 --- a/app/database/seeds/TestDataSeeder.php +++ b/app/database/seeds/TestDataSeeder.php @@ -6,7 +6,6 @@ use Carbon\Carbon; * @SuppressWarnings("TooManyMethods") // I'm fine with this * @SuppressWarnings("CouplingBetweenObjects") // I'm fine with this * @SuppressWarnings("MethodLength") // I'm fine with this - * * Class TestDataSeeder */ @@ -79,64 +78,99 @@ class TestDataSeeder extends Seeder $user = User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => 'james', 'reset' => null, 'remember_token' => null]); - + Log::debug('Created users.'); // create initial accounts and various other stuff: $this->createAssetAccounts($user); + Log::debug('Created asset accounts.'); $this->createBudgets($user); + Log::debug('Created budgets.'); $this->createCategories($user); + Log::debug('Created categories.'); $this->createPiggyBanks($user); + Log::debug('Created piggy banks.'); $this->createReminders($user); + Log::debug('Created reminders.'); $this->createRecurringTransactions($user); + Log::debug('Created recurring transactions.'); $this->createBills($user); + Log::debug('Created bills.'); $this->createExpenseAccounts($user); + Log::debug('Created expense accounts.'); $this->createRevenueAccounts($user); + Log::debug('Created revenue accounts.'); // get some objects from the database: - $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); - $landLord = Account::whereName('Land lord')->orderBy('id', 'DESC')->first(); - $utilities = Account::whereName('Utilities company')->orderBy('id', 'DESC')->first(); + $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); + Log::debug('Found checking: ' . json_encode($checking)); + $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + Log::debug('Found savings: ' . json_encode($savings)); + $landLord = Account::whereName('Land lord')->orderBy('id', 'DESC')->first(); + Log::debug('Found landlord: ' . json_encode($landLord)); + $utilities = Account::whereName('Utilities company')->orderBy('id', 'DESC')->first(); + Log::debug('Found utilities: ' . json_encode($utilities)); $television = Account::whereName('TV company')->orderBy('id', 'DESC')->first(); - $phone = Account::whereName('Phone agency')->orderBy('id', 'DESC')->first(); - $employer = Account::whereName('Employer')->orderBy('id', 'DESC')->first(); + Log::debug('Found tv company: ' . json_encode($television)); + $phone = Account::whereName('Phone agency')->orderBy('id', 'DESC')->first(); + Log::debug('Found phone company: ' . json_encode($phone)); + $employer = Account::whereName('Employer')->orderBy('id', 'DESC')->first(); + Log::debug('Found employer: ' . json_encode($employer)); - $bills = Budget::whereName('Bills')->orderBy('id', 'DESC')->first(); + $bills = Budget::whereName('Bills')->orderBy('id', 'DESC')->first(); + Log::debug('Found bills budget: ' . json_encode($bills)); $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); + Log::debug('Found groceries budget: ' . json_encode($groceries)); $house = Category::whereName('House')->orderBy('id', 'DESC')->first(); + Log::debug('Found house category: ' . json_encode($checking)); $withdrawal = TransactionType::whereType('Withdrawal')->first(); - $deposit = TransactionType::whereType('Deposit')->first(); - $transfer = TransactionType::whereType('Transfer')->first(); + Log::debug('Found withdrawal: ' . json_encode($withdrawal)); + $deposit = TransactionType::whereType('Deposit')->first(); + Log::debug('Found deposit: ' . json_encode($deposit)); + $transfer = TransactionType::whereType('Transfer')->first(); + Log::debug('Found transfer: ' . json_encode($transfer)); $euro = TransactionCurrency::whereCode('EUR')->first(); + Log::debug('Found euro: ' . json_encode($euro)); $rentBill = Bill::where('name', 'Rent')->first(); + Log::debug('Found bill "rent": ' . json_encode($rentBill)); $current = clone $this->_yearAgoStartOfMonth; while ($current <= $this->_startOfMonth) { $cur = $current->format('Y-m-d'); $formatted = $current->format('F Y'); + Log::debug('Now at: ' . $cur); // create expenses for rent, utilities, TV, phone on the 1st of the month. $this->createTransaction($checking, $landLord, 800, $withdrawal, 'Rent for ' . $formatted, $cur, $euro, $bills, $house, $rentBill); + Log::debug('Created rent.'); $this->createTransaction($checking, $utilities, 150, $withdrawal, 'Utilities for ' . $formatted, $cur, $euro, $bills, $house); + Log::debug('Created utilities.'); $this->createTransaction($checking, $television, 50, $withdrawal, 'TV for ' . $formatted, $cur, $euro, $bills, $house); + Log::debug('Created TV.'); $this->createTransaction($checking, $phone, 50, $withdrawal, 'Phone bill for ' . $formatted, $cur, $euro, $bills, $house); + Log::debug('Created phone bill.'); // two transactions. One without a budget, one without a category. $this->createTransaction($checking, $phone, 10, $withdrawal, 'Extra charges on phone bill for ' . $formatted, $cur, $euro, null, $house); + Log::debug('Created extra charges no budget.'); $this->createTransaction($checking, $television, 5, $withdrawal, 'Extra charges on TV bill for ' . $formatted, $cur, $euro, $bills, null); + Log::debug('Created extra charges no category.'); // income from job: $this->createTransaction($employer, $checking, rand(3500, 4000), $deposit, 'Salary for ' . $formatted, $cur, $euro); + Log::debug('Created income.'); $this->createTransaction($checking, $savings, 2000, $transfer, 'Salary to savings account in ' . $formatted, $cur, $euro); + Log::debug('Created savings.'); $this->createGroceries($current); + Log::debug('Created groceries range.'); $this->createBigExpense(clone $current); + Log::debug('Created big expense.'); echo 'Created test-content for ' . $current->format('F Y') . "\n"; $current->addMonth(); @@ -208,15 +242,24 @@ class TestDataSeeder extends Seeder $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); $billID = is_null($bill) ? null : $bill->id; - + Log::debug('String length of encrypted description ("'.$description.'") is: ' . strlen(Crypt::encrypt($description))); /** @var TransactionJournal $journal */ $journal = TransactionJournal::create( [ - 'user_id' => $user->id, 'transaction_type_id' => $type->id, 'transaction_currency_id' => $currency->id, 'bill_id' => $billID, - 'description' => $description, 'completed' => 1, 'date' => $date + 'user_id' => $user->id, + 'transaction_type_id' => $type->id, + 'transaction_currency_id' => $currency->id, + 'bill_id' => $billID, + 'description' => $description, + 'completed' => 1, + 'date' => $date ] ); + //Log::debug('Journal valid: ' . Steam::boolString($journal->isValid())); + //Log::debug('Journal errors: ' . json_encode($journal->getErrors())); + //Log::debug('Journal created: ' . json_encode($journal)); + Transaction::create(['account_id' => $from->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount * -1]); Transaction::create(['account_id' => $to->id, 'transaction_journal_id' => $journal->id, 'amount' => $amount]); @@ -427,10 +470,10 @@ class TestDataSeeder extends Seeder 'user_id' => $user->id, 'name' => 'Gas licht', 'match' => 'no,match', - 'amount_min' => 500, 'amount_max' => 700, + 'amount_min' => 500, 'amount_max' => 700, 'date' => $this->som, - 'active' => 1, 'automatch' => 1, - 'repeat_freq' => 'monthly', 'skip' => 0, + 'active' => 1, 'automatch' => 1, + 'repeat_freq' => 'monthly', 'skip' => 0, ] ); @@ -552,6 +595,7 @@ class TestDataSeeder extends Seeder ); $group->transactionjournals()->save($one); $group->transactionjournals()->save($two); + $group->save(); } diff --git a/app/lib/FireflyIII/Helper/Related/Related.php b/app/lib/FireflyIII/Helper/Related/Related.php index 0627374e74..1732f638ad 100644 --- a/app/lib/FireflyIII/Helper/Related/Related.php +++ b/app/lib/FireflyIII/Helper/Related/Related.php @@ -56,14 +56,36 @@ class Related implements RelatedInterface } $exclude = array_unique($exclude); - $query = $this->getUser()->transactionjournals() - ->withRelevantData() - ->before($end) - ->after($start) - ->whereNotIn('id', $exclude) - ->where('description', 'LIKE', '%' . $query . '%') - ->get(); + /** @var Collection $collection */ + $collection = $this->getUser()->transactionjournals() + ->withRelevantData() + ->before($end) + ->where('encrypted', 0) + ->after($start) + ->whereNotIn('id', $exclude) + ->where('description', 'LIKE', '%' . $query . '%') + ->get(); - return $query; + // manually search encrypted entries: + /** @var Collection $encryptedCollection */ + $encryptedCollection = $this->getUser()->transactionjournals() + ->withRelevantData() + ->before($end) + ->where('encrypted', 1) + ->after($start) + ->whereNotIn('id', $exclude) + ->get(); + $encrypted = $encryptedCollection->filter( + function (\TransactionJournal $journal) use ($query) { + $strPos = strpos($journal->description, $query); + if ($strPos !== false) { + return $journal; + } + } + ); + $collected = $collection->merge($encrypted); + + + return $collected; } } From 512b81ad93364b85e2f499bd27ce0e0a52308e43 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 30 Jan 2015 22:32:12 +0100 Subject: [PATCH 64/71] Fixed some tests. --- app/controllers/HomeController.php | 13 ++------ .../TransactionJournal/TransactionJournal.php | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php index f0d6c35651..e97bf24bdc 100644 --- a/app/controllers/HomeController.php +++ b/app/controllers/HomeController.php @@ -90,12 +90,8 @@ class HomeController extends BaseController public function sessionNext() { Navigation::next(); - $strPosition = strpos($_SERVER['HTTP_REFERER'], Config::get('app.url')); - Log::debug('HTTP_REFERER is: ' . $_SERVER['HTTP_REFERER']); - Log::debug('App.url = ' . Config::get('app.url')); - Log::debug('strpos() = ' . Steam::boolString($strPosition)); - if (isset($_SERVER['HTTP_REFERER']) && $strPosition === 0) { + if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], Config::get('app.url')) === 0) { Log::debug('Redirect back'); return Redirect::back(); } else { @@ -111,13 +107,8 @@ class HomeController extends BaseController public function sessionPrev() { Navigation::prev(); - $strPosition = strpos($_SERVER['HTTP_REFERER'], Config::get('app.url')); - Log::debug('HTTP_REFERER is: ' . $_SERVER['HTTP_REFERER']); - Log::debug('App.url = ' . Config::get('app.url')); - Log::debug('strpos() = ' . Steam::boolString($strPosition)); - - if (isset($_SERVER['HTTP_REFERER']) && $strPosition === 0) { + if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], Config::get('app.url')) === 0) { Log::debug('Redirect back'); return Redirect::back(); } else { diff --git a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php index 3438bbc37d..b7feb1f15b 100644 --- a/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php +++ b/app/lib/FireflyIII/Database/TransactionJournal/TransactionJournal.php @@ -68,7 +68,8 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C 'transaction_type_id' => $data['transaction_type_id'], 'transaction_currency_id' => $data['transaction_currency_id'], 'user_id' => $this->getUser()->id, - 'description' => $data['description'], 'date' => $data['date'], 'completed' => 0] + 'description' => $data['description'], + 'date' => $data['date'], 'completed' => 0] ); $journal->save(); @@ -161,6 +162,9 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C if (!isset($model['what'])) { $errors->add('description', 'Internal error: need to know type of transaction!'); } + if (strlen($model['description']) == 0) { + $errors->add('description', 'The description field is required.'); + } $errors = $errors->merge($this->_validateAmount($model)); $errors = $errors->merge($this->_validateBudget($model)); $errors = $errors->merge($this->_validateAccount($model)); @@ -291,19 +295,6 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C return $typeRepository->findByWhat($type); } - /** - * @param $currency - * - * @return null|\TransactionCurrency - */ - public function getJournalCurrency($currency) - { - /** @var \FireflyIII\Database\TransactionCurrency\TransactionCurrency $currencyRepository */ - $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency\TransactionCurrency'); - - return $currencyRepository->findByCode($currency); - } - /** * @param int $currencyId * @@ -563,6 +554,19 @@ class TransactionJournal implements TransactionJournalInterface, CUDInterface, C return $query; } + /** + * @param $currency + * + * @return null|\TransactionCurrency + */ + public function getJournalCurrency($currency) + { + /** @var \FireflyIII\Database\TransactionCurrency\TransactionCurrency $currencyRepository */ + $currencyRepository = \App::make('FireflyIII\Database\TransactionCurrency\TransactionCurrency'); + + return $currencyRepository->findByCode($currency); + } + /** * @param int $limit * From 94e2f9b6dce23ab8cf34e5f429ba07f5c5ae454a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 30 Jan 2015 22:43:52 +0100 Subject: [PATCH 65/71] Updated tests to understand encrypted database content. --- .../functional/TransactionControllerCest.php | 168 +++++++++++------- 1 file changed, 108 insertions(+), 60 deletions(-) diff --git a/tests/functional/TransactionControllerCest.php b/tests/functional/TransactionControllerCest.php index 6bd5444ad7..f9e40d8062 100644 --- a/tests/functional/TransactionControllerCest.php +++ b/tests/functional/TransactionControllerCest.php @@ -29,7 +29,11 @@ class TransactionControllerCest public function deleteWithdrawal(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Rent for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Withdrawal')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('delete a transaction'); $I->amOnPage('/transaction/delete/' . $journal->id); $I->see('Delete withdrawal "' . $journal->description . '"'); @@ -37,7 +41,11 @@ class TransactionControllerCest public function destroyDeposit(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Salary for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Deposit')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('destroy a deposit'); $I->amOnPage('/transaction/delete/' . $journal->id); $I->submitForm('#destroy', []); @@ -47,9 +55,13 @@ class TransactionControllerCest public function destroyTransfer(FunctionalTester $I) { - $I->wantTo('destroy a transfer'); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Transfer')->first(); - $journal = TransactionJournal::where('description', 'LIKE', '%Money for big expense in%')->first(); + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); + + $I->wantTo('destroy a transfer'); $I->amOnPage('/transaction/delete/' . $journal->id); $I->submitForm('#destroy', []); @@ -59,7 +71,12 @@ class TransactionControllerCest public function destroyWithdrawal(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Rent for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Withdrawal')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); + $I->wantTo('destroy a withdrawal'); $I->amOnPage('/transaction/delete/' . $journal->id); $I->submitForm('#destroy', []); @@ -69,10 +86,15 @@ class TransactionControllerCest public function edit(FunctionalTester $I) { - $journal = TransactionJournal::whereDescription('Money for piggy')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Transfer')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); + $I->wantTo('edit a transaction'); $I->amOnPage('/transaction/edit/' . $journal->id); - $I->see('Edit transfer "Money for piggy"'); + $I->see('Edit transfer "' . $journal->description . '"'); } public function index(FunctionalTester $I) @@ -98,7 +120,11 @@ class TransactionControllerCest public function show(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Rent for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Withdrawal')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('see a transaction'); $I->amOnPage('/transaction/show/' . $journal->id); @@ -106,15 +132,22 @@ class TransactionControllerCest $I->see(intval($journal->getAmount())); } + /** + * @param FunctionalTester $I + */ public function showGroupedJournal(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', 'Big expense in %')->first(); + $groupRow = DB::table('transaction_group_transaction_journal')->select('transaction_journal_id')->first(['transaction_journal_id']); + + $id = $groupRow->transaction_journal_id; + + // get a grouped journal: + $journal = TransactionJournal::find($id); $I->wantTo('see a grouped transaction'); $I->amOnPage('/transaction/show/' . $journal->id); $I->see($journal->description); - $I->see('Money for ' . $journal->description); } public function store(FunctionalTester $I) @@ -137,29 +170,6 @@ class TransactionControllerCest $I->see('Transaction "Test" stored.'); } - - public function storeValidate(FunctionalTester $I) - { - $I->wantTo('validate a transaction'); - $I->amOnPage('/transactions/create/withdrawal'); - $I->submitForm( - '#store', [ - 'reminder' => '', - 'description' => 'TestValidateMe', - 'account_id' => 1, - 'expense_account' => 'Zomaar', - 'amount' => 100, - 'date' => '2014-12-30', - 'budget_id' => 3, - 'category' => 'CategorrXXXXr', - 'post_submit_action' => 'validate_only' - ] - ); - $I->see('OK'); - $I->seeInSession('successes'); - $I->dontSeeRecord('transaction_journals', ['description' => 'TestValidateMe']); - } - public function storeAndFail(FunctionalTester $I) { $I->wantTo('store a transaction and fail'); @@ -200,9 +210,35 @@ class TransactionControllerCest $I->see('Transaction "Test" stored.'); } + public function storeValidate(FunctionalTester $I) + { + $I->wantTo('validate a transaction'); + $I->amOnPage('/transactions/create/withdrawal'); + $I->submitForm( + '#store', [ + 'reminder' => '', + 'description' => 'TestValidateMe', + 'account_id' => 1, + 'expense_account' => 'Zomaar', + 'amount' => 100, + 'date' => '2014-12-30', + 'budget_id' => 3, + 'category' => 'CategorrXXXXr', + 'post_submit_action' => 'validate_only' + ] + ); + $I->see('OK'); + $I->seeInSession('successes'); + $I->dontSeeRecord('transaction_journals', ['description' => 'TestValidateMe']); + } + public function update(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Salary for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Deposit')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('update a transaction'); $I->amOnPage('/transaction/edit/' . $journal->id); @@ -222,33 +258,13 @@ class TransactionControllerCest $I->see($journal->description . '!'); } - public function updateValidate(FunctionalTester $I) - { - $journal = TransactionJournal::where('description', 'LIKE', '%Salary for %')->first(); - - $I->wantTo('validate an updated transaction'); - $I->amOnPage('/transaction/edit/' . $journal->id); - $I->see($journal->description); - $I->submitForm( - '#update', [ - 'description' => $journal->description . 'XYZ', - 'account_id' => 1, - 'expense_account' => 'Portaal', - 'amount' => 500, - 'date' => $journal->date->format('Y-m-d'), - 'budget_id' => is_null($journal->budgets()->first()) ? 0 : $journal->budgets()->first()->id, - 'category' => is_null($journal->categories()->first()) ? '' : $journal->categories()->first()->id, - 'post_submit_action' => 'validate_only' - ] - ); - $I->see($journal->description . 'XYZ'); - $I->see('OK'); - $I->seeInSession('successes'); - } - public function updateAndFail(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Salary for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Deposit')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('update a transaction and fail'); $I->amOnPage('/transaction/edit/' . $journal->id); @@ -270,7 +286,11 @@ class TransactionControllerCest public function updateAndReturn(FunctionalTester $I) { - $journal = TransactionJournal::where('description', 'LIKE', '%Salary for %')->first(); + // get withdrawal transaction type id: + $type = TransactionType::whereType('Deposit')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); $I->wantTo('update a transaction and return to the edit screen'); $I->amOnPage('/transaction/edit/' . $journal->id); $I->see($journal->description); @@ -289,5 +309,33 @@ class TransactionControllerCest $I->see($journal->description . '!'); } + public function updateValidate(FunctionalTester $I) + { + // get withdrawal transaction type id: + $type = TransactionType::whereType('Deposit')->first(); + + // get a journal + $journal = TransactionJournal::where('transaction_type_id', $type->id)->first(); + + $I->wantTo('validate an updated transaction'); + $I->amOnPage('/transaction/edit/' . $journal->id); + $I->see($journal->description); + $I->submitForm( + '#update', [ + 'description' => $journal->description . 'XYZ', + 'account_id' => 1, + 'expense_account' => 'Portaal', + 'amount' => 500, + 'date' => $journal->date->format('Y-m-d'), + 'budget_id' => is_null($journal->budgets()->first()) ? 0 : $journal->budgets()->first()->id, + 'category' => is_null($journal->categories()->first()) ? '' : $journal->categories()->first()->id, + 'post_submit_action' => 'validate_only' + ] + ); + $I->see($journal->description . 'XYZ'); + $I->see('OK'); + $I->seeInSession('successes'); + } + } From e6cfe040b51adee1289da56505c03dd81ca476ac Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:11:55 +0100 Subject: [PATCH 66/71] Clean up some javascript, small fix for the test reporter --- .travis.yml | 2 +- public/assets/javascript/firefly/gcharts.js | 225 ++++---------------- 2 files changed, 46 insertions(+), 181 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9620d44551..5eb9cd293a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,6 @@ script: - php vendor/bin/codecept run --coverage --coverage-xml after_script: - - cp tests/output/coverage.xml build/logs/clover.xml + - cp -v tests/_output/coverage.xml build/logs/clover.xml - php vendor/bin/coveralls - vendor/bin/test-reporter \ No newline at end of file diff --git a/public/assets/javascript/firefly/gcharts.js b/public/assets/javascript/firefly/gcharts.js index 4bf7cdf1a8..92d1067fa4 100644 --- a/public/assets/javascript/firefly/gcharts.js +++ b/public/assets/javascript/firefly/gcharts.js @@ -1,7 +1,8 @@ +var google = google || {}; google.load('visualization', '1.1', {'packages': ['corechart', 'bar', 'sankey', 'table']}); -function googleLineChart(URL, container, options) { - if ($('#' + container).length == 1) { +function googleChart(chartType, URL, container, options) { + if ($('#' + container).length === 1) { $.getJSON(URL).success(function (data) { /* Get the data from the JSON @@ -16,19 +17,45 @@ function googleLineChart(URL, container, options) { groupingSymbol: '.', prefix: currencyCode + ' ' }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { + for (var i = 1; i < gdata.getNumberOfColumns(); i++) { money.format(gdata, i); } /* Create a new google charts object. */ - var chart = new google.visualization.LineChart(document.getElementById(container)); + var chart = false + var options = false; + if (chartType === 'line') { + chart = new google.visualization.LineChart(document.getElementById(container)); + options = options || defaultLineChartOptions; + } + if (chartType === 'column') { + chart = new google.charts.Bar(document.getElementById(container)); + options = options || defaultColumnChartOptions; + } + if (chartType === 'pie') { + chart = new google.visualization.PieChart(document.getElementById(container)); + options = options || defaultPieChartOptions; + } + if (chartType === 'bar') { + chart = new google.charts.Bar(document.getElementById(container)); + options = options || defaultBarChartOptions; + } + if (chartType === 'stackedColumn') { + chart = new google.visualization.ColumnChart(document.getElementById(container)); + options = options || defaultStackedColumnChartOptions; + } + if (chartType === 'combo') { + chart = new google.visualization.ComboChart(document.getElementById(container)); + options = options || defaultComboChartOptions; + } - /* - Draw it: - */ - chart.draw(gdata, options || defaultLineChartOptions); + if (chart === false) { + alert('Cannot draw chart of type "' + chartType + '".'); + } else { + chart.draw(gdata, options); + } }).fail(function () { $('#' + container).addClass('google-chart-error'); @@ -38,189 +65,27 @@ function googleLineChart(URL, container, options) { } } + +function googleLineChart(URL, container, options) { + return googleChart('line', URL, container, options); +} + function googleBarChart(URL, container, options) { - if ($('#' + container).length == 1) { - $.getJSON(URL).success(function (data) { - /* - Get the data from the JSON - */ - gdata = new google.visualization.DataTable(data); - - /* - Format as money - */ - var money = new google.visualization.NumberFormat({ - decimalSymbol: ',', - groupingSymbol: '.', - prefix: currencyCode + ' ' - }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { - money.format(gdata, i); - } - - /* - Create a new google charts object. - */ - var chart = new google.charts.Bar(document.getElementById(container)); - - /* - Draw it: - */ - chart.draw(gdata, options || defaultBarChartOptions); - - }).fail(function () { - $('#' + container).addClass('google-chart-error'); - }); - } else { - console.log('No container found called "' + container + '"'); - } + return googleChart('bar', URL, container, options); } function googleColumnChart(URL, container, options) { - if ($('#' + container).length == 1) { - $.getJSON(URL).success(function (data) { - /* - Get the data from the JSON - */ - gdata = new google.visualization.DataTable(data); - - /* - Format as money - */ - var money = new google.visualization.NumberFormat({ - decimalSymbol: ',', - groupingSymbol: '.', - prefix: currencyCode + ' ' - }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { - money.format(gdata, i); - } - - /* - Create a new google charts object. - */ - var chart = new google.charts.Bar(document.getElementById(container)); - /* - Draw it: - */ - chart.draw(gdata, options || defaultColumnChartOptions); - - }).fail(function () { - $('#' + container).addClass('google-chart-error'); - }); - } else { - console.log('No container found called "' + container + '"'); - } + return googleChart('column', URL, container, options); } function googleStackedColumnChart(URL, container, options) { - if ($('#' + container).length == 1) { - $.getJSON(URL).success(function (data) { - /* - Get the data from the JSON - */ - gdata = new google.visualization.DataTable(data); - - /* - Format as money - */ - var money = new google.visualization.NumberFormat({ - decimalSymbol: ',', - groupingSymbol: '.', - prefix: currencyCode + ' ' - }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { - money.format(gdata, i); - } - - /* - Create a new google charts object. - */ - var chart = new google.visualization.ColumnChart(document.getElementById(container)); - /* - Draw it: - */ - chart.draw(gdata, options || defaultStackedColumnChartOptions); - - }).fail(function () { - $('#' + container).addClass('google-chart-error'); - }); - } else { - console.log('No container found called "' + container + '"'); - } + return googleChart('stackedColumn', URL, container, options); } function googleComboChart(URL, container, options) { - if ($('#' + container).length == 1) { - $.getJSON(URL).success(function (data) { - /* - Get the data from the JSON - */ - gdata = new google.visualization.DataTable(data); - - /* - Format as money - */ - var money = new google.visualization.NumberFormat({ - decimalSymbol: ',', - groupingSymbol: '.', - prefix: currencyCode + ' ' - }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { - money.format(gdata, i); - } - - /* - Create a new google charts object. - */ - var chart = new google.visualization.ComboChart(document.getElementById(container)); - /* - Draw it: - */ - chart.draw(gdata, options || defaultComboChartOptions); - - }).fail(function () { - $('#' + container).addClass('google-chart-error'); - }); - } else { - console.log('No container found called "' + container + '"'); - } + return googleChart('combo', URL, container, options); } function googlePieChart(URL, container, options) { - if ($('#' + container).length == 1) { - $.getJSON(URL).success(function (data) { - /* - Get the data from the JSON - */ - gdata = new google.visualization.DataTable(data); - - /* - Format as money - */ - var money = new google.visualization.NumberFormat({ - decimalSymbol: ',', - groupingSymbol: '.', - prefix: currencyCode + ' ' - }); - for (i = 1; i < gdata.getNumberOfColumns(); i++) { - money.format(gdata, i); - } - - /* - Create a new google charts object. - */ - var chart = new google.visualization.PieChart(document.getElementById(container)); - - /* - Draw it: - */ - chart.draw(gdata, options || defaultPieChartOptions); - - }).fail(function () { - $('#' + container).addClass('google-chart-error'); - }); - } else { - console.log('No container found called "' + container + '"'); - } + return googleChart('pie', URL, container, options); } From ff0e617b2a47d64ac3c54f8986622bd547b9baf9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:21:40 +0100 Subject: [PATCH 67/71] This triggers CC --- app/tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/TestCase.php b/app/tests/TestCase.php index 314bf52c53..3ad0086e9c 100644 --- a/app/tests/TestCase.php +++ b/app/tests/TestCase.php @@ -11,7 +11,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase /** * @SuppressWarnings(PHPMD.UnusedLocalVariable) * - * Creates the application. + * Creates the application.. * * @return \Symfony\Component\HttpKernel\HttpKernelInterface */ From 2427ee44a52b6df72a246be7fdc287695a358c85 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:29:45 +0100 Subject: [PATCH 68/71] Updated travis to handle SSL errors when communicating with Code climate. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5eb9cd293a..102eb830b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,5 @@ script: after_script: - cp -v tests/_output/coverage.xml build/logs/clover.xml - php vendor/bin/coveralls - - vendor/bin/test-reporter \ No newline at end of file + - vendor/bin/test-reporter --stdout > codeclimate.json + - "curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports" \ No newline at end of file From cd0033791f95df0fc2e7e2355cca9daf99932926 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:34:10 +0100 Subject: [PATCH 69/71] New SQL reference for 3.2.5 [skip ci] --- _sql/firefly-iii-reference-3.2.5.sql | 607 +++++++++++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 _sql/firefly-iii-reference-3.2.5.sql diff --git a/_sql/firefly-iii-reference-3.2.5.sql b/_sql/firefly-iii-reference-3.2.5.sql new file mode 100644 index 0000000000..b2ad69f707 --- /dev/null +++ b/_sql/firefly-iii-reference-3.2.5.sql @@ -0,0 +1,607 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4096 +# +# http://www.sequelpro.com/ +# http://code.google.com/p/sequel-pro/ +# +# Host: 127.0.0.1 (MySQL 5.6.19-0ubuntu0.14.04.1) +# Database: homestead +# Generation Time: 2015-01-31 05:33:30 +0000 +# ************************************************************ + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table account_meta +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `account_meta`; + +CREATE TABLE `account_meta` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `account_id` int(10) unsigned NOT NULL, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `data` text COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `account_meta_account_id_name_unique` (`account_id`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table account_types +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `account_types`; + +CREATE TABLE `account_types` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `type` varchar(30) COLLATE utf8_unicode_ci NOT NULL, + `editable` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `account_types_type_unique` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +LOCK TABLES `account_types` WRITE; +/*!40000 ALTER TABLE `account_types` DISABLE KEYS */; + +INSERT INTO `account_types` (`id`, `created_at`, `updated_at`, `type`, `editable`) +VALUES + (1,'2015-01-31 05:33:21','2015-01-31 05:33:21','Default account',1), + (2,'2015-01-31 05:33:21','2015-01-31 05:33:21','Cash account',0), + (3,'2015-01-31 05:33:21','2015-01-31 05:33:21','Asset account',1), + (4,'2015-01-31 05:33:21','2015-01-31 05:33:21','Expense account',1), + (5,'2015-01-31 05:33:21','2015-01-31 05:33:21','Revenue account',1), + (6,'2015-01-31 05:33:21','2015-01-31 05:33:21','Initial balance account',0), + (7,'2015-01-31 05:33:21','2015-01-31 05:33:21','Beneficiary account',1), + (8,'2015-01-31 05:33:21','2015-01-31 05:33:21','Import account',0); + +/*!40000 ALTER TABLE `account_types` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table accounts +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `accounts`; + +CREATE TABLE `accounts` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `user_id` int(10) unsigned NOT NULL, + `account_type_id` int(10) unsigned NOT NULL, + `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL, + `active` tinyint(1) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `accounts_user_id_account_type_id_name_unique` (`user_id`,`account_type_id`,`name`), + KEY `accounts_account_type_id_foreign` (`account_type_id`), + CONSTRAINT `accounts_account_type_id_foreign` FOREIGN KEY (`account_type_id`) REFERENCES `account_types` (`id`) ON DELETE CASCADE, + CONSTRAINT `accounts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table bills +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `bills`; + +CREATE TABLE `bills` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `user_id` int(10) unsigned NOT NULL, + `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `match` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `amount_min` decimal(10,2) NOT NULL, + `amount_max` decimal(10,2) NOT NULL, + `date` date NOT NULL, + `active` tinyint(1) NOT NULL, + `automatch` tinyint(1) NOT NULL, + `repeat_freq` enum('daily','weekly','monthly','quarterly','half-year','yearly') COLLATE utf8_unicode_ci NOT NULL, + `skip` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uid_name_unique` (`user_id`,`name`), + CONSTRAINT `bills_uid_for` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table budget_limits +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `budget_limits`; + +CREATE TABLE `budget_limits` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `budget_id` int(10) unsigned DEFAULT NULL, + `startdate` date NOT NULL, + `amount` decimal(10,2) NOT NULL, + `repeats` tinyint(1) NOT NULL, + `repeat_freq` enum('daily','weekly','monthly','quarterly','half-year','yearly') COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_bl_combi` (`budget_id`,`startdate`,`repeat_freq`), + CONSTRAINT `bid_foreign` FOREIGN KEY (`budget_id`) REFERENCES `budgets` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table budget_transaction_journal +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `budget_transaction_journal`; + +CREATE TABLE `budget_transaction_journal` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `budget_id` int(10) unsigned NOT NULL, + `transaction_journal_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `budid_tjid_unique` (`budget_id`,`transaction_journal_id`), + KEY `budget_transaction_journal_transaction_journal_id_foreign` (`transaction_journal_id`), + CONSTRAINT `budget_transaction_journal_transaction_journal_id_foreign` FOREIGN KEY (`transaction_journal_id`) REFERENCES `transaction_journals` (`id`) ON DELETE CASCADE, + CONSTRAINT `budget_transaction_journal_budget_id_foreign` FOREIGN KEY (`budget_id`) REFERENCES `budgets` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table budgets +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `budgets`; + +CREATE TABLE `budgets` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `user_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `budgets_user_id_name_unique` (`user_id`,`name`), + CONSTRAINT `budgets_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table categories +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `categories`; + +CREATE TABLE `categories` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `user_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `categories_user_id_name_unique` (`user_id`,`name`), + CONSTRAINT `categories_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table category_transaction_journal +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `category_transaction_journal`; + +CREATE TABLE `category_transaction_journal` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `category_id` int(10) unsigned NOT NULL, + `transaction_journal_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `catid_tjid_unique` (`category_id`,`transaction_journal_id`), + KEY `category_transaction_journal_transaction_journal_id_foreign` (`transaction_journal_id`), + CONSTRAINT `category_transaction_journal_transaction_journal_id_foreign` FOREIGN KEY (`transaction_journal_id`) REFERENCES `transaction_journals` (`id`) ON DELETE CASCADE, + CONSTRAINT `category_transaction_journal_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table components +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `components`; + +CREATE TABLE `components` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `user_id` int(10) unsigned NOT NULL, + `class` varchar(20) COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `components_user_id_class_name_unique` (`user_id`,`class`,`name`), + CONSTRAINT `components_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table limit_repetitions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `limit_repetitions`; + +CREATE TABLE `limit_repetitions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `budget_limit_id` int(10) unsigned NOT NULL, + `startdate` date NOT NULL, + `enddate` date NOT NULL, + `amount` decimal(10,2) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `limit_repetitions_limit_id_startdate_enddate_unique` (`budget_limit_id`,`startdate`,`enddate`), + CONSTRAINT `limit_repetitions_limit_id_foreign` FOREIGN KEY (`budget_limit_id`) REFERENCES `budget_limits` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table migrations +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `migrations`; + +CREATE TABLE `migrations` ( + `migration` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `batch` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +LOCK TABLES `migrations` WRITE; +/*!40000 ALTER TABLE `migrations` DISABLE KEYS */; + +INSERT INTO `migrations` (`migration`, `batch`) +VALUES + ('2014_06_27_163032_create_users_table',1), + ('2014_06_27_163145_create_account_types_table',1), + ('2014_06_27_163259_create_accounts_table',1), + ('2014_06_27_163817_create_components_table',1), + ('2014_06_27_163818_create_piggybanks_table',1), + ('2014_06_27_164042_create_transaction_currencies_table',1), + ('2014_06_27_164512_create_transaction_types_table',1), + ('2014_06_27_164619_create_recurring_transactions_table',1), + ('2014_06_27_164620_create_transaction_journals_table',1), + ('2014_06_27_164836_create_transactions_table',1), + ('2014_06_27_165344_create_component_transaction_table',1), + ('2014_07_05_171326_create_component_transaction_journal_table',1), + ('2014_07_06_123842_create_preferences_table',1), + ('2014_07_09_204843_create_session_table',1), + ('2014_07_17_183717_create_limits_table',1), + ('2014_07_19_055011_create_limit_repeat_table',1), + ('2014_08_06_044416_create_component_recurring_transaction_table',1), + ('2014_08_12_173919_create_piggybank_repetitions_table',1), + ('2014_08_18_100330_create_piggybank_events_table',1), + ('2014_08_23_113221_create_reminders_table',1), + ('2014_11_10_172053_create_account_meta_table',1), + ('2014_11_29_135749_create_transaction_groups_table',1), + ('2014_11_29_140217_create_transaction_group_transaction_journal_table',1), + ('2014_12_13_190730_changes_for_v321',1), + ('2014_12_24_191544_changes_for_v322',1), + ('2015_01_18_082406_changes_for_v325',1); + +/*!40000 ALTER TABLE `migrations` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table piggy_bank_events +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `piggy_bank_events`; + +CREATE TABLE `piggy_bank_events` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `piggy_bank_id` int(10) unsigned NOT NULL, + `transaction_journal_id` int(10) unsigned DEFAULT NULL, + `date` date NOT NULL, + `amount` decimal(10,2) NOT NULL, + PRIMARY KEY (`id`), + KEY `piggybank_events_piggybank_id_foreign` (`piggy_bank_id`), + KEY `piggybank_events_transaction_journal_id_foreign` (`transaction_journal_id`), + CONSTRAINT `piggybank_events_transaction_journal_id_foreign` FOREIGN KEY (`transaction_journal_id`) REFERENCES `transaction_journals` (`id`) ON DELETE SET NULL, + CONSTRAINT `piggybank_events_piggybank_id_foreign` FOREIGN KEY (`piggy_bank_id`) REFERENCES `piggy_banks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table piggy_bank_repetitions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `piggy_bank_repetitions`; + +CREATE TABLE `piggy_bank_repetitions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `piggy_bank_id` int(10) unsigned NOT NULL, + `startdate` date DEFAULT NULL, + `targetdate` date DEFAULT NULL, + `currentamount` decimal(10,2) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `piggybank_repetitions_piggybank_id_startdate_targetdate_unique` (`piggy_bank_id`,`startdate`,`targetdate`), + CONSTRAINT `piggybank_repetitions_piggybank_id_foreign` FOREIGN KEY (`piggy_bank_id`) REFERENCES `piggy_banks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table piggy_banks +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `piggy_banks`; + +CREATE TABLE `piggy_banks` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `account_id` int(10) unsigned NOT NULL, + `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL, + `targetamount` decimal(10,2) NOT NULL, + `startdate` date DEFAULT NULL, + `targetdate` date DEFAULT NULL, + `repeats` tinyint(1) NOT NULL, + `rep_length` enum('day','week','quarter','month','year') COLLATE utf8_unicode_ci DEFAULT NULL, + `rep_every` smallint(5) unsigned NOT NULL, + `rep_times` smallint(5) unsigned DEFAULT NULL, + `reminder` enum('day','week','quarter','month','year') COLLATE utf8_unicode_ci DEFAULT NULL, + `reminder_skip` smallint(5) unsigned NOT NULL, + `remind_me` tinyint(1) NOT NULL, + `order` int(10) unsigned NOT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `piggybanks_account_id_name_unique` (`account_id`,`name`), + CONSTRAINT `piggybanks_account_id_foreign` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table preferences +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `preferences`; + +CREATE TABLE `preferences` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `user_id` int(10) unsigned NOT NULL, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `data` text COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `preferences_user_id_name_unique` (`user_id`,`name`), + CONSTRAINT `preferences_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table reminders +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `reminders`; + +CREATE TABLE `reminders` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `user_id` int(10) unsigned NOT NULL, + `startdate` date NOT NULL, + `enddate` date DEFAULT NULL, + `active` tinyint(1) NOT NULL, + `notnow` tinyint(1) NOT NULL DEFAULT '0', + `remindersable_id` int(10) unsigned DEFAULT NULL, + `remindersable_type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `reminders_user_id_foreign` (`user_id`), + CONSTRAINT `reminders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table sessions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `sessions`; + +CREATE TABLE `sessions` ( + `id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `payload` text COLLATE utf8_unicode_ci NOT NULL, + `last_activity` int(11) NOT NULL, + UNIQUE KEY `sessions_id_unique` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table transaction_currencies +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transaction_currencies`; + +CREATE TABLE `transaction_currencies` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `code` varchar(3) COLLATE utf8_unicode_ci NOT NULL, + `name` varchar(48) COLLATE utf8_unicode_ci DEFAULT NULL, + `symbol` varchar(8) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `transaction_currencies_code_unique` (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +LOCK TABLES `transaction_currencies` WRITE; +/*!40000 ALTER TABLE `transaction_currencies` DISABLE KEYS */; + +INSERT INTO `transaction_currencies` (`id`, `created_at`, `updated_at`, `deleted_at`, `code`, `name`, `symbol`) +VALUES + (1,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'EUR','Euro','€'), + (2,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'USD','US Dollar','$'), + (3,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'HUF','Hungarian forint','Ft'); + +/*!40000 ALTER TABLE `transaction_currencies` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table transaction_group_transaction_journal +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transaction_group_transaction_journal`; + +CREATE TABLE `transaction_group_transaction_journal` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `transaction_group_id` int(10) unsigned NOT NULL, + `transaction_journal_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `tt_joined` (`transaction_group_id`,`transaction_journal_id`), + KEY `tr_trj_id` (`transaction_journal_id`), + CONSTRAINT `tr_trj_id` FOREIGN KEY (`transaction_journal_id`) REFERENCES `transaction_journals` (`id`) ON DELETE CASCADE, + CONSTRAINT `tr_grp_id` FOREIGN KEY (`transaction_group_id`) REFERENCES `transaction_groups` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table transaction_groups +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transaction_groups`; + +CREATE TABLE `transaction_groups` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `user_id` int(10) unsigned NOT NULL, + `relation` enum('balance') COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `transaction_groups_user_id_foreign` (`user_id`), + CONSTRAINT `transaction_groups_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table transaction_journals +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transaction_journals`; + +CREATE TABLE `transaction_journals` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `user_id` int(10) unsigned NOT NULL, + `transaction_type_id` int(10) unsigned NOT NULL, + `bill_id` int(10) unsigned DEFAULT NULL, + `transaction_currency_id` int(10) unsigned NOT NULL, + `description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, + `completed` tinyint(1) NOT NULL, + `date` date NOT NULL, + `encrypted` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `transaction_journals_user_id_foreign` (`user_id`), + KEY `transaction_journals_transaction_type_id_foreign` (`transaction_type_id`), + KEY `transaction_journals_transaction_currency_id_foreign` (`transaction_currency_id`), + KEY `bill_id_foreign` (`bill_id`), + CONSTRAINT `bill_id_foreign` FOREIGN KEY (`bill_id`) REFERENCES `bills` (`id`) ON DELETE SET NULL, + CONSTRAINT `transaction_journals_transaction_currency_id_foreign` FOREIGN KEY (`transaction_currency_id`) REFERENCES `transaction_currencies` (`id`) ON DELETE CASCADE, + CONSTRAINT `transaction_journals_transaction_type_id_foreign` FOREIGN KEY (`transaction_type_id`) REFERENCES `transaction_types` (`id`) ON DELETE CASCADE, + CONSTRAINT `transaction_journals_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table transaction_types +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transaction_types`; + +CREATE TABLE `transaction_types` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `type` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `transaction_types_type_unique` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +LOCK TABLES `transaction_types` WRITE; +/*!40000 ALTER TABLE `transaction_types` DISABLE KEYS */; + +INSERT INTO `transaction_types` (`id`, `created_at`, `updated_at`, `deleted_at`, `type`) +VALUES + (1,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'Withdrawal'), + (2,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'Deposit'), + (3,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'Transfer'), + (4,'2015-01-31 05:33:21','2015-01-31 05:33:21',NULL,'Opening balance'); + +/*!40000 ALTER TABLE `transaction_types` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table transactions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `transactions`; + +CREATE TABLE `transactions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `deleted_at` timestamp NULL DEFAULT NULL, + `account_id` int(10) unsigned NOT NULL, + `transaction_journal_id` int(10) unsigned NOT NULL, + `description` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `amount` decimal(10,2) NOT NULL, + PRIMARY KEY (`id`), + KEY `transactions_account_id_foreign` (`account_id`), + KEY `transactions_transaction_journal_id_foreign` (`transaction_journal_id`), + CONSTRAINT `transactions_account_id_foreign` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, + CONSTRAINT `transactions_transaction_journal_id_foreign` FOREIGN KEY (`transaction_journal_id`) REFERENCES `transaction_journals` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `users`; + +CREATE TABLE `users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `email` varchar(100) COLLATE utf8_unicode_ci NOT NULL, + `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL, + `reset` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL, + `remember_token` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `users_email_unique` (`email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; From 3254565c098ab45f5bc44ffbee42acd347d2c74d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:34:31 +0100 Subject: [PATCH 70/71] Seed only in test [skip ci] --- app/database/seeds/DatabaseSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php index 9912f3c281..f357dfd6ec 100644 --- a/app/database/seeds/DatabaseSeeder.php +++ b/app/database/seeds/DatabaseSeeder.php @@ -19,7 +19,7 @@ class DatabaseSeeder extends Seeder $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); - if (App::environment() == 'testing' || App::environment() == 'homestead') { + if (App::environment() == 'testing') { $this->call('TestDataSeeder'); } } From 3c76da71321266fb299fbc13682402b85f511bd2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 31 Jan 2015 06:35:19 +0100 Subject: [PATCH 71/71] Update version in readme file. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cda3921a13..a65289f98f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Firefly III +Firefly III (v3.2.5) =========== [![Build Status](https://travis-ci.org/JC5/firefly-iii.svg?branch=develop)](https://travis-ci.org/JC5/firefly-iii)