diff --git a/.env.example b/.env.example index 07b13be62b..6e6fd0fa1a 100644 --- a/.env.example +++ b/.env.example @@ -12,5 +12,7 @@ CACHE_DRIVER=file SESSION_DRIVER=file EMAIL_SMTP= +EMAIL_DRIVER=smtp EMAIL_USERNAME= EMAIL_PASSWORD= +ANALYTICS_ID= \ No newline at end of file diff --git a/.env.testing b/.env.testing index 887ffc6968..1a6022cbcf 100644 --- a/.env.testing +++ b/.env.testing @@ -11,3 +11,7 @@ DB_PASSWORD=secret CACHE_DRIVER=file SESSION_DRIVER=file +EMAIL_SMTP= +EMAIL_USERNAME= +EMAIL_PASSWORD= +ANALYTICS_ID=ABC \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 618936bd03..ee3f0642c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,12 +11,9 @@ addons: repo_token: 26489f9e854fcdf7e7660ba29c1455694685465b1f90329a79f7d2bf448acb61 install: - - rm composer.lock - - composer install + - composer update - php artisan env - mv -v .env.testing .env - - touch tests/database/db.sqlite - - php artisan migrate --seed script: - phpunit --debug diff --git a/README.md b/README.md index 033f8f3f0c..f907bedc14 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -Firefly III (v3.3.5) +Firefly III (v3.3.6) =========== [![Build Status](https://travis-ci.org/JC5/firefly-iii.svg?branch=develop)](https://travis-ci.org/JC5/firefly-iii) [![Project Status](http://stillmaintained.com/JC5/firefly-iii.png?a=b)](http://stillmaintained.com/JC5/firefly-iii) [![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) +[![Coverage Status](https://coveralls.io/repos/JC5/firefly-iii/badge.svg?branch=master)](https://coveralls.io/r/JC5/firefly-iii?branch=master) +[![Coverage Status](https://coveralls.io/repos/JC5/firefly-iii/badge.svg?branch=master)](https://coveralls.io/r/JC5/firefly-iii?branch=develop) [![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) @@ -25,9 +26,14 @@ To install and use Firefly III, please read [the installation guide](https://git ## Current features -- [A double-entry bookkeeping system](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system); +- [A double-entry bookkeeping system](https://en.wikipedia.org/wiki/Double-entry_bookkeeping_system); - You can store, edit and remove withdrawals, deposits and transfers. This allows you full financial management; -- It's possible to create, change and manage money using _budgets_; +- You can manage different types of accounts + - Asset accounts + - Shared asset accounts (household accounts) + - Saving accounts + - Credit cards +- It's possible to create, change and manage money using _[budgets](https://en.wikipedia.org/wiki/Envelope_system)_; - Organize transactions using categories; - Save towards a goal using piggy banks; - Predict and anticipate bills; @@ -48,9 +54,7 @@ Firefly III will feature, but does not feature yet: - More control over other resources outside of personal finance - - Accounts shared with a partner (household accounts) - Debts - - Credit cards - More test-coverage; - Firefly will be able to split transactions; a single purchase can be split in multiple entries, for more fine-grained control. - Firefly will be able to join transactions. @@ -73,7 +77,4 @@ Some stuff has been removed: ## Current state I have the basics up and running. Test coverage is currently coming, slowly. -Although I have not checked extensively, some forms and views have CSRF vulnerabilities. This is because not all -views escape all characters by default. Will be fixed. - Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)! diff --git a/app/Events/JournalCreated.php b/app/Events/JournalCreated.php index debbefd1b8..0fc11fb491 100644 --- a/app/Events/JournalCreated.php +++ b/app/Events/JournalCreated.php @@ -1,7 +1,5 @@ journal = $journal; + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(TransactionJournal $journal, $piggyBankId) + { + // + $this->journal = $journal; $this->piggyBankId = $piggyBankId; - } + } } diff --git a/app/Events/JournalSaved.php b/app/Events/JournalSaved.php index e618861169..25471ca809 100644 --- a/app/Events/JournalSaved.php +++ b/app/Events/JournalSaved.php @@ -1,25 +1,24 @@ journal = $journal; - } + } } diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php index 73329f983b..7280ccc3c6 100644 --- a/app/Handlers/Events/ConnectJournalToPiggyBank.php +++ b/app/Handlers/Events/ConnectJournalToPiggyBank.php @@ -38,7 +38,7 @@ class ConnectJournalToPiggyBank /** @var TransactionJournal $journal */ $journal = $event->journal; $piggyBankId = $event->piggyBankId; - if(intval($piggyBankId) < 1) { + if (intval($piggyBankId) < 1) { return; } @@ -66,7 +66,8 @@ class ConnectJournalToPiggyBank // update piggy bank rep for date of transaction journal. $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); if (is_null($repetition)) { - Log::debug('Found no repetition for piggy bank for date '.$journal->date->format('Y M d')); + Log::debug('Found no repetition for piggy bank for date ' . $journal->date->format('Y M d')); + return; } diff --git a/app/Handlers/Events/RescanJournal.php b/app/Handlers/Events/RescanJournal.php index 9b74e3bb44..226ae01a3c 100644 --- a/app/Handlers/Events/RescanJournal.php +++ b/app/Handlers/Events/RescanJournal.php @@ -1,8 +1,8 @@ id)->first(); - if(is_null($event)) { + $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first(); + if (is_null($event)) { return; } $piggyBank = $event->piggyBank()->first(); diff --git a/app/Helpers/Reminders/ReminderHelperInterface.php b/app/Helpers/Reminders/ReminderHelperInterface.php index 833afe1a24..c3db2dd1d1 100644 --- a/app/Helpers/Reminders/ReminderHelperInterface.php +++ b/app/Helpers/Reminders/ReminderHelperInterface.php @@ -2,16 +2,17 @@ namespace FireflyIII\Helpers\Reminders; -use FireflyIII\Models\Reminder; -use FireflyIII\Models\PiggyBank; use Carbon\Carbon; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Reminder; /** * Interface ReminderHelperInterface * * @package FireflyIII\Helpers\Reminders */ -interface ReminderHelperInterface { +interface ReminderHelperInterface +{ /** * Takes a reminder, finds the piggy bank and tells you what to do now. * Aka how much money to put in. diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index cec2b058e3..e1252e55c1 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -2,6 +2,7 @@ namespace FireflyIII\Helpers\Report; +use App; use Auth; use Carbon\Carbon; use FireflyIII\Models\Account; @@ -40,17 +41,20 @@ class ReportHelper implements ReportHelperInterface * This method gets some kind of list for a monthly overview. * * @param Carbon $date + * @param bool $showSharedReports * * @return Collection */ - public function getBudgetsForMonth(Carbon $date) + public function getBudgetsForMonth(Carbon $date, $showSharedReports = false) { + /** @var \FireflyIII\Helpers\Report\ReportQueryInterface $query */ + $query = App::make('FireflyIII\Helpers\Report\ReportQueryInterface'); + $start = clone $date; $start->startOfMonth(); $end = clone $date; $end->endOfMonth(); - // all budgets - $set = Auth::user()->budgets() + $set = Auth::user()->budgets()->orderBy('budgets.name', 'ASC') ->leftJoin( 'budget_limits', function (JoinClause $join) use ($date) { $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); @@ -58,22 +62,24 @@ class ReportHelper implements ReportHelperInterface ) ->get(['budgets.*', 'budget_limits.amount as amount']); + $budgets = Steam::makeArray($set); + $amountSet = $query->journalsByBudget($start, $end, $showSharedReports); + $amounts = Steam::makeArray($amountSet); + $budgets = Steam::mergeArrays($budgets, $amounts); + $budgets[0]['spent'] = isset($budgets[0]['spent']) ? $budgets[0]['spent'] : 0.0; + $budgets[0]['amount'] = isset($budgets[0]['amount']) ? $budgets[0]['amount'] : 0.0; + $budgets[0]['name'] = 'No budget'; - $budgets = $this->_helper->makeArray($set); - $amountSet = $this->_queries->journalsByBudget($start, $end); - $amounts = $this->_helper->makeArray($amountSet); - $combined = $this->_helper->mergeArrays($budgets, $amounts); - $combined[0]['spent'] = isset($combined[0]['spent']) ? $combined[0]['spent'] : 0.0; - $combined[0]['amount'] = isset($combined[0]['amount']) ? $combined[0]['amount'] : 0.0; - $combined[0]['name'] = 'No budget'; - - // find transactions to shared expense accounts, which are without a budget by default: - $transfers = $this->_queries->sharedExpenses($start, $end); - foreach ($transfers as $transfer) { - $combined[0]['spent'] += floatval($transfer->amount) * -1; + // find transactions to shared asset accounts, which are without a budget by default: + // which is only relevant when shared asset accounts are hidden. + if ($showSharedReports === false) { + $transfers = $query->sharedExpenses($start, $end); + foreach ($transfers as $transfer) { + $budgets[0]['spent'] += floatval($transfer->amount) * -1; + } } - return $combined; + return $budgets; } /** @@ -113,6 +119,9 @@ class ReportHelper implements ReportHelperInterface $years[] = $start->format('Y'); $start->addYear(); } + $years[] = Carbon::now()->format('Y'); + // force the current year. + $years = array_unique($years); return $years; } diff --git a/app/Helpers/Report/ReportQuery.php b/app/Helpers/Report/ReportQuery.php index 55d0b5ec73..1e00357743 100644 --- a/app/Helpers/Report/ReportQuery.php +++ b/app/Helpers/Report/ReportQuery.php @@ -4,10 +4,12 @@ namespace FireflyIII\Helpers\Report; use Auth; use Carbon\Carbon; +use Crypt; use DB; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; use Steam; @@ -87,9 +89,9 @@ class ReportQuery implements ReportQueryInterface ->whereNull('budget_transaction_journal.budget_id')->whereNull('transaction_journals.deleted_at') ->whereNull('otherJournals.deleted_at') ->where('transactions.account_id', $account->id) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') ->whereNotNull('transaction_group_transaction_journal.transaction_group_id') ->get( [ @@ -174,26 +176,9 @@ class ReportQuery implements ReportQueryInterface */ public function getBudgetSummary(Account $account, Carbon $start, Carbon $end) { - $set = TransactionJournal:: - leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->before($end) - ->after($start) - ->where('accounts.id', $account->id) - ->where('transaction_journals.user_id', Auth::user()->id) - ->where('transaction_types.type', 'Withdrawal') - ->groupBy('budgets.id') - ->orderBy('budgets.name', 'ASC') - ->get(['budgets.id', 'budgets.name', DB::Raw('SUM(`transactions`.`amount`) as `amount`')]); + $query = $this->queryJournalsNoBudget($account, $start, $end); - return $set; + return $query->get(['budgets.id', 'budgets.name', DB::Raw('SUM(`transactions`.`amount`) as `amount`')]); } @@ -209,26 +194,9 @@ class ReportQuery implements ReportQueryInterface */ public function getTransactionsWithoutBudget(Account $account, Carbon $start, Carbon $end) { - $set = TransactionJournal:: - leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->before($end) - ->after($start) - ->where('accounts.id', $account->id) - ->where('transaction_journals.user_id', Auth::user()->id) - ->where('transaction_types.type', 'Withdrawal') - ->whereNull('budgets.id') - ->orderBy('transaction_journals.date', 'ASC') - ->get(['budgets.name', 'transactions.amount', 'transaction_journals.*']); + $query = $this->queryJournalsNoBudget($account, $start, $end); - return $set; + return $query->get(['budgets.name', 'transactions.amount', 'transaction_journals.*']); } /** @@ -244,30 +212,7 @@ class ReportQuery implements ReportQueryInterface */ public function incomeByPeriod(Carbon $start, Carbon $end, $showSharedReports = false) { - $query = 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'); + $query = $this->queryJournalsWithTransactions($start, $end); if ($showSharedReports === false) { // only get deposits not to a shared account // and transfers to a shared account. @@ -291,11 +236,10 @@ class ReportQuery implements ReportQueryInterface // any deposit is fine. $query->where('transaction_types.type', 'Deposit'); } - $query->before($end)->after($start) - ->where('transaction_journals.user_id', Auth::user()->id) - ->groupBy('t_from.account_id')->orderBy('transaction_journals.date'); + $query->groupBy('t_from.account_id')->orderBy('transaction_journals.date'); - return $query->get( + // get everything, decrypt and return + $data = $query->get( ['transaction_journals.id', 'transaction_journals.description', 'transaction_journals.encrypted', @@ -303,8 +247,19 @@ class ReportQuery implements ReportQueryInterface DB::Raw('SUM(`t_to`.`amount`) as `amount`'), 'transaction_journals.date', 't_from.account_id as account_id', - 'ac_from.name as name'] + 'ac_from.name as name', + 'ac_from.encrypted as account_encrypted' + ] ); + + $data->each( + function (Model $object) { +// $object->description = intval($object->encrypted); + $object->name = intval($object->account_encrypted) == 1 ? Crypt::decrypt($object->name) : $object->name; + } + ); + + return $data; } /** @@ -382,7 +337,15 @@ class ReportQuery implements ReportQueryInterface ->groupBy('categories.id') ->orderBy('amount'); - return $query->get(['categories.id', 'categories.name', DB::Raw('SUM(`transactions`.`amount`) AS `amount`')]); + $data = $query->get(['categories.id', 'categories.encrypted', 'categories.name', DB::Raw('SUM(`transactions`.`amount`) AS `amount`')]); + // decrypt data: + $data->each( + function (Model $object) { + $object->name = intval($object->encrypted) == 1 ? Crypt::decrypt($object->name) : $object->name; + } + ); + + return $data; } @@ -400,29 +363,7 @@ class ReportQuery implements ReportQueryInterface */ public function journalsByExpenseAccount(Carbon $start, Carbon $end, $showSharedReports = false) { - $query = 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'); - + $query = $this->queryJournalsWithTransactions($start, $end); if ($showSharedReports === false) { // get all withdrawals not from a shared accounts // and all transfers to a shared account @@ -446,13 +387,21 @@ class ReportQuery implements ReportQueryInterface // any withdrawal goes: $query->where('transaction_types.type', 'Withdrawal'); } - $query->before($end) - ->after($start) + $query->before($end)->after($start) ->where('transaction_journals.user_id', Auth::user()->id) ->groupBy('t_to.account_id') ->orderBy('amount', 'DESC'); - return $query->get(['t_to.account_id as id', 'ac_to.name as name', DB::Raw('SUM(t_to.amount) as `amount`')]); + $data = $query->get(['t_to.account_id as id', 'ac_to.name as name', 'ac_to.encrypted', DB::Raw('SUM(t_to.amount) as `amount`')]); + + // decrypt + $data->each( + function (Model $object) { + $object->name = intval($object->encrypted) == 1 ? Crypt::decrypt($object->name) : $object->name; + } + ); + + return $data; } /** @@ -466,30 +415,7 @@ class ReportQuery implements ReportQueryInterface */ public function journalsByRevenueAccount(Carbon $start, Carbon $end, $showSharedReports = false) { - $query = 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'); + $query = $this->queryJournalsWithTransactions($start, $end); if ($showSharedReports === false) { // show queries where transfer type is deposit, and its not to a shared account @@ -514,11 +440,20 @@ class ReportQuery implements ReportQueryInterface // any deposit goes: $query->where('transaction_types.type', 'Deposit'); } - $query->before($end)->after($start) - ->where('transaction_journals.user_id', Auth::user()->id) - ->groupBy('t_from.account_id')->orderBy('amount'); - return $query->get(['t_from.account_id as account_id', 'ac_from.name as name', DB::Raw('SUM(t_from.amount) as `amount`')]); + $query->groupBy('t_from.account_id')->orderBy('amount'); + + $data = $query->get( + ['t_from.account_id as account_id', 'ac_from.name as name', 'ac_from.encrypted as encrypted', DB::Raw('SUM(t_from.amount) as `amount`')] + ); + // decrypt + $data->each( + function (Model $object) { + $object->name = intval($object->encrypted) == 1 ? Crypt::decrypt($object->name) : $object->name; + } + ); + + return $data; } /** @@ -604,4 +539,74 @@ class ReportQuery implements ReportQueryInterface ); } + /** + * + * This query will get all transaction journals and budget information for a specified account + * in a certain date range, where the transaction journal does not have a budget. + * There is no get() specified, this is up to the method itself. + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return Builder + */ + protected function queryJournalsNoBudget(Account $account, Carbon $start, Carbon $end) + { + return TransactionJournal:: + leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->before($end) + ->after($start) + ->where('accounts.id', $account->id) + ->where('transaction_journals.user_id', Auth::user()->id) + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('budgets.id') + ->orderBy('budgets.name', 'ASC'); + } + + /** + * @param Carbon $start + * @param Carbon $end + * + * @return Builder + */ + protected function queryJournalsWithTransactions(Carbon $start, Carbon $end) + { + $query = 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'); + $query->before($end)->after($start)->where('transaction_journals.user_id', Auth::user()->id); + + return $query; + } + } diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 9cf2d0918e..b671775f0c 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -84,6 +84,7 @@ class AccountController extends Controller */ public function edit(Account $account, AccountRepositoryInterface $repository) { + $what = Config::get('firefly.shortNamesByFullName')[$account->accountType->type]; $subTitle = 'Edit ' . strtolower(e($account->accountType->type)) . ' "' . e($account->name) . '"'; $subTitleIcon = Config::get('firefly.subIconsByIdentifier.' . $what); @@ -93,15 +94,19 @@ class AccountController extends Controller // the opening balance is tricky: $openingBalanceAmount = null; + if ($openingBalance) { - $transaction = $openingBalance->transactions()->where('account_id', $account->id)->first(); + $transaction = $repository->getFirstTransaction($openingBalance, $account); $openingBalanceAmount = $transaction->amount; } $preFilled = [ - 'accountRole' => $account->getMeta('accountRole'), - 'openingBalanceDate' => $openingBalance ? $openingBalance->date->format('Y-m-d') : null, - 'openingBalance' => $openingBalanceAmount + 'accountRole' => $account->getMeta('accountRole'), + 'ccType' => $account->getMeta('ccType'), + 'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'), + 'openingBalanceDate' => $openingBalance ? $openingBalance->date->format('Y-m-d') : null, + 'openingBalance' => $openingBalanceAmount, + 'virtualBalance' => floatval($account->virtual_balance) ]; Session::flash('preFilled', $preFilled); @@ -132,7 +137,7 @@ class AccountController extends Controller $total = Auth::user()->accounts()->accountTypeIn($types)->count(); // last activity: - $start = clone Session::get('start'); + $start = clone Session::get('start', Carbon::now()->startOfMonth()); $start->subDay(); $set->each( function (Account $account) use ($start) { @@ -169,7 +174,8 @@ class AccountController extends Controller $what = Config::get('firefly.shortNamesByFullName.' . $account->accountType->type); $journals = $repository->getJournals($account, $page); $subTitle = 'Details for ' . strtolower(e($account->accountType->type)) . ' "' . e($account->name) . '"'; - $journals->setPath('accounts/show/'.$account->id); + $journals->setPath('accounts/show/' . $account->id); + return view('accounts.show', compact('account', 'what', 'subTitleIcon', 'journals', 'subTitle')); } @@ -184,6 +190,7 @@ class AccountController extends Controller $accountData = [ 'name' => $request->input('name'), 'accountType' => $request->input('what'), + 'virtualBalance' => floatval($request->input('virtualBalance')), 'active' => true, 'user' => Auth::user()->id, 'accountRole' => $request->input('accountRole'), @@ -219,9 +226,12 @@ class AccountController extends Controller 'active' => $request->input('active'), 'user' => Auth::user()->id, 'accountRole' => $request->input('accountRole'), + 'virtualBalance' => floatval($request->input('virtualBalance')), 'openingBalance' => floatval($request->input('openingBalance')), 'openingBalanceDate' => new Carbon($request->input('openingBalanceDate')), 'openingBalanceCurrency' => intval($request->input('balance_currency_id')), + 'ccType' => $request->input('ccType'), + 'ccMonthlyPaymentDate' => $request->input('ccMonthlyPaymentDate'), ]; $repository->update($account, $accountData); diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index a4fd311bfd..97bb5bc4b1 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -64,7 +64,7 @@ class AuthController extends Controller ); } - $data =$request->all(); + $data = $request->all(); $data['password'] = bcrypt($data['password']); $this->auth->login($this->registrar->create($data)); @@ -81,6 +81,8 @@ class AuthController extends Controller // set flash message Session::flash('success', 'You have registered successfully!'); + Session::flash('gaEventCategory', 'user'); + Session::flash('gaEventAction', 'new-registration'); return redirect($this->redirectPath()); diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 457b20bdb1..1535577102 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -1,9 +1,10 @@ match); + $description = []; + $expense = null; + + // get users expense accounts: + $type = AccountType::where('type', 'Expense account')->first(); + $accounts = Auth::user()->accounts()->where('account_type_id', $type->id)->get(); + + foreach ($matches as $match) { + $match = strtolower($match); + // find expense account for each word if not found already: + if (is_null($expense)) { + /** @var Account $account */ + foreach ($accounts as $account) { + $name = strtolower($account->name); + if (!(strpos($name, $match) === false)) { + $expense = $account; + break; + } + } + + + } + if (is_null($expense)) { + $description[] = $match; + } + } + $parameters = [ + 'description' => ucfirst(join(' ', $description)), + 'expense_account' => is_null($expense) ? '' : $expense->name, + 'amount' => round(($bill->amount_min + $bill->amount_max), 2), + ]; + Session::put('preFilled', $parameters); + + return Redirect::to(route('transactions.create', 'withdrawal')); + } + /** * @return $this */ @@ -139,11 +187,10 @@ class BillController extends Controller public function show(Bill $bill, BillRepositoryInterface $repository) { $journals = $bill->transactionjournals()->withRelevantData() - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') - - ->get(); + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->get(); $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); $hideBill = true; @@ -156,22 +203,8 @@ class BillController extends Controller */ public function store(BillFormRequest $request, BillRepositoryInterface $repository) { - - $billData = [ - 'name' => $request->get('name'), - 'match' => $request->get('match'), - 'amount_min' => floatval($request->get('amount_min')), - 'amount_currency_id' => floatval($request->get('amount_currency_id')), - 'amount_max' => floatval($request->get('amount_max')), - 'date' => new Carbon($request->get('date')), - 'user' => Auth::user()->id, - 'repeat_freq' => $request->get('repeat_freq'), - 'skip' => intval($request->get('skip')), - 'automatch' => intval($request->get('automatch')) === 1, - 'active' => intval($request->get('active')) === 1, - ]; - - $bill = $repository->store($billData); + $billData = $request->getBillData(); + $bill = $repository->store($billData); Session::flash('success', 'Bill "' . e($bill->name) . '" stored.'); if (intval(Input::get('create_another')) === 1) { @@ -189,21 +222,8 @@ class BillController extends Controller */ public function update(Bill $bill, BillFormRequest $request, BillRepositoryInterface $repository) { - $billData = [ - 'name' => $request->get('name'), - 'match' => $request->get('match'), - 'amount_min' => floatval($request->get('amount_min')), - 'amount_currency_id' => floatval($request->get('amount_currency_id')), - 'amount_max' => floatval($request->get('amount_max')), - 'date' => new Carbon($request->get('date')), - 'user' => Auth::user()->id, - 'repeat_freq' => $request->get('repeat_freq'), - 'skip' => intval($request->get('skip')), - 'automatch' => intval($request->get('automatch')) === 1, - 'active' => intval($request->get('active')) === 1, - ]; - - $bill = $repository->update($bill, $billData); + $billData = $request->getBillData(); + $bill = $repository->update($bill, $billData); if (intval(Input::get('return_to_edit')) === 1) { return Redirect::route('bills.edit', $bill->id); diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 1cd2d46706..a4862219a6 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use FireflyIII\Http\Requests; use FireflyIII\Http\Requests\BudgetFormRequest; use FireflyIII\Models\Budget; +use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Input; @@ -13,7 +14,7 @@ use Redirect; use Response; use Session; use View; - +use URL; /** * Class BudgetController * @@ -22,6 +23,9 @@ use View; class BudgetController extends Controller { + /** + * + */ public function __construct() { View::share('title', 'Budgets'); @@ -32,7 +36,6 @@ class BudgetController extends Controller * @param Budget $budget * * @return \Illuminate\Http\JsonResponse - * @throws Exception */ public function amount(Budget $budget, BudgetRepositoryInterface $repository) { @@ -49,6 +52,12 @@ class BudgetController extends Controller */ public function create() { + // put previous url in session if not redirect from store (not "create another"). + if (Session::get('budgets.create.fromStore') !== true) { + Session::put('budgets.create.url', URL::previous()); + } + Session::forget('budgets.create.fromStore'); + return view('budgets.create')->with('subTitle', 'Create a new budget'); } @@ -61,6 +70,9 @@ class BudgetController extends Controller { $subTitle = 'Delete budget' . e($budget->name) . '"'; + // put previous url in session + Session::put('budgets.delete.url', URL::previous()); + return view('budgets.delete', compact('budget', 'subTitle')); } @@ -75,9 +87,11 @@ class BudgetController extends Controller $name = $budget->name; $repository->destroy($budget); + + Session::flash('success', 'The budget "' . e($name) . '" was deleted.'); - return Redirect::route('budgets.index'); + return Redirect::to(Session::get('budgets.delete.url')); } /** @@ -89,6 +103,12 @@ class BudgetController extends Controller { $subTitle = 'Edit budget "' . e($budget->name) . '"'; + // put previous url in session if not redirect from store (not "return_to_edit"). + if (Session::get('budgets.edit.fromUpdate') !== true) { + Session::put('budgets.edit.url', URL::previous()); + } + Session::forget('budgets.edit.fromUpdate'); + return view('budgets.edit', compact('budget', 'subTitle')); } @@ -98,7 +118,15 @@ class BudgetController extends Controller */ public function index(BudgetRepositoryInterface $repository) { - $budgets = Auth::user()->budgets()->get(); + $budgets = Auth::user()->budgets()->where('active', 1)->get(); + $inactive = Auth::user()->budgets()->where('active', 0)->get(); + + /** + * Do some cleanup: + */ + $repository->cleanupBudgets(); + + // loop the budgets: $budgets->each( @@ -117,7 +145,7 @@ class BudgetController extends Controller $budgetMax = Preferences::get('budgetMaximum', 1000); $budgetMaximum = $budgetMax->data; - return view('budgets.index', compact('budgetMaximum', 'budgets', 'spent', 'spentPCT', 'overspent', 'amount')); + return view('budgets.index', compact('budgetMaximum', 'inactive', 'budgets', 'spent', 'spentPCT', 'overspent', 'amount')); } /** @@ -133,9 +161,9 @@ class BudgetController extends Controller ->whereNull('budget_transaction_journal.id') ->before($end) ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') ->get(['transaction_journals.*']); $subTitle = 'Transactions without a budget in ' . $start->format('F Y'); @@ -171,10 +199,13 @@ class BudgetController extends Controller Session::flash('success', 'New budget "' . $budget->name . '" stored!'); if (intval(Input::get('create_another')) === 1) { + // set value so create routine will not overwrite URL: + Session::put('budgets.create.fromStore', true); return Redirect::route('budgets.create')->withInput(); } - return Redirect::route('budgets.index'); + // redirect to previous URL. + return Redirect::to(Session::get('budgets.create.url')); } @@ -209,7 +240,8 @@ class BudgetController extends Controller public function update(Budget $budget, BudgetFormRequest $request, BudgetRepositoryInterface $repository) { $budgetData = [ - 'name' => $request->input('name'), + 'name' => $request->input('name'), + 'active' => intval($request->input('active')) == 1 ]; $repository->update($budget, $budgetData); @@ -217,10 +249,13 @@ class BudgetController extends Controller Session::flash('success', 'Budget "' . $budget->name . '" updated.'); if (intval(Input::get('return_to_edit')) === 1) { - return Redirect::route('budgets.edit', $budget->id); + // set value so edit routine will not overwrite URL: + Session::put('budgets.edit.fromUpdate', true); + return Redirect::route('budgets.edit', $budget->id)->withInput(['return_to_edit' => 1]); } - return Redirect::route('budgets.index'); + // redirect to previous URL. + return Redirect::to(Session::get('budgets.edit.url')); } diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index e0147a4dbd..619e61748f 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -11,7 +11,7 @@ use Input; use Redirect; use Session; use View; - +use URL; /** * Class CategoryController @@ -35,6 +35,12 @@ class CategoryController extends Controller */ public function create() { + // put previous url in session if not redirect from store (not "create another"). + if (Session::get('categories.create.fromStore') !== true) { + Session::put('categories.create.url', URL::previous()); + } + Session::forget('categories.create.fromStore'); + return view('categories.create')->with('subTitle', 'Create a new category'); } @@ -47,6 +53,9 @@ class CategoryController extends Controller { $subTitle = 'Delete category' . e($category->name) . '"'; + // put previous url in session + Session::put('categories.delete.url', URL::previous()); + return view('categories.delete', compact('category', 'subTitle')); } @@ -63,7 +72,7 @@ class CategoryController extends Controller Session::flash('success', 'The category "' . e($name) . '" was deleted.'); - return Redirect::route('categories.index'); + return Redirect::to(Session::get('categories.delete.url')); } /** @@ -75,6 +84,12 @@ class CategoryController extends Controller { $subTitle = 'Edit category "' . e($category->name) . '"'; + // put previous url in session if not redirect from store (not "return_to_edit"). + if (Session::get('categories.edit.fromUpdate') !== true) { + Session::put('categories.edit.url', URL::previous()); + } + Session::forget('categories.edit.fromUpdate'); + return view('categories.edit', compact('category', 'subTitle')); } @@ -89,10 +104,10 @@ class CategoryController extends Controller $categories->each( function (Category $category) { $latest = $category->transactionjournals() - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') - ->first(); + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->first(); if ($latest) { $category->lastActivity = $latest->date; } @@ -115,9 +130,9 @@ class CategoryController extends Controller ->whereNull('category_transaction_journal.id') ->before($end) ->after($start) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') ->get(['transaction_journals.*']); $subTitle = 'Transactions without a category between ' . $start->format('jS F Y') . ' and ' . $end->format('jS F Y'); @@ -136,14 +151,12 @@ class CategoryController extends Controller $page = intval(Input::get('page')); $offset = $page > 0 ? $page * 50 : 0; $set = $category->transactionJournals()->withRelevantData()->take(50)->offset($offset) - - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC') - - ->get( - ['transaction_journals.*'] - ); + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->get( + ['transaction_journals.*'] + ); $count = $category->transactionJournals()->count(); $journals = new LengthAwarePaginator($set, $count, 50, $page); @@ -166,16 +179,22 @@ class CategoryController extends Controller $category = $repository->store($categoryData); Session::flash('success', 'New category "' . $category->name . '" stored!'); - + if (intval(Input::get('create_another')) === 1) { + Session::put('categories.create.fromStore', true); return Redirect::route('categories.create')->withInput(); } +<<<<<<< HEAD if (intval(Input::get('create_another')) === 1) { return Redirect::route('categories.create'); } return Redirect::route('categories.index'); +======= + // redirect to previous URL. + return Redirect::to(Session::get('categories.create.url')); +>>>>>>> release/3.3.6 } @@ -198,10 +217,12 @@ class CategoryController extends Controller Session::flash('success', 'Category "' . $category->name . '" updated.'); if (intval(Input::get('return_to_edit')) === 1) { + Session::put('categories.edit.fromUpdate', true); return Redirect::route('categories.edit', $category->id); } - return Redirect::route('categories.index'); + // redirect to previous URL. + return Redirect::to(Session::get('categories.edit.url')); } diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index 9fed87a44b..a88eeb65d0 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -10,7 +10,7 @@ use Preferences; use Redirect; use Session; use View; - +use URL; /** * Class CurrencyController @@ -39,6 +39,12 @@ class CurrencyController extends Controller $subTitleIcon = 'fa-plus'; $subTitle = 'Create a new currency'; + // put previous url in session if not redirect from store (not "create another"). + if (Session::get('currency.create.fromStore') !== true) { + Session::put('currency.create.url', URL::previous()); + } + Session::forget('currency.create.fromStore'); + return view('currency.create', compact('subTitleIcon', 'subTitle')); } @@ -72,6 +78,9 @@ class CurrencyController extends Controller if ($currency->transactionJournals()->count() > 0) { Session::flash('error', 'Cannot delete ' . e($currency->name) . ' because there are still transactions attached to it.'); + // put previous url in session + Session::put('currency.delete.url', URL::previous()); + return Redirect::route('currency.index'); } @@ -96,7 +105,7 @@ class CurrencyController extends Controller $currency->delete(); - return Redirect::route('currency.index'); + return Redirect::to(Session::get('currency.delete.url')); } /** @@ -110,6 +119,12 @@ class CurrencyController extends Controller $subTitle = 'Edit currency "' . e($currency->name) . '"'; $currency->symbol = htmlentities($currency->symbol); + // put previous url in session if not redirect from store (not "return_to_edit"). + if (Session::get('currency.edit.fromUpdate') !== true) { + Session::put('currency.edit.url', URL::previous()); + } + Session::forget('currency.edit.fromUpdate'); + return view('currency.edit', compact('currency', 'subTitle', 'subTitleIcon')); } @@ -148,10 +163,12 @@ class CurrencyController extends Controller Session::flash('success', 'Currency "' . $currency->name . '" created'); if (intval(Input::get('create_another')) === 1) { + Session::put('currency.create.fromStore', true); return Redirect::route('currency.create')->withInput(); } - return Redirect::route('currency.index'); + // redirect to previous URL. + return Redirect::to(Session::get('categories.create.url')); } @@ -173,10 +190,12 @@ class CurrencyController extends Controller if (intval(Input::get('return_to_edit')) === 1) { + Session::put('currency.edit.fromUpdate', true); return Redirect::route('currency.edit', $currency->id); } - return Redirect::route('currency.index'); + // redirect to previous URL. + return Redirect::to(Session::get('currency.edit.url')); } diff --git a/app/Http/Controllers/GoogleChartController.php b/app/Http/Controllers/GoogleChartController.php index c7095c0c84..cee4d60b8e 100644 --- a/app/Http/Controllers/GoogleChartController.php +++ b/app/Http/Controllers/GoogleChartController.php @@ -1,8 +1,10 @@ startOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth()); $current = clone $start; - $today = new Carbon; + $today = new Carbon; while ($end >= $current) { $certain = $current < $today; @@ -237,29 +239,31 @@ class GoogleChartController extends Controller $start = Session::get('start', Carbon::now()->startOfMonth()); $end = Session::get('end', Carbon::now()->endOfMonth()); $set = TransactionJournal:: - where('transaction_journals.user_id',Auth::user()->id) - ->leftJoin( - 'transactions', - function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('amount', '>', 0); - } - ) + where('transaction_journals.user_id', Auth::user()->id) + ->leftJoin( + 'transactions', + function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('amount', '>', 0); + } + ) ->leftJoin( 'category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id' ) ->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id') ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') ->before($end) - ->where('categories.user_id',Auth::user()->id) + ->where('categories.user_id', Auth::user()->id) ->after($start) ->where('transaction_types.type', 'Withdrawal') ->groupBy('categories.id') ->orderBy('sum', 'DESC') - ->get(['categories.id', 'categories.name', \DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); + ->get(['categories.id', 'categories.encrypted', 'categories.name', \DB::Raw('SUM(`transactions`.`amount`) AS `sum`')]); foreach ($set as $entry) { - $entry->name = strlen($entry->name) == 0 ? '(no category)' : $entry->name; - $chart->addRow($entry->name, floatval($entry->sum)); + $isEncrypted = intval($entry->encrypted) == 1 ? true : false; + $name = strlen($entry->name) == 0 ? '(no category)' : $entry->name; + $name = $isEncrypted ? Crypt::decrypt($name) : $name; + $chart->addRow($name, floatval($entry->sum)); } $chart->generate(); @@ -279,7 +283,7 @@ class GoogleChartController extends Controller $chart->addColumn('Date', 'date'); $chart->addColumn('Max amount', 'number'); $chart->addColumn('Min amount', 'number'); - $chart->addColumn('Current entry', 'number'); + $chart->addColumn('Recorded bill entry', 'number'); // get first transaction or today for start: $first = $bill->transactionjournals()->orderBy('date', 'ASC')->first(); @@ -288,22 +292,18 @@ class GoogleChartController extends Controller } else { $start = new Carbon; } - $end = new Carbon; - while ($start <= $end) { - $result = $bill->transactionjournals()->before($end)->after($start)->first(); - if ($result) { - /** @var Transaction $tr */ - foreach ($result->transactions()->get() as $tr) { - if (floatval($tr->amount) > 0) { - $amount = floatval($tr->amount); - } + + $results = $bill->transactionjournals()->after($start)->get(); + /** @var TransactionJournal $result */ + foreach ($results as $result) { + $amount = 0; + /** @var Transaction $tr */ + foreach ($result->transactions()->get() as $tr) { + if (floatval($tr->amount) > 0) { + $amount = floatval($tr->amount); } - } else { - $amount = 0; } - unset($result); - $chart->addRow(clone $start, $bill->amount_max, $bill->amount_min, $amount); - $start = Navigation::addPeriod($start, $bill->repeat_freq, 0); + $chart->addRow(clone $result->date, $bill->amount_max, $bill->amount_min, $amount); } $chart->generate(); @@ -356,6 +356,49 @@ class GoogleChartController extends Controller } } + /** + * Find credit card accounts and possibly unpaid credit card bills. + */ + $creditCards = Auth::user()->accounts() + ->hasMetaValue('accountRole', 'ccAsset') + ->hasMetaValue('ccType', 'monthlyFull') + ->get( + [ + 'accounts.*', + 'ccType.data as ccType', + 'accountRole.data as accountRole' + ] + ); + // if the balance is not zero, the monthly payment is still underway. + /** @var Account $creditCard */ + foreach ($creditCards as $creditCard) { + $balance = Steam::balance($creditCard, null, true); + $date = new Carbon($creditCard->getMeta('ccMonthlyPaymentDate')); + if ($balance < 0) { + // unpaid! + $unpaid['amount'] += $balance * -1; + $unpaid['items'][] = $creditCard->name . ' (expected ' . Amount::format(($balance * -1), false) . ') on the ' . $date->format('jS') . ')'; + } + if ($balance == 0) { + // find a transfer TO the credit card which should account for + // anything paid. If not, the CC is not yet used. + $transactions = $creditCard->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->before($end)->after($start)->get(); + if ($transactions->count() > 0) { + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $journal = $transaction->transactionJournal; + if ($journal->transactionType->type == 'Transfer') { + $paid['amount'] += floatval($transaction->amount); + $paid['items'][] = $creditCard->name . + ' (paid ' . Amount::format((floatval($transaction->amount)), false) . + ' on the ' . $journal->date->format('jS') . ')'; + } + } + } + } + } $chart->addRow('Unpaid: ' . join(', ', $unpaid['items']), $unpaid['amount']); $chart->addRow('Paid: ' . join(', ', $paid['items']), $paid['amount']); $chart->generate(); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index b44db645be..76b41d0bc9 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -1,11 +1,12 @@ getFrontpageAccounts($frontPage); $savings = $repository->getSavingsAccounts(); + // check if all books are correct. + $sum = floatval(Auth::user()->transactions()->sum('amount')); + if ($sum != 0) { + Session::flash( + 'error', 'Your transactions are unbalanced. This means a' + . ' withdrawal, deposit or transfer was not stored properly. ' + . 'Please check your accounts and transactions for errors.' + ); + } + foreach ($accounts as $account) { $set = $repository->getFrontpageTransactions($account, $start, $end); if (count($set) > 0) { diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 48981f8143..322e34c97a 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -2,14 +2,18 @@ use Amount; use Auth; +use Carbon\Carbon; use DB; +use FireflyIII\Models\Account; use FireflyIII\Models\Bill; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Input; use Preferences; use Response; use Session; +use Steam; /** * Class JsonController @@ -70,11 +74,34 @@ class JsonController extends Controller $count = $bill->transactionjournals()->before($range['end'])->after($range['start'])->count(); if ($count == 0) { $amount += floatval($bill->amount_max + $bill->amount_min / 2); - } } } + + /** + * Find credit card accounts and possibly unpaid credit card bills. + */ + $creditCards = Auth::user()->accounts() + ->hasMetaValue('accountRole', 'ccAsset') + ->hasMetaValue('ccType', 'monthlyFull') + ->get( + [ + 'accounts.*', + 'ccType.data as ccType', + 'accountRole.data as accountRole' + ] + ); + // if the balance is not zero, the monthly payment is still underway. + /** @var Account $creditCard */ + foreach ($creditCards as $creditCard) { + $balance = Steam::balance($creditCard, null, true); + if ($balance < 0) { + // unpaid! + $amount += $balance * -1; + } + } + break; case 'bills-paid': $box = 'bills-paid'; @@ -89,8 +116,8 @@ class JsonController extends Controller // paid a bill in this range? $count = $bill->transactionjournals()->before($range['end'])->after($range['start'])->count(); if ($count != 0) { - $journal = $bill->transactionjournals()->with('transactions')->before($range['end'])->after($range['start'])->first(); - $currentAmount = 0; + $journal = $bill->transactionjournals()->with('transactions')->before($range['end'])->after($range['start'])->first(); + $currentAmount = 0; foreach ($journal->transactions as $t) { if (floatval($t->amount) > 0) { $currentAmount = floatval($t->amount); @@ -101,6 +128,41 @@ class JsonController extends Controller } } + + /** + * Find credit card accounts and possibly unpaid credit card bills. + */ + $creditCards = Auth::user()->accounts() + ->hasMetaValue('accountRole', 'ccAsset') + ->hasMetaValue('ccType', 'monthlyFull') + ->get( + [ + 'accounts.*', + 'ccType.data as ccType', + 'accountRole.data as accountRole' + ] + ); + // if the balance is not zero, the monthly payment is still underway. + /** @var Account $creditCard */ + foreach ($creditCards as $creditCard) { + $balance = Steam::balance($creditCard, null, true); + if ($balance == 0) { + // find a transfer TO the credit card which should account for + // anything paid. If not, the CC is not yet used. + $transactions = $creditCard->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->before($end)->after($start)->get(); + if ($transactions->count() > 0) { + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $journal = $transaction->transactionJournal; + if ($journal->transactionType->type == 'Transfer') { + $amount += floatval($transaction->amount); + } + } + } + } + } } return Response::json(['box' => $box, 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]); diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index dd0a6979c0..51349c6371 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -17,6 +17,7 @@ use Redirect; use Session; use Steam; use View; +use URL; /** * Class PiggyBankController @@ -49,9 +50,6 @@ class PiggyBankController extends Controller $leftToSave = $piggyBank->targetamount - $savedSoFar; $maxAmount = min($leftOnAccount, $leftToSave); - - \Log::debug('Now going to view for piggy bank #' . $piggyBank->id . ' (' . $piggyBank->name . ')'); - return view('piggy-banks.add', compact('piggyBank', 'maxAmount')); } @@ -68,6 +66,12 @@ class PiggyBankController extends Controller $subTitle = 'Create new piggy bank'; $subTitleIcon = 'fa-plus'; + // put previous url in session if not redirect from store (not "create another"). + if (Session::get('piggy-banks.create.fromStore') !== true) { + Session::put('piggy-banks.create.url', URL::previous()); + } + Session::forget('piggy-banks.create.fromStore'); + return view('piggy-banks.create', compact('accounts', 'periods', 'subTitle', 'subTitleIcon')); } @@ -80,6 +84,9 @@ class PiggyBankController extends Controller { $subTitle = 'Delete "' . e($piggyBank->name) . '"'; + // put previous url in session + Session::put('piggy-banks.delete.url', URL::previous()); + return view('piggy-banks.delete', compact('piggyBank', 'subTitle')); } @@ -94,7 +101,7 @@ class PiggyBankController extends Controller Session::flash('success', 'Piggy bank "' . e($piggyBank->name) . '" deleted.'); $piggyBank->delete(); - return Redirect::route('piggy-banks.index'); + return Redirect::to(Session::get('piggy-banks.delete.url')); } /** @@ -132,6 +139,12 @@ class PiggyBankController extends Controller ]; Session::flash('preFilled', $preFilled); + // put previous url in session if not redirect from store (not "return_to_edit"). + if (Session::get('piggy-banks.edit.fromUpdate') !== true) { + Session::put('piggy-banks.edit.url', URL::previous()); + } + Session::forget('piggy-banks.edit.fromUpdate'); + return view('piggy-banks.edit', compact('subTitle', 'subTitleIcon', 'piggyBank', 'accounts', 'periods', 'preFilled')); } @@ -141,7 +154,7 @@ class PiggyBankController extends Controller public function index(AccountRepositoryInterface $repository) { /** @var Collection $piggyBanks */ - $piggyBanks = Auth::user()->piggyBanks()->where('repeats', 0)->orderBy('order', 'ASC')->get(); + $piggyBanks = Auth::user()->piggyBanks()->orderBy('order', 'ASC')->get(); $accounts = []; /** @var PiggyBank $piggyBank */ @@ -157,7 +170,7 @@ class PiggyBankController extends Controller if (!isset($accounts[$account->id])) { $accounts[$account->id] = [ 'name' => $account->name, - 'balance' => Steam::balance($account), + 'balance' => Steam::balance($account,null,true), 'leftForPiggyBanks' => $repository->leftOnAccount($account), 'sumOfSaved' => $piggyBank->savedSoFar, 'sumOfTargets' => floatval($piggyBank->targetamount), @@ -298,7 +311,6 @@ class PiggyBankController extends Controller public function store(PiggyBankFormRequest $request, PiggyBankRepositoryInterface $repository) { $piggyBankData = [ - 'repeats' => false, 'name' => $request->get('name'), 'startdate' => new Carbon, 'account_id' => intval($request->get('account_id')), @@ -313,11 +325,13 @@ class PiggyBankController extends Controller Session::flash('success', 'Stored piggy bank "' . e($piggyBank->name) . '".'); if (intval(Input::get('create_another')) === 1) { + Session::put('piggy-banks.create.fromStore', true); return Redirect::route('piggy-banks.create')->withInput(); } - return Redirect::route('piggy-banks.index'); + // redirect to previous URL. + return Redirect::to(Session::get('piggy-banks.create.url')); } /** @@ -330,7 +344,6 @@ class PiggyBankController extends Controller public function update(PiggyBank $piggyBank, PiggyBankRepositoryInterface $repository, PiggyBankFormRequest $request) { $piggyBankData = [ - 'repeats' => false, 'name' => $request->get('name'), 'startdate' => is_null($piggyBank->startdate) ? $piggyBank->created_at : $piggyBank->startdate, 'account_id' => intval($request->get('account_id')), @@ -346,11 +359,13 @@ class PiggyBankController extends Controller Session::flash('success', 'Updated piggy bank "' . e($piggyBank->name) . '".'); if (intval(Input::get('return_to_edit')) === 1) { + Session::put('piggy-banks.edit.fromUpdate', true); return Redirect::route('piggy-banks.edit', $piggyBank->id); } - return Redirect::route('piggy-banks.index'); + // redirect to previous URL. + return Redirect::to(Session::get('piggy-banks.edit.url')); } diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 1a6eef25ee..20f24d4d6b 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -1,18 +1,17 @@ query = $query; + $this->helper = $helper; + View::share('title', 'Reports'); View::share('mainTitleIcon', 'fa-line-chart'); @@ -38,7 +46,7 @@ class ReportController extends Controller * * @return \Illuminate\View\View */ - public function budget($year = '2014', $month = '1', ReportQueryInterface $query) + public function budget($year = '2014', $month = '1') { try { new Carbon($year . '-' . $month . '-01'); @@ -52,7 +60,7 @@ class ReportController extends Controller $end->endOfMonth(); $start->subDay(); - // shared accounts preference: + /** @var Preference $pref */ $pref = Preferences::get('showSharedReports', false); $showSharedReports = $pref->data; @@ -61,13 +69,13 @@ class ReportController extends Controller $subTitle = 'Budget report for ' . $date->format('F Y'); $subTitleIcon = 'fa-calendar'; $dayEarly = $dayEarly->subDay(); - $accounts = $query->getAllAccounts($start, $end, $showSharedReports); + $accounts = $this->query->getAllAccounts($start, $end, $showSharedReports); $start->addDay(); $accounts->each( - function (Account $account) use ($start, $end, $query) { - $budgets = $query->getBudgetSummary($account, $start, $end); - $balancedAmount = $query->balancedTransactionsSum($account, $start, $end); + function (Account $account) use ($start, $end) { + $budgets = $this->query->getBudgetSummary($account, $start, $end); + $balancedAmount = $this->query->balancedTransactionsSum($account, $start, $end); $array = []; $hide = true; foreach ($budgets as $budget) { @@ -85,35 +93,10 @@ class ReportController extends Controller } ); - $start = clone $date; - $start->startOfMonth(); - /** * Start getBudgetsForMonth DONE */ - $set = Auth::user()->budgets()->orderBy('budgets.name', 'ASC') - ->leftJoin( - 'budget_limits', function (JoinClause $join) use ($date) { - $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); - } - ) - ->get(['budgets.*', 'budget_limits.amount as amount']); - $budgets = Steam::makeArray($set); - $amountSet = $query->journalsByBudget($start, $end, $showSharedReports); - $amounts = Steam::makeArray($amountSet); - $budgets = Steam::mergeArrays($budgets, $amounts); - $budgets[0]['spent'] = isset($budgets[0]['spent']) ? $budgets[0]['spent'] : 0.0; - $budgets[0]['amount'] = isset($budgets[0]['amount']) ? $budgets[0]['amount'] : 0.0; - $budgets[0]['name'] = 'No budget'; - - // find transactions to shared asset accounts, which are without a budget by default: - // which is only relevant when shared asset accounts are hidden. - if ($showSharedReports === false) { - $transfers = $query->sharedExpenses($start, $end); - foreach ($transfers as $transfer) { - $budgets[0]['spent'] += floatval($transfer->amount) * -1; - } - } + $budgets = $this->helper->getBudgetsForMonth($date, $showSharedReports); /** * End getBudgetsForMonth DONE @@ -128,11 +111,11 @@ class ReportController extends Controller * * @return View */ - public function index(ReportHelperInterface $helper) + public function index() { $start = Session::get('first'); - $months = $helper->listOfMonths($start); - $years = $helper->listOfYears($start); + $months = $this->helper->listOfMonths($start); + $years = $this->helper->listOfYears($start); $title = 'Reports'; $mainTitleIcon = 'fa-line-chart'; @@ -146,7 +129,7 @@ class ReportController extends Controller * * @return \Illuminate\View\View */ - public function modalBalancedTransfers(Account $account, $year = '2014', $month = '1', ReportQueryInterface $query) + public function modalBalancedTransfers(Account $account, $year = '2014', $month = '1') { try { @@ -158,7 +141,7 @@ class ReportController extends Controller $end = clone $start; $end->endOfMonth(); - $journals = $query->balancedTransactionsList($account, $start, $end); + $journals = $this->query->balancedTransactionsList($account, $start, $end); return view('reports.modal-journal-list', compact('journals')); @@ -173,7 +156,7 @@ class ReportController extends Controller * * @return View */ - public function modalLeftUnbalanced(Account $account, $year = '2014', $month = '1', ReportQueryInterface $query) + public function modalLeftUnbalanced(Account $account, $year = '2014', $month = '1') { try { new Carbon($year . '-' . $month . '-01'); @@ -183,7 +166,7 @@ class ReportController extends Controller $start = new Carbon($year . '-' . $month . '-01'); $end = clone $start; $end->endOfMonth(); - $set = $query->getTransactionsWithoutBudget($account, $start, $end); + $set = $this->query->getTransactionsWithoutBudget($account, $start, $end); $journals = $set->filter( function (TransactionJournal $journal) { @@ -204,7 +187,7 @@ class ReportController extends Controller * * @return \Illuminate\View\View */ - public function modalNoBudget(Account $account, $year = '2014', $month = '1', ReportQueryInterface $query) + public function modalNoBudget(Account $account, $year = '2014', $month = '1') { try { new Carbon($year . '-' . $month . '-01'); @@ -214,7 +197,7 @@ class ReportController extends Controller $start = new Carbon($year . '-' . $month . '-01'); $end = clone $start; $end->endOfMonth(); - $journals = $query->getTransactionsWithoutBudget($account, $start, $end); + $journals = $this->query->getTransactionsWithoutBudget($account, $start, $end); return view('reports.modal-journal-list', compact('journals')); @@ -226,7 +209,7 @@ class ReportController extends Controller * * @return \Illuminate\View\View */ - public function month($year = '2014', $month = '1', ReportQueryInterface $query) + public function month($year = '2014', $month = '1') { try { new Carbon($year . '-' . $month . '-01'); @@ -237,7 +220,7 @@ class ReportController extends Controller $subTitle = 'Report for ' . $date->format('F Y'); $subTitleIcon = 'fa-calendar'; $displaySum = true; // to show sums in report. - + /** @var Preference $pref */ $pref = Preferences::get('showSharedReports', false); $showSharedReports = $pref->data; @@ -256,14 +239,15 @@ class ReportController extends Controller /** * Start getIncomeForMonth DONE */ - $income = $query->incomeByPeriod($start, $end, $showSharedReports); + $income = $this->query->incomeByPeriod($start, $end, $showSharedReports); /** * End getIncomeForMonth DONE */ /** * Start getExpenseGroupedForMonth DONE */ - $set = $query->journalsByExpenseAccount($start, $end, $showSharedReports); + $set = $this->query->journalsByExpenseAccount($start, $end, $showSharedReports); + $expenses = Steam::makeArray($set); $expenses = Steam::sortArray($expenses); $expenses = Steam::limitArray($expenses, 10); @@ -273,28 +257,7 @@ class ReportController extends Controller /** * Start getBudgetsForMonth DONE */ - $set = Auth::user()->budgets() - ->leftJoin( - 'budget_limits', function (JoinClause $join) use ($date) { - $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); - } - ) - ->get(['budgets.*', 'budget_limits.amount as amount']); - $budgets = Steam::makeArray($set); - $amountSet = $query->journalsByBudget($start, $end, $showSharedReports); - $amounts = Steam::makeArray($amountSet); - $budgets = Steam::mergeArrays($budgets, $amounts); - $budgets[0]['spent'] = isset($budgets[0]['spent']) ? $budgets[0]['spent'] : 0.0; - $budgets[0]['amount'] = isset($budgets[0]['amount']) ? $budgets[0]['amount'] : 0.0; - $budgets[0]['name'] = 'No budget'; - - // find transactions to shared expense accounts, which are without a budget by default: - if ($showSharedReports === false) { - $transfers = $query->sharedExpenses($start, $end); - foreach ($transfers as $transfer) { - $budgets[0]['spent'] += floatval($transfer->amount) * -1; - } - } + $budgets = $this->helper->getBudgetsForMonth($date, $showSharedReports); /** * End getBudgetsForMonth DONE @@ -303,18 +266,20 @@ class ReportController extends Controller * Start getCategoriesForMonth DONE */ // all categories. - $result = $query->journalsByCategory($start, $end); + $result = $this->query->journalsByCategory($start, $end); $categories = Steam::makeArray($result); + // all transfers if ($showSharedReports === false) { - $result = $query->sharedExpensesByCategory($start, $end); + $result = $this->query->sharedExpensesByCategory($start, $end); $transfers = Steam::makeArray($result); $merged = Steam::mergeArrays($categories, $transfers); } else { $merged = $categories; } + // sort. $sorted = Steam::sortNegativeArray($merged); @@ -326,7 +291,7 @@ class ReportController extends Controller /** * Start getAccountsForMonth */ - $list = $query->accountList($showSharedReports); + $list = $this->query->accountList($showSharedReports); $accounts = []; /** @var Account $account */ foreach ($list as $account) { @@ -360,7 +325,7 @@ class ReportController extends Controller * * @return $this */ - public function year($year, ReportHelperInterface $helper, ReportQueryInterface $query) + public function year($year) { try { new Carbon('01-01-' . $year); @@ -377,9 +342,9 @@ class ReportController extends Controller $subTitle = $year; $subTitleIcon = 'fa-bar-chart'; $mainTitleIcon = 'fa-line-chart'; - $balances = $helper->yearBalanceReport($date, $showSharedReports); - $groupedIncomes = $query->journalsByRevenueAccount($date, $end, $showSharedReports); - $groupedExpenses = $query->journalsByExpenseAccount($date, $end, $showSharedReports); + $balances = $this->helper->yearBalanceReport($date, $showSharedReports); + $groupedIncomes = $this->query->journalsByRevenueAccount($date, $end, $showSharedReports); + $groupedExpenses = $this->query->journalsByExpenseAccount($date, $end, $showSharedReports); return view( 'reports.year', compact('date', 'groupedIncomes', 'groupedExpenses', 'year', 'balances', 'title', 'subTitle', 'subTitleIcon', 'mainTitleIcon') diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 56054d859e..e8300109ad 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -1,7 +1,6 @@ transactionType->type); $subTitle = 'Delete ' . e($type) . ' "' . e($journal->description) . '"'; + // put previous url in session + Session::put('transactions.delete.url', URL::previous()); + return View::make('transactions.delete', compact('journal', 'subTitle')); @@ -91,23 +100,12 @@ class TransactionController extends Controller */ public function destroy(TransactionJournal $transactionJournal) { - $type = $transactionJournal->transactionType->type; - $return = 'withdrawal'; - Session::flash('success', 'Transaction "' . e($transactionJournal->description) . '" destroyed.'); $transactionJournal->delete(); - switch ($type) { - case 'Deposit': - $return = 'deposit'; - break; - case 'Transfer': - $return = 'transfers'; - break; - } - - return Redirect::route('transactions.index', $return); + // redirect to previous URL: + return Redirect::to(Session::get('transactions.delete.url')); } /** @@ -164,6 +162,12 @@ class TransactionController extends Controller $preFilled['account_from_id'] = $transactions[1]->account->id; $preFilled['account_to_id'] = $transactions[0]->account->id; + // put previous url in session if not redirect from store (not "return_to_edit"). + if (Session::get('transactions.edit.fromUpdate') !== true) { + Session::put('transactions.edit.url', URL::previous()); + } + Session::forget('transactions.edit.fromUpdate'); + return View::make('transactions.edit', compact('journal', 'accounts', 'what', 'budgets', 'piggies', 'subTitle'))->with('data', $preFilled); } @@ -199,16 +203,13 @@ class TransactionController extends Controller $page = intval(\Input::get('page')); $offset = $page > 0 ? ($page - 1) * 50 : 0; - $set = Auth::user()-> - transactionJournals()-> - transactionTypes($types)-> - withRelevantData()->take(50)->offset($offset) - ->orderBy('date', 'DESC') - ->orderBy('order','ASC') - ->orderBy('id','DESC') - ->get( - ['transaction_journals.*'] - ); + $set = Auth::user()->transactionJournals()->transactionTypes($types)->withRelevantData()->take(50)->offset($offset) + ->orderBy('date', 'DESC') + ->orderBy('order', 'ASC') + ->orderBy('id', 'DESC') + ->get( + ['transaction_journals.*'] + ); $count = Auth::user()->transactionJournals()->transactionTypes($types)->count(); $journals = new LengthAwarePaginator($set, $count, 50, $page); $journals->setPath('transactions/' . $what); @@ -227,13 +228,14 @@ class TransactionController extends Controller $order = 0; foreach ($ids as $id) { $journal = Auth::user()->transactionjournals()->where('id', $id)->where('date', Input::get('date'))->first(); - if($journal) { + if ($journal) { $journal->order = $order; $order++; $journal->save(); } } } + return Response::json(true); } @@ -251,10 +253,10 @@ class TransactionController extends Controller $t->account->transactions()->leftJoin( 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' ) - ->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d')) - ->where('transaction_journals.order','>=',$journal->order) - ->where('transaction_journals.id', '!=', $journal->id) - ->sum('transactions.amount') + ->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d')) + ->where('transaction_journals.order', '>=', $journal->order) + ->where('transaction_journals.id', '!=', $journal->id) + ->sum('transactions.amount') ); $t->after = $t->before + $t->amount; } @@ -274,25 +276,13 @@ class TransactionController extends Controller */ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) { - $journalData = [ - 'what' => $request->get('what'), - 'description' => $request->get('description'), - 'account_id' => intval($request->get('account_id')), - 'account_from_id' => intval($request->get('account_from_id')), - 'account_to_id' => intval($request->get('account_to_id')), - 'expense_account' => $request->get('expense_account'), - 'revenue_account' => $request->get('revenue_account'), - 'amount' => floatval($request->get('amount')), - 'user' => Auth::user()->id, - 'amount_currency_id' => intval($request->get('amount_currency_id')), - 'date' => new Carbon($request->get('date')), - 'budget_id' => intval($request->get('budget_id')), - 'category' => $request->get('category'), - ]; - $journal = $repository->store($journalData); + $journalData = $request->getJournalData(); + $journal = $repository->store($journalData); + // rescan journal, UpdateJournalConnection event(new JournalSaved($journal)); + // ConnectJournalToPiggyBank event(new JournalCreated($journal, intval($request->get('piggy_bank_id')))); if (intval($request->get('reminder_id')) > 0) { @@ -304,10 +294,14 @@ class TransactionController extends Controller Session::flash('success', 'New transaction "' . $journal->description . '" stored!'); if (intval(Input::get('create_another')) === 1) { + // set value so create routine will not overwrite URL: + Session::put('transactions.create.fromStore', true); + return Redirect::route('transactions.create', $request->input('what'))->withInput(); } - return Redirect::route('transactions.index', $request->input('what')); + // redirect to previous URL. + return Redirect::to(Session::get('transactions.create.url')); } @@ -321,23 +315,7 @@ class TransactionController extends Controller public function update(TransactionJournal $journal, JournalFormRequest $request, JournalRepositoryInterface $repository) { - - $journalData = [ - 'what' => $request->get('what'), - 'description' => $request->get('description'), - 'account_id' => intval($request->get('account_id')), - 'account_from_id' => intval($request->get('account_from_id')), - 'account_to_id' => intval($request->get('account_to_id')), - 'expense_account' => $request->get('expense_account'), - 'revenue_account' => $request->get('revenue_account'), - 'amount' => floatval($request->get('amount')), - 'user' => Auth::user()->id, - 'amount_currency_id' => intval($request->get('amount_currency_id')), - 'date' => new Carbon($request->get('date')), - 'budget_id' => intval($request->get('budget_id')), - 'category' => $request->get('category'), - ]; - + $journalData = $request->getJournalData(); $repository->update($journal, $journalData); event(new JournalSaved($journal)); @@ -346,11 +324,14 @@ class TransactionController extends Controller Session::flash('success', 'Transaction "' . e($journalData['description']) . '" updated.'); if (intval(Input::get('return_to_edit')) === 1) { - return Redirect::route('transactions.edit', $journal->id); + // set value so edit routine will not overwrite URL: + Session::put('transactions.edit.fromUpdate', true); + + return Redirect::route('transactions.edit', $journal->id)->withInput(['return_to_edit' => 1]); } - - return Redirect::route('transactions.index', $journalData['what']); + // redirect to previous URL. + return Redirect::to(Session::get('transactions.edit.url')); } diff --git a/app/Http/Middleware/PiggyBanks.php b/app/Http/Middleware/PiggyBanks.php index 92ac858ba4..0854d338a8 100644 --- a/app/Http/Middleware/PiggyBanks.php +++ b/app/Http/Middleware/PiggyBanks.php @@ -13,7 +13,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Collection; use Navigation; use Session; - +use App; /** * Class PiggyBanks @@ -50,12 +50,11 @@ class PiggyBanks */ public function handle(Request $request, Closure $next) { - if ($this->auth->check() && !$request->isXmlHttpRequest()) { + if ($this->auth->check() && !$request->isXmlHttpRequest() && App::environment() != 'testing') { // get piggy banks without a repetition: /** @var Collection $set */ $set = $this->auth->user()->piggybanks() ->leftJoin('piggy_bank_repetitions', 'piggy_banks.id', '=', 'piggy_bank_repetitions.piggy_bank_id') - ->where('piggy_banks.repeats', 0) ->whereNull('piggy_bank_repetitions.id') ->get(['piggy_banks.id', 'piggy_banks.startdate', 'piggy_banks.targetdate']); if ($set->count() > 0) { @@ -70,68 +69,6 @@ class PiggyBanks } } unset($partialPiggy, $set, $repetition); - - // get repeating piggy banks without a repetition for current time frame. - /** @var Collection $set */ - $set = $this->auth->user()->piggybanks()->leftJoin( - 'piggy_bank_repetitions', function (JoinClause $join) { - $join->on('piggy_bank_repetitions.piggy_bank_id', '=', 'piggy_banks.id') - ->where('piggy_bank_repetitions.targetdate', '>=', Session::get('start')->format('Y-m-d')) - ->where('piggy_bank_repetitions.startdate', '<=', Session::get('end')->format('Y-m-d')); - } - ) - ->where('repeats', 1) - ->whereNull('piggy_bank_repetitions.id') - ->get(['piggy_banks.*']); - - // these piggy banks are missing a repetition. start looping and create them! - if ($set->count() > 0) { - /** @var PiggyBank $piggyBank */ - foreach ($set as $piggyBank) { - $start = clone $piggyBank->startdate; - $end = clone $piggyBank->targetdate; - $max = clone $piggyBank->targetdate; - - // first loop: start date to target date. - // then, continue looping until end is > today - while ($start <= $max) { - // first loop fixes this date. or should fix it. - $max = new Carbon; - - echo '[#'.$piggyBank->id.', from: '.$start->format('Y-m-d.').' to '.$end->format('Y-m-d.').']'; - // create stuff. Or at least, try: - $repetition = $piggyBank->piggyBankRepetitions()->onDates($start, $end)->first(); - if(!$repetition) { - $repetition = new PiggyBankRepetition; - $repetition->piggyBank()->associate($piggyBank); - $repetition->startdate = $start; - $repetition->targetdate = $end; - $repetition->currentamount = 0; - // it might exist, catch: - $repetition->save(); - } - - // start where end 'ended': - $start = clone $end; - // move end. - $end = Navigation::addPeriod($end, $piggyBank->rep_length, 0); - - } - - - // first repetition: from original start to original target. - $repetition = new PiggyBankRepetition; - $repetition->piggyBank()->associate($piggyBank); - $repetition->startdate = is_null($piggyBank->startdate) ? null : $piggyBank->startdate; - $repetition->targetdate = is_null($piggyBank->targetdate) ? null : $piggyBank->targetdate; - $repetition->currentamount = 0; - // it might exist, catch: - - // then, loop from original target up to now. - } - } - - } return $next($request); diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index 7a5f212bc5..7407e4a7d2 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -3,6 +3,7 @@ namespace FireflyIII\Http\Middleware; +use App; use Carbon\Carbon; use Closure; use Illuminate\Contracts\Auth\Guard; @@ -47,14 +48,14 @@ class Range */ public function handle(Request $request, Closure $theNext) { - if ($this->auth->check()) { + if ($this->auth->check() && App::environment() != 'testing') { // ignore preference. set the range to be the current month: if (!Session::has('start') && !Session::has('end')) { /** @var \FireflyIII\Models\Preference $viewRange */ $viewRange = Preferences::get('viewRange', '1M'); - $start = Session::has('start') ? Session::get('start') : new Carbon; + $start = new Carbon; $start = Navigation::updateStartDate($viewRange->data, $start); $end = Navigation::updateEndDate($viewRange->data, $start); @@ -62,11 +63,16 @@ class Range Session::put('end', $end); } if (!Session::has('first')) { - $journal = $this->auth->user()->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); + /** + * Get helper thing. + */ + /** @var \FireflyIII\Repositories\Journal\JournalRepositoryInterface $repository */ + $repository = App::make('FireflyIII\Repositories\Journal\JournalRepositoryInterface'); + $journal = $repository->first(); if ($journal) { Session::put('first', $journal->date); } else { - Session::put('first', Carbon::now()); + Session::put('first', Carbon::now()->startOfYear()); } } diff --git a/app/Http/Middleware/Reminders.php b/app/Http/Middleware/Reminders.php index 55c555aec0..457e4e2543 100644 --- a/app/Http/Middleware/Reminders.php +++ b/app/Http/Middleware/Reminders.php @@ -46,7 +46,7 @@ class Reminders */ public function handle(Request $request, Closure $next) { - if ($this->auth->check() && !$request->isXmlHttpRequest()) { + if ($this->auth->check() && !$request->isXmlHttpRequest() && App::environment() != 'testing') { // do reminders stuff. $piggyBanks = $this->auth->user()->piggyBanks()->where('remind_me', 1)->get(); $today = new Carbon; diff --git a/app/Http/Middleware/ReplaceTestVars.php b/app/Http/Middleware/ReplaceTestVars.php index ad35519a3e..bc75e07f7c 100644 --- a/app/Http/Middleware/ReplaceTestVars.php +++ b/app/Http/Middleware/ReplaceTestVars.php @@ -46,7 +46,7 @@ class ReplaceTestVars $input = $request->all(); $input['_token'] = $request->session()->token(); // we need to update _token value to make sure we get the POST / PUT tests passed. - Log::debug('Input token replaced ('.$input['_token'].').'); + Log::debug('Input token replaced (' . $input['_token'] . ').'); $request->replace($input); } diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 3ab4a26faf..f5c03fd7d7 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -28,22 +28,29 @@ class AccountFormRequest extends Request */ public function rules() { - $accountRoles = join(',', array_keys(Config::get('firefly.accountRoles'))); - $types = join(',', array_keys(Config::get('firefly.subTitlesByIdentifier'))); + $accountRoles = join(',', array_keys(Config::get('firefly.accountRoles'))); + $types = join(',', array_keys(Config::get('firefly.subTitlesByIdentifier'))); + $ccPaymentTypes = join(',', array_keys(Config::get('firefly.ccTypes'))); $nameRule = 'required|between:1,100|uniqueAccountForUser'; + $idRule = ''; if (Account::find(Input::get('id'))) { - $nameRule = 'required|between:1,100|belongsToUser:accounts|uniqueForUser:'.Input::get('id'); + $idRule = 'belongsToUser:accounts'; + $nameRule = 'required|between:1,100|uniqueAccountForUser:' . Input::get('id'); } return [ - 'name' => $nameRule, - 'openingBalance' => 'numeric', - 'openingBalanceDate' => 'date', - 'accountRole' => 'in:' . $accountRoles, - 'active' => 'boolean', - 'balance_currency_id' => 'exists:transaction_currencies,id', - 'what' => 'in:' . $types + 'id' => $idRule, + 'name' => $nameRule, + 'openingBalance' => 'numeric', + 'virtualBalance' => 'numeric', + 'openingBalanceDate' => 'date', + 'accountRole' => 'in:' . $accountRoles, + 'active' => 'boolean', + 'ccType' => 'in:' . $ccPaymentTypes, + 'ccMonthlyPaymentDate' => 'date', + 'balance_currency_id' => 'exists:transaction_currencies,id', + 'what' => 'in:' . $types ]; } } diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index b3057bc885..49dc166af6 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -3,6 +3,7 @@ namespace FireflyIII\Http\Requests; use Auth; +use Carbon\Carbon; use Input; /** @@ -21,19 +22,41 @@ class BillFormRequest extends Request return Auth::check(); } + /** + * @return array + */ + public function getBillData() + { + return [ + 'name' => $this->get('name'), + 'match' => $this->get('match'), + 'amount_min' => floatval($this->get('amount_min')), + 'amount_currency_id' => floatval($this->get('amount_currency_id')), + 'amount_max' => floatval($this->get('amount_max')), + 'date' => new Carbon($this->get('date')), + 'user' => Auth::user()->id, + 'repeat_freq' => $this->get('repeat_freq'), + 'skip' => intval($this->get('skip')), + 'automatch' => intval($this->get('automatch')) === 1, + 'active' => intval($this->get('active')) === 1, + ]; + } + /** * @return array */ public function rules() { - $nameRule = 'required|between:1,255|uniqueForUser:bills,name'; + $nameRule = 'required|between:1,255|uniqueObjectForUser:bills,name,name_encrypted'; + $matchRule = 'required|between:1,255|uniqueObjectForUser:bills,match,match_encrypted'; if (intval(Input::get('id')) > 0) { - $nameRule = 'required|between:1,255'; + $nameRule .= ',' . intval(Input::get('id')); + $matchRule .= ',' . intval(Input::get('id')); } $rules = [ 'name' => $nameRule, - 'match' => 'required|between:1,255', + 'match' => $matchRule, 'amount_min' => 'required|numeric|min:0.01', 'amount_max' => 'required|numeric|min:0.01', 'amount_currency_id' => 'required|exists:transaction_currencies,id', diff --git a/app/Http/Requests/BudgetFormRequest.php b/app/Http/Requests/BudgetFormRequest.php index 19c3924417..57cd60d20f 100644 --- a/app/Http/Requests/BudgetFormRequest.php +++ b/app/Http/Requests/BudgetFormRequest.php @@ -28,13 +28,14 @@ class BudgetFormRequest extends Request public function rules() { - $nameRule = 'required|between:1,100|uniqueForUser:budgets,name'; + $nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name,encrypted'; if (Budget::find(Input::get('id'))) { - $nameRule = 'required|between:1,100'; + $nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name,encrypted,'.intval(Input::get('id')); } return [ - 'name' => $nameRule, + 'name' => $nameRule, + 'active' => 'numeric|between:0,1' ]; } } diff --git a/app/Http/Requests/CategoryFormRequest.php b/app/Http/Requests/CategoryFormRequest.php index 7b9e577dc0..4a074750bd 100644 --- a/app/Http/Requests/CategoryFormRequest.php +++ b/app/Http/Requests/CategoryFormRequest.php @@ -28,9 +28,9 @@ class CategoryFormRequest extends Request public function rules() { - $nameRule = 'required|between:1,100|uniqueForUser:categories,name'; + $nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,encrypted'; if (Category::find(Input::get('id'))) { - $nameRule = 'required|between:1,100'; + $nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,encrypted,'.intval(Input::get('id')); } return [ diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 82b8f8549e..b0da081e4a 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -3,8 +3,10 @@ namespace FireflyIII\Http\Requests; use Auth; -use Input; +use Carbon\Carbon; use Exception; +use Input; + /** * Class JournalFormRequest * @@ -21,6 +23,28 @@ class JournalFormRequest extends Request return Auth::check(); } + /** + * @return array + */ + public function getJournalData() + { + return [ + 'what' => $this->get('what'), + 'description' => $this->get('description'), + 'account_id' => intval($this->get('account_id')), + 'account_from_id' => intval($this->get('account_from_id')), + 'account_to_id' => intval($this->get('account_to_id')), + 'expense_account' => $this->get('expense_account'), + 'revenue_account' => $this->get('revenue_account'), + 'amount' => floatval($this->get('amount')), + 'user' => Auth::user()->id, + 'amount_currency_id' => intval($this->get('amount_currency_id')), + 'date' => new Carbon($this->get('date')), + 'budget_id' => intval($this->get('budget_id')), + 'category' => $this->get('category'), + ]; + } + /** * @return array * @throws Exception diff --git a/app/Http/Requests/PiggyBankFormRequest.php b/app/Http/Requests/PiggyBankFormRequest.php index 795246c1b8..2e950b8019 100644 --- a/app/Http/Requests/PiggyBankFormRequest.php +++ b/app/Http/Requests/PiggyBankFormRequest.php @@ -29,33 +29,20 @@ class PiggyBankFormRequest extends Request public function rules() { - $nameRule = 'required|between:1,255|uniquePiggyBankForUser:piggy_banks,name'; + $nameRule = 'required|between:1,255|uniquePiggyBankForUser'; $targetDateRule = 'date'; if (intval(Input::get('id'))) { - $nameRule = 'required|between:1,255'; - } - - if (intval(Input::get('repeats')) == 1) { - $targetDateRule = 'required|date|after:' . date('Y-m-d'); - // switch on rep_every, make sure it's not too far away. - if (!is_null(Input::get('rep_length'))) { - $end = Navigation::addPeriod(new Carbon, Input::get('rep_length'), 0); - $targetDateRule .= '|before:' . $end->format('Y-m-d'); - } + $nameRule = 'required|between:1,255|uniquePiggyBankForUser:'.intval(Input::get('id')); } $rules = [ - 'repeats' => 'required|boolean', 'name' => $nameRule, 'account_id' => 'required|belongsToUser:accounts', 'targetamount' => 'required|min:0.01', 'amount_currency_id' => 'exists:transaction_currencies,id', 'startdate' => 'date', 'targetdate' => $targetDateRule, - 'rep_length' => 'in:day,week,quarter,month,year', - 'rep_every' => 'integer|min:0|max:31', - 'rep_times' => 'integer|min:0|max:99', 'reminder' => 'in:day,week,quarter,month,year', 'reminder_skip' => 'integer|min:0|max:99', 'remind_me' => 'boolean|piggyBankReminder', diff --git a/app/Http/Requests/ProfileFormRequest.php b/app/Http/Requests/ProfileFormRequest.php index 0fc12e3377..8bac377a5b 100644 --- a/app/Http/Requests/ProfileFormRequest.php +++ b/app/Http/Requests/ProfileFormRequest.php @@ -26,7 +26,7 @@ class ProfileFormRequest extends Request public function rules() { return [ - 'current_password' => 'required', + 'current_password' => 'required', 'new_password' => 'required|confirmed', 'new_password_confirmation' => 'required', ]; diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 4ec8165e8c..cdb6a5226e 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -3,13 +3,13 @@ use Carbon\Carbon; use DaveJamesMiller\Breadcrumbs\Generator; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; -use FireflyIII\Models\Budget; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; use FireflyIII\Models\Category; -use FireflyIII\Models\Reminder; use FireflyIII\Models\LimitRepetition; use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Reminder; +use FireflyIII\Models\TransactionJournal; /* * Back home. diff --git a/app/Http/routes.php b/app/Http/routes.php index 89e5e5bd1b..3b6a5bc63f 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -107,7 +107,7 @@ Route::bind( where('piggy_banks.id', $value) ->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id') ->where('accounts.user_id', Auth::user()->id) - ->where('repeats', 0)->first(['piggy_banks.*']); + ->first(['piggy_banks.*']); } return null; @@ -132,7 +132,7 @@ Route::get('/register', ['uses' => 'Auth\AuthController@getRegister', 'as' => 'r Route::controllers( [ - 'auth' => 'Auth\AuthController', + 'auth' => 'Auth\AuthController', 'password' => 'Auth\PasswordController', ] ); @@ -142,7 +142,7 @@ Route::controllers( * Home Controller */ Route::group( - ['middleware' => ['auth', 'range', 'reminders','piggybanks']], function () { + ['middleware' => ['auth', 'range', 'reminders', 'piggybanks']], function () { Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']); Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']); Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']); @@ -167,6 +167,7 @@ Route::group( Route::get('/bills/rescan/{bill}', ['uses' => 'BillController@rescan', 'as' => 'bills.rescan']); # rescan for matching. Route::get('/bills/create', ['uses' => 'BillController@create', 'as' => 'bills.create']); Route::get('/bills/edit/{bill}', ['uses' => 'BillController@edit', 'as' => 'bills.edit']); + Route::get('/bills/add/{bill}', ['uses' => 'BillController@add', 'as' => 'bills.add']); Route::get('/bills/delete/{bill}', ['uses' => 'BillController@delete', 'as' => 'bills.delete']); Route::get('/bills/show/{bill}', ['uses' => 'BillController@show', 'as' => 'bills.show']); diff --git a/app/Models/Account.php b/app/Models/Account.php index c6c8816423..d366a85c14 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -1,10 +1,13 @@ 'required|exists:users,id', 'account_type_id' => 'required|exists:account_types,id', 'name' => 'required|between:1,1024|uniqueAccountForUser', 'active' => 'required|boolean' ]; - protected $fillable = ['user_id', 'account_type_id', 'name', 'active']; - /** - * @param $fieldName + * @param array $fields * - * @return string|null + * @return Account|null */ - public function getMeta($fieldName) + public static function firstOrCreateEncrypted(array $fields) { - foreach ($this->accountMeta as $meta) { - if ($meta->name == $fieldName) { - return $meta->data; + // everything but the name: + $query = Account::orderBy('id'); + foreach ($fields as $name => $value) { + if ($name != 'name') { + $query->where($name, $value); } } + $set = $query->get(['accounts.*']); + /** @var Account $account */ + foreach ($set as $account) { + if ($account->name == $fields['name']) { + return $account; + } + } + // create it! + $account = Account::create($fields); + if (is_null($account->id)) { + // could not create account: + App::abort(500, 'Could not create new account with data: ' . json_encode($fields)); - return null; - - } - - /** - * @param $value - * - * @return string - */ - public function getNameAttribute($value) - { - if ($this->encrypted) { - return Crypt::decrypt($value); } - // @codeCoverageIgnoreStart - return $value; - // @codeCoverageIgnoreEnd } /** @@ -81,6 +81,48 @@ class Account extends Model return ['created_at', 'updated_at', 'deleted_at']; } + /** + * @param $fieldName + * + * @return string|null + */ + public function getMeta($fieldName) + { + foreach ($this->accountMeta as $meta) { + if ($meta->name == $fieldName) { + return $meta->data; + } + } + + return null; + + } + + /** + * @param $value + * + * @return string + */ + public function getNameAttribute($value) + { + + if (intval($this->encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function piggyBanks() + { + return $this->hasMany('FireflyIII\Models\PiggyBank'); + } + /** * @param EloquentBuilder $query * @param array $types @@ -94,6 +136,31 @@ class Account extends Model $query->whereIn('account_types.type', $types); } + /** + * @param EloquentBuilder $query + * @param string $name + * @param string $value + */ + public function scopeHasMetaValue(EloquentBuilder $query, $name, $value) + { + $joinName = str_replace('.', '_', $name); + $query->leftJoin( + 'account_meta as ' . $joinName, function (JoinClause $join) use ($joinName, $name) { + $join->on($joinName . '.account_id', '=', 'accounts.id')->where($joinName . '.name', '=', $name); + } + ); + $query->where($joinName . '.data', json_encode($value)); + } + + /** + * @param $value + */ + public function setNameAttribute($value) + { + $this->attributes['name'] = Crypt::encrypt($value); + $this->attributes['encrypted'] = true; + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ @@ -110,12 +177,4 @@ class Account extends Model return $this->belongsTo('FireflyIII\User'); } - /** - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function piggyBanks() - { - return $this->hasMany('FireflyIII\Models\PiggyBank'); - } - } diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 1aba876ce2..16e6f7a91b 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -14,12 +14,12 @@ class AccountMeta extends Model use ValidatingTrait; protected $fillable = ['account_id', 'name', 'data']; protected $rules - = [ + = [ 'account_id' => 'required|exists:accounts,id', 'name' => 'required|between:1,100', 'data' => 'required' ]; - protected $table = 'account_meta'; + protected $table = 'account_meta'; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 7cdcc2e67c..d181e616c3 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -1,5 +1,6 @@ match_encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } + + /** + * @param $value + * + * @return string + */ + public function getNameAttribute($value) + { + + if (intval($this->name_encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } + + /** + * @param $value + */ + public function setMatchAttribute($value) + { + $this->attributes['match'] = Crypt::encrypt($value); + $this->attributes['match_encrypted'] = true; + } + + /** + * @param $value + */ + public function setNameAttribute($value) + { + $this->attributes['name'] = Crypt::encrypt($value); + $this->attributes['name_encrypted'] = true; + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Models/Budget.php b/app/Models/Budget.php index b07c11b830..63a7e3979b 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -1,5 +1,6 @@ encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ @@ -39,6 +57,15 @@ class Budget extends Model return $this->hasManyThrough('FireflyIII\Models\LimitRepetition', 'FireflyIII\Models\BudgetLimit', 'budget_id'); } + /** + * @param $value + */ + public function setNameAttribute($value) + { + $this->attributes['name'] = Crypt::encrypt($value); + $this->attributes['encrypted'] = true; + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ diff --git a/app/Models/Category.php b/app/Models/Category.php index 37f12c7ca8..539f889378 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -2,7 +2,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; - +use Crypt; /** * Class Category * @@ -38,4 +38,29 @@ class Category extends Model return $this->belongsTo('FireflyIII\User'); } + /** + * @param $value + */ + public function setNameAttribute($value) + { + $this->attributes['name'] = Crypt::encrypt($value); + $this->attributes['encrypted'] = true; + } + /** + * @param $value + * + * @return string + */ + public function getNameAttribute($value) + { + + if (intval($this->encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } + } diff --git a/app/Models/Component.php b/app/Models/Component.php index 7a9bba141b..a17450943d 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -12,7 +12,7 @@ class Component extends Model { use SoftDeletes; - protected $fillable = ['user_id', 'name','class']; + protected $fillable = ['user_id', 'name', 'class']; /** * @return array diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index ad1b4fd672..c81ef28b71 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -1,11 +1,8 @@ currentRep; } // repeating piggy banks are no longer supported. - if (intval($this->repeats) === 0) { - $rep = $this->piggyBankRepetitions()->first(['piggy_bank_repetitions.*']); - $this->currentRep = $rep; + $rep = $this->piggyBankRepetitions()->first(['piggy_bank_repetitions.*']); + $this->currentRep = $rep; - return $rep; - } else { - Log::error('Tried to work with a piggy bank with a repeats=1 value! (id is '.$this->id.')'); - } + return $rep; } @@ -91,4 +83,29 @@ class PiggyBank extends Model { return $this->morphMany('FireflyIII\Models\Reminder', 'remindersable'); } + + /** + * @param $value + */ + public function setNameAttribute($value) + { + $this->attributes['name'] = Crypt::encrypt($value); + $this->attributes['encrypted'] = true; + } + /** + * @param $value + * + * @return string + */ + public function getNameAttribute($value) + { + + if (intval($this->encrypted) == 1) { + return Crypt::decrypt($value); + } + + // @codeCoverageIgnoreStart + return $value; + // @codeCoverageIgnoreEnd + } } diff --git a/app/Models/Reminder.php b/app/Models/Reminder.php index 7a39b59a54..62c053d765 100644 --- a/app/Models/Reminder.php +++ b/app/Models/Reminder.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; - +use Crypt; /** * Class Reminder * @@ -40,6 +40,9 @@ class Reminder extends Model */ public function getMetadataAttribute($value) { + if (intval($this->encrypted) == 1) { + return json_decode(Crypt::decrypt($value)); + } return json_decode($value); } @@ -86,7 +89,8 @@ class Reminder extends Model */ public function setMetadataAttribute($value) { - $this->attributes['metadata'] = json_encode($value); + $this->attributes['encrypted'] = true; + $this->attributes['metadata'] = Crypt::encrypt(json_encode($value)); } /** diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 90af507dae..ab1e04c0be 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -3,7 +3,8 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Watson\Validating\ValidatingTrait; - +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use Carbon\Carbon; /** * Class Transaction * @@ -30,6 +31,28 @@ class Transaction extends Model return $this->belongsTo('FireflyIII\Models\Account'); } + /** + * @param EloquentBuilder $query + * @param Carbon $date + * + * @return mixed + */ + public function scopeAfter(EloquentBuilder $query, Carbon $date) + { + return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d 00:00:00')); + } + + /** + * @param EloquentBuilder $query + * @param Carbon $date + * + * @return mixed + */ + public function scopeBefore(EloquentBuilder $query, Carbon $date) + { + return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); + } + /** * @return array */ diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index c92888c235..2b41702c09 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -12,7 +12,7 @@ use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; use Illuminate\Database\QueryException; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Log; - +use Reminder; /** * Class EventServiceProvider * @@ -28,7 +28,7 @@ class EventServiceProvider extends ServiceProvider */ protected $listen = [ - 'FireflyIII\Events\JournalSaved' => [ + 'FireflyIII\Events\JournalSaved' => [ 'FireflyIII\Handlers\Events\RescanJournal', 'FireflyIII\Handlers\Events\UpdateJournalConnection', @@ -60,6 +60,14 @@ class EventServiceProvider extends ServiceProvider ); + PiggyBank::deleting(function(PiggyBank $piggyBank) { + $reminders = $piggyBank->reminders()->get(); + /** @var Reminder $reminder */ + foreach($reminders as $reminder) { + $reminder->delete(); + } + }); + Account::deleted( function (Account $account) { diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 6ea2ec97a8..4d580a848e 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -48,6 +48,18 @@ class AccountRepository implements AccountRepositoryInterface return true; } + /** + * @param TransactionJournal $journal + * @param Account $account + * + * @return Transaction + */ + public function getFirstTransaction(TransactionJournal $journal, Account $account) + { + + return $journal->transactions()->where('account_id', $account->id)->first(); + } + /** * @param Preference $preference * @@ -106,9 +118,9 @@ class AccountRepository implements AccountRepositoryInterface ->withRelevantData() ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('transactions.account_id', $account->id) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC'); + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC'); $query->before(Session::get('end', Carbon::now()->endOfMonth())); $query->after(Session::get('start', Carbon::now()->startOfMonth())); @@ -119,7 +131,6 @@ class AccountRepository implements AccountRepositoryInterface return $paginator; - } /** @@ -134,8 +145,8 @@ class AccountRepository implements AccountRepositoryInterface ->where('account_meta.name', 'accountRole') ->where('account_meta.data', '"savingAsset"') ->get(['accounts.*']); - $start = clone Session::get('start'); - $end = clone Session::get('end'); + $start = clone Session::get('start', new Carbon); + $end = clone Session::get('end', new Carbon); $accounts->each( function (Account $account) use ($start, $end) { @@ -171,7 +182,7 @@ class AccountRepository implements AccountRepositoryInterface */ public function leftOnAccount(Account $account) { - $balance = \Steam::balance($account); + $balance = \Steam::balance($account, null, true); /** @var PiggyBank $p */ foreach ($account->piggybanks()->get() as $p) { $balance -= $p->currentRelevantRep()->currentamount; @@ -209,10 +220,11 @@ class AccountRepository implements AccountRepositoryInterface if ($data['openingBalance'] != 0) { $type = $data['openingBalance'] < 0 ? 'expense' : 'revenue'; $opposingData = [ - 'user' => $data['user'], - 'accountType' => $type, - 'name' => $data['name'] . ' initial balance', - 'active' => false, + 'user' => $data['user'], + 'accountType' => $type, + 'virtual_balance' => $data['virtualBalance'], + 'name' => $data['name'] . ' initial balance', + 'active' => false, ]; $opposing = $this->storeAccount($opposingData); $this->storeInitialBalance($newAccount, $opposing, $data); @@ -230,8 +242,9 @@ class AccountRepository implements AccountRepositoryInterface public function update(Account $account, array $data) { // update the account: - $account->name = $data['name']; - $account->active = $data['active'] == '1' ? true : false; + $account->name = $data['name']; + $account->active = $data['active'] == '1' ? true : false; + $account->virtual_balance = $data['virtualBalance']; $account->save(); // update meta data: @@ -308,17 +321,21 @@ class AccountRepository implements AccountRepositoryInterface */ protected function storeMetadata(Account $account, array $data) { - $metaData = new AccountMeta( - [ - 'account_id' => $account->id, - 'name' => 'accountRole', - 'data' => $data['accountRole'] - ] - ); - if (!$metaData->isValid()) { - App::abort(500); + $validFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType']; + foreach ($validFields as $field) { + if (isset($data[$field])) { + $metaData = new AccountMeta( + [ + 'account_id' => $account->id, + 'name' => $field, + 'data' => $data[$field] + ] + ); + $metaData->save(); + } + + } - $metaData->save(); } /** @@ -399,30 +416,28 @@ class AccountRepository implements AccountRepositoryInterface */ protected function updateMetadata(Account $account, array $data) { - $metaEntries = $account->accountMeta()->get(); + $validFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType']; $updated = false; - /** @var AccountMeta $entry */ - foreach ($metaEntries as $entry) { - if ($entry->name == 'accountRole') { - $entry->data = $data['accountRole']; - $updated = true; + foreach ($validFields as $field) { + $entry = $account->accountMeta()->where('name', $field)->first(); + + // update if new data is present: + if ($entry && isset($data[$field])) { + $entry->data = $data[$field]; $entry->save(); } - } - - if ($updated === false) { - $metaData = new AccountMeta( - [ - 'account_id' => $account->id, - 'name' => 'accountRole', - 'data' => $data['accountRole'] - ] - ); - if (!$metaData->isValid()) { - App::abort(500); + // no entry but data present? + if (!$entry && isset($data[$field])) { + $metaData = new AccountMeta( + [ + 'account_id' => $account->id, + 'name' => $field, + 'data' => $data[$field] + ] + ); + $metaData->save(); } - $metaData->save(); } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index e948ba01c8..8d00441a22 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -2,11 +2,13 @@ namespace FireflyIII\Repositories\Account; -use FireflyIII\Models\Account; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\Preference; -use Illuminate\Support\Collection; use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Support\Collection; + /** * Interface AccountRepositoryInterface * @@ -85,4 +87,13 @@ interface AccountRepositoryInterface * @return Collection */ public function getSavingsAccounts(); + + + /** + * @param TransactionJournal $journal + * @param Account $account + * + * @return Transaction + */ + public function getFirstTransaction(TransactionJournal $journal, Account $account); } diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 44402cea23..34142fcc37 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -11,7 +11,8 @@ use FireflyIII\Models\TransactionJournal; * * @package FireflyIII\Repositories\Bill */ -interface BillRepositoryInterface { +interface BillRepositoryInterface +{ /** * @param Bill $bill @@ -25,7 +26,7 @@ interface BillRepositoryInterface { * and returns date ranges that fall within the given range; those ranges are the bills expected. When a bill is due on the 14th of the month and * you give 1st and the 31st of that month as argument, you'll get one response, matching the range of your bill. * - * @param Bill $bill + * @param Bill $bill * @param Carbon $start * @param Carbon $end * diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 2c547d1618..bfd2655e46 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -16,6 +16,31 @@ use Illuminate\Pagination\LengthAwarePaginator; class BudgetRepository implements BudgetRepositoryInterface { + /** + * @return void + */ + public function cleanupBudgets() + { + $limits = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')->get(['budget_limits.*']); + + // loop budget limits: + $found = []; + /** @var BudgetLimit $limit */ + foreach ($limits as $limit) { + $key = $limit->budget_id . '-' . $limit->startdate; + if (isset($found[$key])) { + $limit->delete(); + } else { + $found[$key] = true; + } + unset($key); + } + + // delete limits with amount 0: + BudgetLimit::where('amount',0)->delete(); + + } + /** * @param Budget $budget * @@ -43,9 +68,9 @@ class BudgetRepository implements BudgetRepositoryInterface $setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order','ASC') - ->orderBy('transaction_journals.id','DESC'); + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC'); $countQuery = $budget->transactionJournals(); @@ -57,6 +82,7 @@ class BudgetRepository implements BudgetRepositoryInterface $set = $setQuery->get(['transaction_journals.*']); $count = $countQuery->count(); + return new LengthAwarePaginator($set, $count, $take, $offset); } @@ -103,7 +129,8 @@ class BudgetRepository implements BudgetRepositoryInterface public function update(Budget $budget, array $data) { // update the account: - $budget->name = $data['name']; + $budget->name = $data['name']; + $budget->active = $data['active']; $budget->save(); return $budget; @@ -118,10 +145,12 @@ class BudgetRepository implements BudgetRepositoryInterface */ public function updateLimitAmount(Budget $budget, Carbon $date, $amount) { + // there should be a budget limit for this startdate: /** @var BudgetLimit $limit */ - $limit = $budget->limitrepetitions()->where('limit_repetitions.startdate', $date)->first(['limit_repetitions.*']); + $limit = $budget->budgetlimits()->where('budget_limits.startdate', $date)->first(['budget_limits.*']); + if (!$limit) { - // create one! + // if not, create one! $limit = new BudgetLimit; $limit->budget()->associate($budget); $limit->startdate = $date; @@ -129,6 +158,10 @@ class BudgetRepository implements BudgetRepositoryInterface $limit->repeat_freq = 'monthly'; $limit->repeats = 0; $limit->save(); + + // likewise, there should be a limit repetition to match the end date + // (which is always the end of the month) but that is caught by an event. + } else { if ($amount > 0) { $limit->amount = $amount; diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index dce5f21ae4..9b5eef9229 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -20,6 +20,11 @@ interface BudgetRepositoryInterface */ public function destroy(Budget $budget); + /** + * @return void + */ + public function cleanupBudgets(); + /** * @param Budget $budget * @param Carbon $date diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index a3bde0c535..fdda6ae3d4 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -2,6 +2,7 @@ namespace FireflyIII\Repositories\Journal; +use App; use Auth; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; @@ -11,6 +12,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; +use Log; /** * Class JournalRepository @@ -20,6 +22,16 @@ use Illuminate\Support\Collection; class JournalRepository implements JournalRepositoryInterface { + /** + * Get users first transaction journal + * + * @return TransactionJournal + */ + public function first() + { + return Auth::user()->transactionjournals()->orderBy('date', 'ASC')->first(['transaction_journals.*']); + } + /** * * Get the account_id, which is the asset account that paid for the transaction. @@ -139,41 +151,7 @@ class JournalRepository implements JournalRepositoryInterface } // store accounts (depends on type) - switch ($transactionType->type) { - case 'Withdrawal': - - $from = Account::find($data['account_id']); - - if (strlen($data['expense_account']) > 0) { - $toType = AccountType::where('type', 'Expense account')->first(); - $to = Account::firstOrCreate( - ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1] - ); - } else { - $toType = AccountType::where('type', 'Cash account')->first(); - $to = Account::firstOrCreate(['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]); - } - break; - - case 'Deposit': - $to = Account::find($data['account_id']); - - if (strlen($data['revenue_account']) > 0) { - $fromType = AccountType::where('type', 'Revenue account')->first(); - $from = Account::firstOrCreate( - ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1] - ); - } else { - $toType = AccountType::where('type', 'Cash account')->first(); - $from = Account::firstOrCreate(['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]); - } - - break; - case 'Transfer': - $from = Account::find($data['account_from_id']); - $to = Account::find($data['account_to_id']); - break; - } + list($from, $to) = $this->storeAccounts($transactionType, $data); // store accompanying transactions. Transaction::create( // first transaction. @@ -198,7 +176,6 @@ class JournalRepository implements JournalRepositoryInterface } - /** * @param TransactionJournal $journal * @param array $data @@ -228,41 +205,7 @@ class JournalRepository implements JournalRepositoryInterface } // store accounts (depends on type) - switch ($journal->transactionType->type) { - case 'Withdrawal': - - $from = Account::find($data['account_id']); - - if (strlen($data['expense_account']) > 0) { - $toType = AccountType::where('type', 'Expense account')->first(); - $to = Account::firstOrCreate( - ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1] - ); - } else { - $toType = AccountType::where('type', 'Cash account')->first(); - $to = Account::firstOrCreate(['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]); - } - break; - - case 'Deposit': - $to = Account::find($data['account_id']); - - if (strlen($data['revenue_account']) > 0) { - $fromType = AccountType::where('type', 'Revenue account')->first(); - $from = Account::firstOrCreate( - ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1] - ); - } else { - $toType = AccountType::where('type', 'Cash account')->first(); - $from = Account::firstOrCreate(['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1]); - } - - break; - case 'Transfer': - $from = Account::find($data['account_from_id']); - $to = Account::find($data['account_to_id']); - break; - } + list($from, $to) = $this->storeAccounts($journal->transactionType, $data); // update the from and to transaction. /** @var Transaction $transaction */ @@ -286,4 +229,65 @@ class JournalRepository implements JournalRepositoryInterface return $journal; } + /** + * @param TransactionType $type + * @param array $data + * + * @return array + */ + protected function storeAccounts(TransactionType $type, array $data) + { + $from = null; + $to = null; + switch ($type->type) { + case 'Withdrawal': + + $from = Account::find($data['account_id']); + + if (strlen($data['expense_account']) > 0) { + $toType = AccountType::where('type', 'Expense account')->first(); + $to = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => $data['expense_account'], 'active' => 1] + ); + } else { + $toType = AccountType::where('type', 'Cash account')->first(); + $to = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1] + ); + } + break; + + case 'Deposit': + $to = Account::find($data['account_id']); + + if (strlen($data['revenue_account']) > 0) { + $fromType = AccountType::where('type', 'Revenue account')->first(); + $from = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['revenue_account'], 'active' => 1] + ); + } else { + $toType = AccountType::where('type', 'Cash account')->first(); + $from = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $toType->id, 'name' => 'Cash account', 'active' => 1] + ); + } + + break; + case 'Transfer': + $from = Account::find($data['account_from_id']); + $to = Account::find($data['account_to_id']); + break; + } + if (is_null($to->id)) { + Log::error('"to"-account is null, so we cannot continue!'); + App::abort(500, '"to"-account is null, so we cannot continue!'); + } + if (is_null($from->id)) { + Log::error('"from"-account is null, so we cannot continue!'); + App::abort(500, '"from"-account is null, so we cannot continue!'); + } + + return [$from, $to]; + } + } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index cf10d0e557..2bd13b2870 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -3,6 +3,7 @@ namespace FireflyIII\Repositories\Journal; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\Transaction; use Illuminate\Support\Collection; /** @@ -44,4 +45,10 @@ interface JournalRepositoryInterface * @return mixed */ public function update(TransactionJournal $journal, array $data); + + /** + * Get users first transaction journal + * @return TransactionJournal + */ + public function first(); } diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 3fedcf5871..fb381ba1c1 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -111,7 +111,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function setOrder($id, $order) { $piggyBank = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', Auth::user()->id) - ->where('piggy_banks.id',$id)->first(['piggy_banks.*']); + ->where('piggy_banks.id', $id)->first(['piggy_banks.*']); if ($piggyBank) { $piggyBank->order = $order; $piggyBank->save(); @@ -153,9 +153,6 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface $piggyBank->targetdate = $data['targetdate']; $piggyBank->reminder = $data['reminder']; $piggyBank->startdate = $data['startdate']; - $piggyBank->rep_length = isset($data['rep_length']) ? $data['rep_length'] : null; - $piggyBank->rep_every = isset($data['rep_every']) ? $data['rep_every'] : null; - $piggyBank->rep_times = isset($data['rep_times']) ? $data['rep_times'] : null; $piggyBank->remind_me = isset($data['remind_me']) && $data['remind_me'] == '1' ? 1 : 0; $piggyBank->save(); diff --git a/app/Repositories/PiggyBank/PiggybankPart.php b/app/Repositories/PiggyBank/PiggybankPart.php index 9bbbad807c..bbde8435f3 100644 --- a/app/Repositories/PiggyBank/PiggybankPart.php +++ b/app/Repositories/PiggyBank/PiggybankPart.php @@ -2,10 +2,9 @@ namespace FireflyIII\Repositories\PiggyBank; -use FireflyIII\Models\Reminder; -use FireflyIII\Models\PiggyBankRepetition; - use Carbon\Carbon; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\Reminder; /** * Class PiggyBankPart diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 115392eb1f..aed7c6bfcb 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -6,6 +6,7 @@ use Cache; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; +use Illuminate\Support\Collection; use Preferences as Prefs; /** @@ -134,6 +135,14 @@ class Amount return $this->formatWithSymbol($symbol, $amount, $coloured); } + /** + * @return Collection + */ + public function getAllCurrencies() + { + return TransactionCurrency::orderBy('code', 'ASC')->get(); + } + /** * @return string */ diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 99c28470ed..e0d93024c2 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -34,7 +34,7 @@ class ExpandedForm $options['step'] = 'any'; $options['min'] = '0.01'; $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); - $currencies = TransactionCurrency::orderBy('code', 'ASC')->get(); + $currencies = Amt::getAllCurrencies(); $html = View::make('form.amount', compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); return $html; @@ -53,18 +53,22 @@ class ExpandedForm 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', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'piggy_bank_id' => 'Piggy bank']; + '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', + 'budget_id' => 'Budget', + 'openingBalance' => 'Opening balance', + 'virtualBalance' => 'Virtual balance', + 'targetamount' => 'Target amount', + 'accountRole' => 'Account role', + 'openingBalanceDate' => 'Opening balance date', + 'ccType' => 'Credit card payment plan', + 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Piggy bank']; return isset($labels[$name]) ? $labels[$name] : str_replace('_', ' ', ucfirst($name)); @@ -143,7 +147,7 @@ class ExpandedForm $value = $this->fillFieldValue($name, $value); $options['step'] = 'any'; $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); - $currencies = TransactionCurrency::orderBy('code', 'ASC')->get(); + $currencies = Amt::getAllCurrencies(); $html = View::make('form.balance', compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); return $html; @@ -190,6 +194,24 @@ class ExpandedForm return $html; } + /** + * @param $name + * @param null $value + * @param array $options + * + * @return string + */ + public function month($name, $value = null, array $options = []) + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $html = View::make('form.month', compact('classes', 'name', 'label', 'value', 'options'))->render(); + + return $html; + } + /** * @param $name * @param null $value diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index 2ef5c9a4bd..65f3de722b 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -344,6 +344,48 @@ class Navigation throw new FireflyException('Cannot do startOfPeriod for $repeat_freq ' . $repeatFreq); } + /** + * @param Carbon $theDate + * @param $repeatFreq + * @param int $subtract + * + * @return Carbon + * @throws FireflyException + */ + public function subtractPeriod(Carbon $theDate, $repeatFreq, $subtract = 1) + { + $date = clone $theDate; + + $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; + } + + throw new FireflyException('Cannot do subtractPeriod for $repeat_freq ' . $repeatFreq); + } + /** * @param $range * @param Carbon $start @@ -414,47 +456,5 @@ class Navigation throw new FireflyException('updateStartDate cannot handle $range ' . $range); } - /** - * @param Carbon $theDate - * @param $repeatFreq - * @param int $subtract - * - * @return Carbon - * @throws FireflyException - */ - public function subtractPeriod(Carbon $theDate, $repeatFreq, $subtract = 1) - { - $date = clone $theDate; - - $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; - } - - throw new FireflyException('Cannot do subtractPeriod for $repeat_freq ' . $repeatFreq); - } - } diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index b12a8fef6a..0604a0b1be 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -16,7 +16,7 @@ class Preferences * @param $name * @param null $default * - * @return null|\Preference + * @return null|Preference */ public function get($name, $default = null) { @@ -37,7 +37,7 @@ class Preferences * @param $name * @param $value * - * @return \Preference + * @return Preference */ public function set($name, $value) { diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index 5270cdf610..f1e0c658ae 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -112,8 +112,8 @@ class Search implements SearchInterface )->get(); // encrypted - $all = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 1)->get(); - $set = $all->filter( + $all = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 1)->get(); + $set = $all->filter( function (TransactionJournal $journal) use ($words) { foreach ($words as $word) { $haystack = strtolower($journal->description); diff --git a/app/Support/Search/SearchInterface.php b/app/Support/Search/SearchInterface.php index 4b58393065..431275e8fe 100644 --- a/app/Support/Search/SearchInterface.php +++ b/app/Support/Search/SearchInterface.php @@ -9,7 +9,8 @@ use Illuminate\Support\Collection; * * @package FireflyIII\Support\Search */ -interface SearchInterface { +interface SearchInterface +{ /** * @param array $words * diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 756c11b73d..5bd043eefe 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -19,10 +19,11 @@ class Steam * * @param Account $account * @param Carbon $date + * @param bool $ignoreVirtualBalance * * @return float */ - public function balance(Account $account, Carbon $date = null) + public function balance(Account $account, Carbon $date = null, $ignoreVirtualBalance = false) { $date = is_null($date) ? Carbon::now() : $date; @@ -47,6 +48,9 @@ class Steam 'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id' )->where('transaction_journals.date', '<=', $date->format('Y-m-d'))->sum('transactions.amount') ); + if (!$ignoreVirtualBalance) { + $balance += floatval($account->virtual_balance); + } return $balance; } @@ -100,11 +104,13 @@ class Steam if (isset($array[$id])) { $array[$id]['amount'] += floatval($entry->amount); $array[$id]['spent'] += floatval($entry->spent); + $array[$id]['encrypted'] = intval($entry->encrypted); } else { $array[$id] = [ - 'amount' => floatval($entry->amount), - 'spent' => floatval($entry->spent), - 'name' => $entry->name + 'amount' => floatval($entry->amount), + 'spent' => floatval($entry->spent), + 'encrypted' => intval($entry->encrypted), + 'name' => $entry->name ]; } } diff --git a/app/User.php b/app/User.php index 659d236c1d..1631995fd7 100644 --- a/app/User.php +++ b/app/User.php @@ -75,6 +75,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $this->hasManyThrough('FireflyIII\Models\PiggyBank', 'FireflyIII\Models\Account'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function transactions() + { + return $this->hasManyThrough('FireflyIII\Models\Transaction', 'FireflyIII\Models\TransactionJournal'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index d4b4771e90..3ba63000ab 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -5,10 +5,13 @@ namespace FireflyIII\Validation; use Auth; use Carbon\Carbon; use Config; +use Crypt; use DB; +use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Validation\Validator; -use Input; +use Log; use Navigation; /** @@ -28,6 +31,7 @@ class FireflyValidator extends Validator */ public function validateBelongsToUser($attribute, $value, $parameters) { + $count = DB::table($parameters[0])->where('user_id', Auth::user()->id)->where('id', $value)->count(); if ($count == 1) { return true; @@ -79,44 +83,62 @@ class FireflyValidator extends Validator */ public function validateUniqueAccountForUser($attribute, $value, $parameters) { - // get account type from data, we must have this: - $validTypes = array_keys(Config::get('firefly.subTitlesByIdentifier')); + $type = null; - $type = isset($this->data['what']) && in_array($this->data['what'], $validTypes) ? $this->data['what'] : null; - // some fallback: - if (is_null($type)) { - $type = in_array(Input::get('what'), $validTypes) ? Input::get('what') : null; - } - // still null? - if (is_null($type)) { - // find by other field: - $type = isset($this->data['account_type_id']) ? $this->data['account_type_id'] : 0; - $dbType = AccountType::find($type); - } else { - $longType = Config::get('firefly.accountTypeByIdentifier.' . $type); - $dbType = AccountType::whereType($longType)->first(); + /** + * Switch on different cases on which this method can respond: + */ + $hasWhat = isset($this->data['what']); + $hasAccountTypeId = isset($this->data['account_type_id']) && isset($this->data['name']); + $hasAccountId = isset($this->data['id']); + $ignoreId = 0; + + + if ($hasWhat) { + $search = Config::get('firefly.accountTypeByIdentifier.' . $this->data['what']); + $type = AccountType::whereType($search)->first(); + // this field can be used to find the exact type, and continue. } - if (is_null($dbType)) { + if ($hasAccountTypeId) { + $type = AccountType::find($this->data['account_type_id']); + } + + if ($hasAccountId) { + /** @var Account $account */ + $account = Account::find($this->data['id']); + $ignoreId = intval($this->data['id']); + $type = AccountType::find($account->account_type_id); + unset($account); + } + + /** + * Try to decrypt data just in case: + */ + try { + $value = Crypt::decrypt($value); + } catch (DecryptException $e) { + } + + + if (is_null($type)) { + Log::error('Could not determine type of account to validate.'); + return false; } - // user id? - $userId = Auth::check() ? Auth::user()->id : $this->data['user_id']; - - $query = DB::table('accounts')->where('name', $value)->where('account_type_id', $dbType->id)->where('user_id', $userId); - - if (isset($parameters[0])) { - $query->where('id', '!=', $parameters[0]); - } - $count = $query->count(); - if ($count == 0) { - - return true; + // get all accounts with this type, and find the name. + $userId = Auth::check() ? Auth::user()->id : 0; + $set = Account::where('account_type_id', $type->id)->where('id', '!=', $ignoreId)->where('user_id', $userId)->get(); + /** @var Account $entry */ + foreach ($set as $entry) { + if ($entry->name == $value) { + return false; + } } - return false; + return true; } @@ -143,6 +165,46 @@ class FireflyValidator extends Validator } + /** + * Validate an object and its unicity. Checks for encryption / encrypted values as well. + * + * parameter 0: the table + * parameter 1: the field + * parameter 2: the encrypted / not encrypted boolean. Defaults to "encrypted". + * parameter 3: an id to ignore (when editing) + * + * @param $attribute + * @param $value + * @param $parameters + * + * @return bool + */ + public function validateUniqueObjectForUser($attribute, $value, $parameters) + { + $table = $parameters[0]; + $field = $parameters[1]; + $encrypted = isset($parameters[2]) ? $parameters[2] : 'encrypted'; + $exclude = isset($parameters[3]) ? $parameters[3] : null; + + $query = DB::table($table)->where('user_id', Auth::user()->id); + + if (!is_null($exclude)) { + $query->where('id', '!=', $exclude); + } + + + $set = $query->get(); + foreach ($set as $entry) { + $isEncrypted = intval($entry->$encrypted) == 1 ? true : false; + $checkValue = $isEncrypted ? Crypt::decrypt($entry->$field) : $entry->$field; + if ($checkValue == $value) { + return false; + } + } + + return true; + } + /** * @param $attribute * @param $value @@ -152,18 +214,24 @@ class FireflyValidator extends Validator */ public function validateUniquePiggyBankForUser($attribute, $value, $parameters) { - $query = DB::table($parameters[0])->where('piggy_banks.'.$parameters[1], $value); + $exclude = isset($parameters[0]) ? $parameters[0] : null; + $query = DB::table('piggy_banks'); $query->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id'); $query->where('accounts.user_id', Auth::user()->id); - if (isset($paramers[2])) { - $query->where('piggy_banks.id', '!=', $parameters[2]); + if (!is_null($exclude)) { + $query->where('piggy_banks.id', '!=', $exclude); } - $count = $query->count(); - if ($count == 0) { - return true; + $set = $query->get(['piggy_banks.*']); + + foreach($set as $entry) { + $isEncrypted = intval($entry->encrypted) == 1 ? true : false; + $checkValue = $isEncrypted ? Crypt::decrypt($entry->name) : $entry->name; + if($checkValue == $value) { + return false; + } } - return false; + return true; } } diff --git a/composer.json b/composer.json index d268b01254..c9459d4f3f 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,11 @@ "barryvdh/laravel-ide-helper": "~2.0", "phpunit/phpunit": "~4.0", "phpspec/phpspec": "~2.1", - "satooshi/php-coveralls": "0.6.1" + "satooshi/php-coveralls": "0.6.1", + "mockery/mockery": "0.9.*", + "league/factory-muffin": "~2.1" + + }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 1a108b872b..889d37ed8f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "b77b9f717b25e1e193bdc6edb18ad492", + "hash": "0d43c4c85607c5cdc901cde2d18b75d5", "packages": [ { "name": "classpreloader/classpreloader", @@ -412,16 +412,16 @@ }, { "name": "doctrine/common", - "version": "v2.4.2", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "5db6ab40e4c531f14dad4ca96a394dfce5d4255b" + "reference": "cd8daf2501e10c63dced7b8b9b905844316ae9d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/5db6ab40e4c531f14dad4ca96a394dfce5d4255b", - "reference": "5db6ab40e4c531f14dad4ca96a394dfce5d4255b", + "url": "https://api.github.com/repos/doctrine/common/zipball/cd8daf2501e10c63dced7b8b9b905844316ae9d3", + "reference": "cd8daf2501e10c63dced7b8b9b905844316ae9d3", "shasum": "" }, "require": { @@ -438,7 +438,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4.x-dev" + "dev-master": "2.6.x-dev" } }, "autoload": { @@ -451,17 +451,6 @@ "MIT" ], "authors": [ - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com", - "homepage": "http://www.jwage.com/", - "role": "Creator" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com", - "homepage": "http://www.instaclick.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -470,11 +459,17 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, { "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" + "email": "schmittjoh@gmail.com" } ], "description": "Common Library for Doctrine projects", @@ -486,7 +481,7 @@ "persistence", "spl" ], - "time": "2014-05-21 19:28:51" + "time": "2015-04-02 19:55:44" }, { "name": "doctrine/dbal", @@ -950,16 +945,16 @@ }, { "name": "laravel/framework", - "version": "v5.0.23", + "version": "v5.0.26", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "59219f7afb60be05d74ce01fcb5d2440f7a1b13d" + "reference": "8e53c33e144f94032cc6ecbfee0be2a96ed63be0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/59219f7afb60be05d74ce01fcb5d2440f7a1b13d", - "reference": "59219f7afb60be05d74ce01fcb5d2440f7a1b13d", + "url": "https://api.github.com/repos/laravel/framework/zipball/8e53c33e144f94032cc6ecbfee0be2a96ed63be0", + "reference": "8e53c33e144f94032cc6ecbfee0be2a96ed63be0", "shasum": "" }, "require": { @@ -1072,7 +1067,7 @@ "framework", "laravel" ], - "time": "2015-03-28 16:56:59" + "time": "2015-04-03 02:58:05" }, { "name": "league/commonmark", @@ -1135,16 +1130,16 @@ }, { "name": "league/flysystem", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "51cd7cd7ee0defbaafc6ec0d3620110a5d71e11a" + "reference": "3c2400a99ccc3be6884d40361890010449c6b447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/51cd7cd7ee0defbaafc6ec0d3620110a5d71e11a", - "reference": "51cd7cd7ee0defbaafc6ec0d3620110a5d71e11a", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3c2400a99ccc3be6884d40361890010449c6b447", + "reference": "3c2400a99ccc3be6884d40361890010449c6b447", "shasum": "" }, "require": { @@ -1154,7 +1149,7 @@ "ext-fileinfo": "*", "league/phpunit-coverage-listener": "~1.1", "mockery/mockery": "~0.9", - "phpspec/phpspec": "~2.0.0", + "phpspec/phpspec": "~2.0", "phpspec/prophecy-phpunit": "~1.0", "phpunit/phpunit": "~4.1", "predis/predis": "~1.0", @@ -1214,7 +1209,7 @@ "sftp", "storage" ], - "time": "2015-03-10 11:04:14" + "time": "2015-03-29 14:01:43" }, { "name": "monolog/monolog", @@ -1588,17 +1583,17 @@ }, { "name": "symfony/console", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Console", "source": { "type": "git", "url": "https://github.com/symfony/Console.git", - "reference": "53f86497ccd01677e22435cfb7262599450a90d1" + "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/53f86497ccd01677e22435cfb7262599450a90d1", - "reference": "53f86497ccd01677e22435cfb7262599450a90d1", + "url": "https://api.github.com/repos/symfony/Console/zipball/5b91dc4ed5eb08553f57f6df04c4730a73992667", + "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667", "shasum": "" }, "require": { @@ -1642,21 +1637,21 @@ ], "description": "Symfony Console Component", "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/debug", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Debug", "source": { "type": "git", "url": "https://github.com/symfony/Debug.git", - "reference": "5c1570dea188ade0c6c5e874c2f0a6570587aa1c" + "reference": "d49a46a20a8f0544aedac54466750ad787d3d3e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Debug/zipball/5c1570dea188ade0c6c5e874c2f0a6570587aa1c", - "reference": "5c1570dea188ade0c6c5e874c2f0a6570587aa1c", + "url": "https://api.github.com/repos/symfony/Debug/zipball/d49a46a20a8f0544aedac54466750ad787d3d3e3", + "reference": "d49a46a20a8f0544aedac54466750ad787d3d3e3", "shasum": "" }, "require": { @@ -1703,11 +1698,11 @@ ], "description": "Symfony Debug Component", "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" + "time": "2015-03-22 16:55:57" }, { "name": "symfony/event-dispatcher", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/EventDispatcher", "source": { "type": "git", @@ -1766,17 +1761,17 @@ }, { "name": "symfony/filesystem", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Filesystem", "source": { "type": "git", "url": "https://github.com/symfony/Filesystem.git", - "reference": "fdc5f151bc2db066b51870d5bea3773d915ced0b" + "reference": "4983964b3693e4f13449cb3800c64a9112c301b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Filesystem/zipball/fdc5f151bc2db066b51870d5bea3773d915ced0b", - "reference": "fdc5f151bc2db066b51870d5bea3773d915ced0b", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/4983964b3693e4f13449cb3800c64a9112c301b4", + "reference": "4983964b3693e4f13449cb3800c64a9112c301b4", "shasum": "" }, "require": { @@ -1812,21 +1807,21 @@ ], "description": "Symfony Filesystem Component", "homepage": "http://symfony.com", - "time": "2015-03-12 10:28:44" + "time": "2015-03-22 16:55:57" }, { "name": "symfony/finder", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", "url": "https://github.com/symfony/Finder.git", - "reference": "bebc7479c566fa4f14b9bcef9e32e719eabec74e" + "reference": "5dbe2e73a580618f5b4880fda93406eed25de251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/bebc7479c566fa4f14b9bcef9e32e719eabec74e", - "reference": "bebc7479c566fa4f14b9bcef9e32e719eabec74e", + "url": "https://api.github.com/repos/symfony/Finder/zipball/5dbe2e73a580618f5b4880fda93406eed25de251", + "reference": "5dbe2e73a580618f5b4880fda93406eed25de251", "shasum": "" }, "require": { @@ -1862,21 +1857,21 @@ ], "description": "Symfony Finder Component", "homepage": "http://symfony.com", - "time": "2015-03-12 10:28:44" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/http-foundation", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/HttpFoundation", "source": { "type": "git", "url": "https://github.com/symfony/HttpFoundation.git", - "reference": "d527885e37b55ec0e3dc6f4b70566d0f9b2f2388" + "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/d527885e37b55ec0e3dc6f4b70566d0f9b2f2388", - "reference": "d527885e37b55ec0e3dc6f4b70566d0f9b2f2388", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/8a6337233f08f7520de97f4ffd6f00e947d892f9", + "reference": "8a6337233f08f7520de97f4ffd6f00e947d892f9", "shasum": "" }, "require": { @@ -1916,21 +1911,21 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" + "time": "2015-04-01 16:50:12" }, { "name": "symfony/http-kernel", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/HttpKernel", "source": { "type": "git", "url": "https://github.com/symfony/HttpKernel.git", - "reference": "6f7b2d3ba8bf02cf77edb399696e85ef24a888a4" + "reference": "3829cacfe21eaf3f73604a62d79183d1f6e792c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/6f7b2d3ba8bf02cf77edb399696e85ef24a888a4", - "reference": "6f7b2d3ba8bf02cf77edb399696e85ef24a888a4", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/3829cacfe21eaf3f73604a62d79183d1f6e792c4", + "reference": "3829cacfe21eaf3f73604a62d79183d1f6e792c4", "shasum": "" }, "require": { @@ -1994,21 +1989,21 @@ ], "description": "Symfony HttpKernel Component", "homepage": "http://symfony.com", - "time": "2015-03-17 14:58:46" + "time": "2015-04-01 16:55:26" }, { "name": "symfony/process", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Process", "source": { "type": "git", "url": "https://github.com/symfony/Process.git", - "reference": "4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc" + "reference": "a8bebaec1a9dc6cde53e0250e32917579b0be552" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc", - "reference": "4d717f34f3d1d6ab30fbe79f7132960a27f4a0dc", + "url": "https://api.github.com/repos/symfony/Process/zipball/a8bebaec1a9dc6cde53e0250e32917579b0be552", + "reference": "a8bebaec1a9dc6cde53e0250e32917579b0be552", "shasum": "" }, "require": { @@ -2044,21 +2039,21 @@ ], "description": "Symfony Process Component", "homepage": "http://symfony.com", - "time": "2015-03-12 10:28:44" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/routing", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Routing", "source": { "type": "git", "url": "https://github.com/symfony/Routing.git", - "reference": "a7f3eb540e5c553c3c95993c6fc2e7edb2f3b9d2" + "reference": "4e173a645b63ff60a124f3741b4f15feebd908fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Routing/zipball/a7f3eb540e5c553c3c95993c6fc2e7edb2f3b9d2", - "reference": "a7f3eb540e5c553c3c95993c6fc2e7edb2f3b9d2", + "url": "https://api.github.com/repos/symfony/Routing/zipball/4e173a645b63ff60a124f3741b4f15feebd908fa", + "reference": "4e173a645b63ff60a124f3741b4f15feebd908fa", "shasum": "" }, "require": { @@ -2113,21 +2108,21 @@ "uri", "url" ], - "time": "2015-03-13 17:37:22" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/security-core", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Security/Core", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "889290a5c00d3f174cc73ce13a11a0a6406939e9" + "reference": "d25c17db741f58c0f615e52006a47f6fb23cd9b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/889290a5c00d3f174cc73ce13a11a0a6406939e9", - "reference": "889290a5c00d3f174cc73ce13a11a0a6406939e9", + "url": "https://api.github.com/repos/symfony/security-core/zipball/d25c17db741f58c0f615e52006a47f6fb23cd9b3", + "reference": "d25c17db741f58c0f615e52006a47f6fb23cd9b3", "shasum": "" }, "require": { @@ -2177,21 +2172,21 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/translation", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Translation", "source": { "type": "git", "url": "https://github.com/symfony/Translation.git", - "reference": "043db5f1eef9598d1bc1d75b93304984c003d7d9" + "reference": "bd939f05cdaca128f4ddbae1b447d6f0203b60af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Translation/zipball/043db5f1eef9598d1bc1d75b93304984c003d7d9", - "reference": "043db5f1eef9598d1bc1d75b93304984c003d7d9", + "url": "https://api.github.com/repos/symfony/Translation/zipball/bd939f05cdaca128f4ddbae1b447d6f0203b60af", + "reference": "bd939f05cdaca128f4ddbae1b447d6f0203b60af", "shasum": "" }, "require": { @@ -2236,21 +2231,21 @@ ], "description": "Symfony Translation Component", "homepage": "http://symfony.com", - "time": "2015-03-14 11:42:25" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/var-dumper", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/VarDumper", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "61ee6c848fd2c623e13f59df48833f8b8bad7fda" + "reference": "aafae00236e147568832de3c65ccb94cfc836278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/61ee6c848fd2c623e13f59df48833f8b8bad7fda", - "reference": "61ee6c848fd2c623e13f59df48833f8b8bad7fda", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/aafae00236e147568832de3c65ccb94cfc836278", + "reference": "aafae00236e147568832de3c65ccb94cfc836278", "shasum": "" }, "require": { @@ -2296,7 +2291,7 @@ "debug", "dump" ], - "time": "2015-03-06 16:45:31" + "time": "2015-03-31 08:12:29" }, { "name": "vlucas/phpdotenv", @@ -2577,6 +2572,54 @@ ], "time": "2014-10-13 12:58:55" }, + { + "name": "fzaninotto/faker", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/010c7efedd88bf31141a02719f51fb44c732d5a0", + "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-0": { + "Faker": "src/", + "Faker\\PHPUnit": "test/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2014-06-04 14:43:02" + }, { "name": "guzzle/guzzle", "version": "v3.9.3", @@ -2672,6 +2715,112 @@ ], "time": "2015-03-18 18:23:50" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "ac50c470531243944f977b8de75be0b684a9cb51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/ac50c470531243944f977b8de75be0b684a9cb51", + "reference": "ac50c470531243944f977b8de75be0b684a9cb51", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-01-20 19:34:09" + }, + { + "name": "league/factory-muffin", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/factory-muffin.git", + "reference": "91f0adcdac6b5f7bf2277ac2c90f94352afe65de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/factory-muffin/zipball/91f0adcdac6b5f7bf2277ac2c90f94352afe65de", + "reference": "91f0adcdac6b5f7bf2277ac2c90f94352afe65de", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "1.4.*", + "php": ">=5.3.3" + }, + "replace": { + "zizaco/factory-muff": "self.version" + }, + "require-dev": { + "illuminate/database": "~4.1", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "illuminate/database": "Factory Muffin works well with eloquent models." + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\FactoryMuffin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@mineuk.com" + }, + { + "name": "Zizaco Zizuini", + "email": "zizaco@gmail.com" + }, + { + "name": "Scott Robertson", + "email": "scottymeuk@gmail.com" + } + ], + "description": "The goal of this package is to enable the rapid creation of objects for the purpose of testing.", + "homepage": "http://factory-muffin.thephpleague.com/", + "keywords": [ + "factory", + "laravel", + "testing" + ], + "time": "2014-09-18 18:29:06" + }, { "name": "maximebf/debugbar", "version": "v1.10.4", @@ -2728,6 +2877,71 @@ ], "time": "2015-02-05 07:51:20" }, + { + "name": "mockery/mockery", + "version": "0.9.4", + "source": { + "type": "git", + "url": "https://github.com/padraic/mockery.git", + "reference": "70bba85e4aabc9449626651f48b9018ede04f86b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", + "reference": "70bba85e4aabc9449626651f48b9018ede04f86b", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2015-04-02 19:54:00" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.4", @@ -2889,21 +3103,22 @@ }, { "name": "phpspec/prophecy", - "version": "v1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9" + "reference": "8724cd239f8ef4c046f55a3b18b4d91cc7f3e4c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/9ca52329bcdd1500de24427542577ebf3fc2f1c9", - "reference": "9ca52329bcdd1500de24427542577ebf3fc2f1c9", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8724cd239f8ef4c046f55a3b18b4d91cc7f3e4c5", + "reference": "8724cd239f8ef4c046f55a3b18b4d91cc7f3e4c5", "shasum": "" }, "require": { - "doctrine/instantiator": "~1.0,>=1.0.2", - "phpdocumentor/reflection-docblock": "~2.0" + "doctrine/instantiator": "^1.0.2", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -2911,7 +3126,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -2935,7 +3150,7 @@ } ], "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "http://phpspec.org", + "homepage": "https://github.com/phpspec/prophecy", "keywords": [ "Double", "Dummy", @@ -2944,7 +3159,7 @@ "spy", "stub" ], - "time": "2014-11-17 16:23:49" + "time": "2015-03-27 19:31:25" }, { "name": "phpunit/php-code-coverage", @@ -3192,16 +3407,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.5.0", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5" + "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5b578d3865a9128b9c209b011fda6539ec06e7a5", - "reference": "5b578d3865a9128b9c209b011fda6539ec06e7a5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d6429b0995b24a2d9dfe5587ee3a7071c1161af4", + "reference": "d6429b0995b24a2d9dfe5587ee3a7071c1161af4", "shasum": "" }, "require": { @@ -3211,8 +3426,8 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", - "phpspec/prophecy": "~1.3.1", - "phpunit/php-code-coverage": "~2.0", + "phpspec/prophecy": "~1.3,>=1.3.1", + "phpunit/php-code-coverage": "~2.0,>=2.0.11", "phpunit/php-file-iterator": "~1.3.2", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "~1.0.2", @@ -3260,29 +3475,29 @@ "testing", "xunit" ], - "time": "2015-02-05 15:51:19" + "time": "2015-03-29 09:24:05" }, { "name": "phpunit/phpunit-mock-objects", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "c63d2367247365f688544f0d500af90a11a44c65" + "reference": "74ffb87f527f24616f72460e54b595f508dccb5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c63d2367247365f688544f0d500af90a11a44c65", - "reference": "c63d2367247365f688544f0d500af90a11a44c65", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/74ffb87f527f24616f72460e54b595f508dccb5c", + "reference": "74ffb87f527f24616f72460e54b595f508dccb5c", "shasum": "" }, "require": { - "doctrine/instantiator": "~1.0,>=1.0.1", + "doctrine/instantiator": "~1.0,>=1.0.2", "php": ">=5.3.3", "phpunit/php-text-template": "~1.2" }, "require-dev": { - "phpunit/phpunit": "~4.3" + "phpunit/phpunit": "~4.4" }, "suggest": { "ext-soap": "*" @@ -3315,7 +3530,7 @@ "mock", "xunit" ], - "time": "2014-10-03 05:12:11" + "time": "2015-04-02 05:36:41" }, { "name": "satooshi/php-coveralls", @@ -3758,17 +3973,17 @@ }, { "name": "symfony/class-loader", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/ClassLoader", "source": { "type": "git", "url": "https://github.com/symfony/ClassLoader.git", - "reference": "56bf6fe551ca013471541d866f73a6cc70ece9c5" + "reference": "861765b3e5f32979de5bd19ad2577cbb830a29d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/56bf6fe551ca013471541d866f73a6cc70ece9c5", - "reference": "56bf6fe551ca013471541d866f73a6cc70ece9c5", + "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/861765b3e5f32979de5bd19ad2577cbb830a29d5", + "reference": "861765b3e5f32979de5bd19ad2577cbb830a29d5", "shasum": "" }, "require": { @@ -3805,21 +4020,21 @@ ], "description": "Symfony ClassLoader Component", "homepage": "http://symfony.com", - "time": "2015-03-13 17:37:22" + "time": "2015-03-27 10:19:51" }, { "name": "symfony/config", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Config", "source": { "type": "git", "url": "https://github.com/symfony/Config.git", - "reference": "7a47189c7667ca69bcaafd19ef8a8941db449a2c" + "reference": "d91be01336605db8da21b79bc771e46a7276d1bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/7a47189c7667ca69bcaafd19ef8a8941db449a2c", - "reference": "7a47189c7667ca69bcaafd19ef8a8941db449a2c", + "url": "https://api.github.com/repos/symfony/Config/zipball/d91be01336605db8da21b79bc771e46a7276d1bc", + "reference": "d91be01336605db8da21b79bc771e46a7276d1bc", "shasum": "" }, "require": { @@ -3856,21 +4071,21 @@ ], "description": "Symfony Config Component", "homepage": "http://symfony.com", - "time": "2015-03-12 10:28:44" + "time": "2015-03-30 15:54:10" }, { "name": "symfony/stopwatch", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Stopwatch", "source": { "type": "git", "url": "https://github.com/symfony/Stopwatch.git", - "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5" + "reference": "5f196e84b5640424a166d2ce9cca161ce1e9d912" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/ba4e774f71e2ce3e3f65cabac4031b9029972af5", - "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5", + "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/5f196e84b5640424a166d2ce9cca161ce1e9d912", + "reference": "5f196e84b5640424a166d2ce9cca161ce1e9d912", "shasum": "" }, "require": { @@ -3906,21 +4121,21 @@ ], "description": "Symfony Stopwatch Component", "homepage": "http://symfony.com", - "time": "2015-02-24 11:52:21" + "time": "2015-03-22 16:55:57" }, { "name": "symfony/yaml", - "version": "v2.6.5", + "version": "v2.6.6", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "0cd8e72071e46e15fc072270ae39ea1b66b10a9d" + "reference": "174f009ed36379a801109955fc5a71a49fe62dd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/0cd8e72071e46e15fc072270ae39ea1b66b10a9d", - "reference": "0cd8e72071e46e15fc072270ae39ea1b66b10a9d", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/174f009ed36379a801109955fc5a71a49fe62dd4", + "reference": "174f009ed36379a801109955fc5a71a49fe62dd4", "shasum": "" }, "require": { @@ -3956,7 +4171,7 @@ ], "description": "Symfony Yaml Component", "homepage": "http://symfony.com", - "time": "2015-03-12 10:28:44" + "time": "2015-03-30 15:54:10" } ], "aliases": [], diff --git a/config/database.php b/config/database.php index 3b823a1e0f..c57b4735b2 100644 --- a/config/database.php +++ b/config/database.php @@ -48,7 +48,7 @@ return [ 'sqlite' => [ 'driver' => 'sqlite', - 'database' => realpath(__DIR__ . '/../tests/database/db.sqlite'), + 'database' => ':memory:', 'prefix' => '', ], diff --git a/config/firefly.php b/config/firefly.php index da0ceee692..4996101cde 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -20,7 +20,8 @@ return [ 'accountRoles' => [ 'defaultAsset' => 'Default asset account', 'sharedAsset' => 'Shared asset account', - 'savingAsset' => 'Savings account' + 'savingAsset' => 'Savings account', + 'ccAsset' => 'Credit card', ], 'range_to_text' => [ @@ -31,6 +32,9 @@ return [ '6M' => 'half year', 'custom' => '(custom)' ], + 'ccTypes' => [ + 'monthlyFull' => 'Full payment every month' + ], 'range_to_name' => [ '1D' => 'one day', '1W' => 'one week', diff --git a/database/migrations/2014_12_13_190730_changes_for_v321.php b/database/migrations/2014_12_13_190730_changes_for_v321.php index 84a59ab82b..b381e774ab 100644 --- a/database/migrations/2014_12_13_190730_changes_for_v321.php +++ b/database/migrations/2014_12_13_190730_changes_for_v321.php @@ -1,13 +1,12 @@ each( function (BudgetLimit $bl) { - \Log::debug('Now at budgetLimit #' . $bl->id . ' with component_id: ' . $bl->component_id); + Log::debug('Now at budgetLimit #' . $bl->id . ' with component_id: ' . $bl->component_id); $component = Component::find($bl->component_id); if ($component) { - \Log::debug('Found component with id #' . $component->id . ' and name ' . $component->name); + Log::debug('Found component with id #' . $component->id . ' and name ' . $component->name); $budget = Budget::whereName($component->name)->whereUserId($component->user_id)->first(); if ($budget) { - \Log::debug('Found a budget with ID #' . $budget->id . ' and name ' . $budget->name); + Log::debug('Found a budget with ID #' . $budget->id . ' and name ' . $budget->name); $bl->budget_id = $budget->id; $bl->save(); - \Log::debug('Connected budgetLimit #' . $bl->id . ' to budget_id' . $budget->id); + Log::debug('Connected budgetLimit #' . $bl->id . ' to budget_id' . $budget->id); } else { - \Log::debug('Could not find a matching budget with name ' . $component->name); + Log::debug('Could not find a matching budget with name ' . $component->name); } } else { - \Log::debug('Could not find a component with id ' . $bl->component_id); + Log::debug('Could not find a component with id ' . $bl->component_id); } } ); - \Log::debug('Done with moveComponentIdToBudgetId()'); + //Log::debug('Done with moveComponentIdToBudgetId()'); } diff --git a/database/migrations/2015_02_27_210653_changes_for_v332.php b/database/migrations/2015_02_27_210653_changes_for_v332.php index 41d651d0cc..bbf876afd4 100644 --- a/database/migrations/2015_02_27_210653_changes_for_v332.php +++ b/database/migrations/2015_02_27_210653_changes_for_v332.php @@ -1,20 +1,31 @@ smallInteger('order',false,true)->default(0); + $table->smallInteger('order', false, true)->default(0); } ); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - // - } + } } diff --git a/database/migrations/2015_03_29_174140_changes_for_v336.php b/database/migrations/2015_03_29_174140_changes_for_v336.php new file mode 100644 index 0000000000..304fd78ac9 --- /dev/null +++ b/database/migrations/2015_03_29_174140_changes_for_v336.php @@ -0,0 +1,239 @@ +dropForeign('account_user_id'); + + } + ); + + + Schema::table( + 'accounts', function (Blueprint $table) { + $table->string('name', 255)->change(); + $table->dropColumn('virtual_balance'); + + // recreate foreign key + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + + // recreate unique: + $table->unique(['user_id', 'account_type_id', 'name']); + } + ); + + + /** + * BILLS + */ + // change field to be cryptable. + Schema::table( + 'bills', function (Blueprint $table) { + // drop foreign key: + $table->dropForeign('bill_user_id'); + + // drop unique: + $table->dropUnique('bill_user_id'); + } + ); + // + Schema::table( + 'bills', function (Blueprint $table) { + // raw query: + + DB::insert('ALTER TABLE `bills` CHANGE `name` `name` varchar(255) NOT NULL'); + DB::insert('ALTER TABLE `bills` CHANGE `match` `match` varchar(255) NOT NULL'); + $table->foreign('user_id', 'bills_uid_for')->references('id')->on('users')->onDelete('cascade'); + $table->unique(['user_id', 'name'], 'uid_name_unique'); + } + ); + + // remove a long forgotten index: + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->dropUnique('unique_limit'); + } + ); + + } + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + + /** + * ACCOUNTS + */ + // change field to be cryptable. + Schema::table( + 'accounts', function (Blueprint $table) { + // drop foreign key: + $table->dropForeign('accounts_user_id_foreign'); + + // drop unique: + $table->dropUnique('accounts_user_id_account_type_id_name_unique'); + } + ); + + Schema::table( + 'accounts', function (Blueprint $table) { + $table->text('name')->change(); + $table->decimal('virtual_balance', 10, 2)->default(0); + $table->foreign('user_id', 'account_user_id')->references('id')->on('users')->onDelete('cascade'); + } + ); + + /** + * BUDGETS + */ + // add active/inactive and encrypt. + Schema::table( + 'budgets', function (Blueprint $table) { + $table->smallInteger('active', false, true)->default(1); + $table->smallInteger('encrypted', false, true)->default(0); + + // drop foreign key: + $table->dropForeign('budgets_user_id_foreign'); + + // drop unique: + $table->dropUnique('budgets_user_id_name_unique'); + + } + ); + Schema::table( + 'budgets', function (Blueprint $table) { + $table->text('name')->change(); + $table->foreign('user_id', 'budget_user_id')->references('id')->on('users')->onDelete('cascade'); + } + ); + + // reinstate a long forgotten index: + Schema::table( + 'budget_limits', function (Blueprint $table) { + $table->unique(['budget_id', 'startdate'],'unique_limit'); + } + ); + + + /** + * BILLS + */ + // change field to be cryptable. + Schema::table( + 'bills', function (Blueprint $table) { + // drop foreign key: + $table->dropForeign('bills_uid_for'); + + // drop unique: + $table->dropUnique('uid_name_unique'); + } + ); + + Schema::table( + 'bills', function (Blueprint $table) { + // raw query: + try { + DB::insert('ALTER TABLE `bills` CHANGE `name` `name` TEXT NOT NULL'); + } catch (PDOException $e) { + // don't care. + } + try { + DB::insert('ALTER TABLE `bills` CHANGE `match` `match` TEXT NOT NULL'); + } catch (PDOException $e) { + // don't care. + } + $table->smallInteger('name_encrypted', false, true)->default(0); + $table->smallInteger('match_encrypted', false, true)->default(0); + $table->foreign('user_id', 'bill_user_id')->references('id')->on('users')->onDelete('cascade'); + } + ); + + /** + * CATEGORIES + */ + Schema::table( + 'categories', function (Blueprint $table) { + $table->smallInteger('encrypted', false, true)->default(0); + + // drop foreign key: + $table->dropForeign('categories_user_id_foreign'); + + // drop unique: + $table->dropUnique('categories_user_id_name_unique'); + + } + ); + Schema::table( + 'categories', function (Blueprint $table) { + $table->text('name')->change(); + $table->foreign('user_id', 'category_user_id')->references('id')->on('users')->onDelete('cascade'); + } + ); + + /** + * PIGGY BANKS + */ + Schema::table( + 'piggy_banks', function (Blueprint $table) { + $table->smallInteger('encrypted', false, true)->default(0); + + // drop foreign: + $table->dropForeign('piggybanks_account_id_foreign'); + + // drop unique: + $table->dropUnique('piggybanks_account_id_name_unique'); + + } + ); + Schema::table( + 'piggy_banks', function (Blueprint $table) { + try { + DB::insert('ALTER TABLE `piggy_banks` CHANGE `name` `name` TEXT NOT NULL'); + } catch (PDOException $e) { + // don't care. + } + $table->dropColumn(['repeats', 'rep_length', 'rep_every', 'rep_times']); + + // create index again: + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + } + ); + + /** + * REMINDERS + */ + Schema::table( + 'reminders', function (Blueprint $table) { + $table->smallInteger('encrypted', false, true)->default(0); + + + } + ); + + } + +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 4c2e245530..4fc3a0b3d2 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -21,7 +21,7 @@ class DatabaseSeeder extends Seeder $this->call('TransactionCurrencySeeder'); $this->call('TransactionTypeSeeder'); - if (App::environment() == 'testing' || App::environment() == 'homestead') { + if (App::environment() == 'testing' || App::environment() == 'homestead' || gethostname() == 'vagrant-firefly-iii') { $this->call('TestDataSeeder'); } } diff --git a/database/seeds/TestDataSeeder.php b/database/seeds/TestDataSeeder.php index 62ee9465bb..beb5a03eef 100644 --- a/database/seeds/TestDataSeeder.php +++ b/database/seeds/TestDataSeeder.php @@ -113,7 +113,9 @@ class TestDataSeeder extends Seeder */ public function createUsers() { - User::create(['email' => 'reset@example.com', 'password' => bcrypt('functional'), 'reset' => 'okokokokokokokokokokokokokokokok', 'remember_token' => null]); + User::create( + ['email' => 'reset@example.com', 'password' => bcrypt('functional'), 'reset' => 'okokokokokokokokokokokokokokokok', 'remember_token' => null] + ); User::create(['email' => 'functional@example.com', 'password' => bcrypt('functional'), 'reset' => null, 'remember_token' => null]); User::create(['email' => 'thegrumpydictator@gmail.com', 'password' => bcrypt('james'), 'reset' => null, 'remember_token' => null]); } @@ -238,7 +240,8 @@ class TestDataSeeder extends Seeder public function createPiggyBanks() { // account - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + $savings = $this->findAccount('Savings account'); + // some dates $endDate = clone $this->_startOfMonth; @@ -247,7 +250,7 @@ class TestDataSeeder extends Seeder $endDate->addMonths(4); $nextYear->addYear()->subDay(); - $end = $endDate->format('Y-m-d'); + $end = $endDate->format('Y-m-d'); // piggy bank $newCamera = PiggyBank::create( @@ -257,10 +260,6 @@ class TestDataSeeder extends Seeder '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, @@ -278,10 +277,6 @@ class TestDataSeeder extends Seeder '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, @@ -295,22 +290,18 @@ class TestDataSeeder extends Seeder * New: create no less than eight piggy banks that * create all sorts of reminders */ - $list = ['week','quarter','month','year']; + $list = ['week', 'quarter', 'month', 'year']; $nextYear = clone $this->_startOfMonth; $nextYear->addYear(); - foreach($list as $entry) { + foreach ($list as $entry) { PiggyBank::create( [ 'account_id' => $savings->id, - 'name' => $entry.' piggy bank with target date.', + 'name' => $entry . ' piggy bank with target date.', 'targetamount' => 1000, 'startdate' => $this->som, 'targetdate' => $nextYear, - 'repeats' => 0, - 'rep_length' => null, - 'rep_every' => 0, - 'rep_times' => null, 'reminder' => $entry, 'reminder_skip' => 0, 'remind_me' => 1, @@ -320,14 +311,10 @@ class TestDataSeeder extends Seeder PiggyBank::create( [ 'account_id' => $savings->id, - 'name' => $entry.' piggy bank without target date.', + 'name' => $entry . ' piggy bank without target date.', 'targetamount' => 1000, 'startdate' => $this->som, 'targetdate' => null, - 'repeats' => 0, - 'rep_length' => null, - 'rep_every' => 0, - 'rep_times' => null, 'reminder' => $entry, 'reminder_skip' => 0, 'remind_me' => 1, @@ -337,6 +324,26 @@ class TestDataSeeder extends Seeder } } + /** + * @param $name + * + * @return Account|null + */ + protected function findAccount($name) + { + // account + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + /** @var Account $account */ + foreach (Account::get() as $account) { + if ($account->name == $name && $user->id == $account->user_id) { + return $account; + break; + } + } + + return null; + } + /** * */ @@ -431,20 +438,20 @@ class TestDataSeeder extends Seeder public function createMonthlyExpenses(Carbon $date) { // 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(); - $house = Category::whereName('House')->orderBy('id', 'DESC')->first(); + $checking = $this->findAccount('Checking account'); + $savings = $this->findAccount('Savings account'); + $landLord = $this->findAccount('Land lord'); + $utilities = $this->findAccount('Utilities company'); + $television = $this->findAccount('TV company'); + $phone = $this->findAccount('Phone agency'); + $employer = $this->findAccount('Employer'); + $bills = $this->findBudget('Bills'); + $house = $this->findCategory('House'); $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(); + $rentBill = $this->findBill('Rent'); $cur = $date->format('Y-m-d'); $formatted = $date->format('F Y'); @@ -487,21 +494,102 @@ class TestDataSeeder extends Seeder } + /** + * @param $name + * + * @return Budget|null + */ + protected function findBudget($name) + { + // account + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + /** @var Budget $budget */ + foreach (Budget::get() as $budget) { + if ($budget->name == $name && $user->id == $budget->user_id) { + return $budget; + break; + } + } + + return null; + } + + /** + * @param $name + * + * @return PiggyBank|null + */ + protected function findPiggyBank($name) + { + // account + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + /** @var Budget $budget */ + foreach (PiggyBank::get() as $piggyBank) { + $account = $piggyBank->account()->first(); + if ($piggyBank->name == $name && $user->id == $account->user_id) { + return $piggyBank; + break; + } + } + + return null; + } + + /** + * @param $name + * + * @return Category|null + */ + protected function findCategory($name) + { + // account + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + /** @var Category $category */ + foreach (Category::get() as $category) { + if ($category->name == $name && $user->id == $category->user_id) { + return $category; + break; + } + } + + return null; + } + + /** + * @param $name + * + * @return Bill|null + */ + protected function findBill($name) + { + // account + $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); + /** @var Bill $bill */ + foreach (Bill::get() as $bill) { + if ($bill->name == $name && $user->id == $bill->user_id) { + return $bill; + break; + } + } + + return null; + } + /** * @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(); + $checking = $this->findAccount('Checking account'); + $shopOne = $this->findAccount('Groceries House'); + $shopTwo = $this->findAccount('Super savers'); + $lunchHouse = $this->findAccount('Lunch House'); + $lunch = $this->findCategory('Lunch'); + $daily = $this->findCategory('DailyGroceries'); $euro = TransactionCurrency::whereCode('EUR')->first(); $withdrawal = TransactionType::whereType('Withdrawal')->first(); - $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); + $groceries = $this->findBudget('Groceries'); $shops = [$shopOne, $shopTwo]; @@ -534,9 +622,9 @@ class TestDataSeeder extends Seeder { $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(); + $checking = $this->findAccount('Checking account'); + $savings = $this->findAccount('Savings account'); + $buyMore = $this->findAccount('Buy More'); $withdrawal = TransactionType::whereType('Withdrawal')->first(); $user = User::whereEmail('thegrumpydictator@gmail.com')->first(); @@ -571,13 +659,13 @@ class TestDataSeeder extends Seeder // piggy bank event // add money to this piggy bank // create a piggy bank event to match: - $checking = Account::whereName('Checking account')->orderBy('id', 'DESC')->first(); - $savings = Account::whereName('Savings account')->orderBy('id', 'DESC')->first(); + $checking = $this->findAccount('Checking account'); + $savings = $this->findAccount('Savings account'); $transfer = TransactionType::whereType('Transfer')->first(); $euro = TransactionCurrency::whereCode('EUR')->first(); - $groceries = Budget::whereName('Groceries')->orderBy('id', 'DESC')->first(); - $house = Category::whereName('House')->orderBy('id', 'DESC')->first(); - $piggyBank = PiggyBank::whereName('New camera')->orderBy('id', 'DESC')->first(); + $groceries = $this->findBudget('Groceries'); + $house = $this->findCategory('House'); + $piggyBank = $this->findPiggyBank('New camera'); $intoPiggy = $this->createJournal( ['from' => $checking, 'to' => $savings, 'amount' => 100, 'transactionType' => $transfer, 'description' => 'Money for piggy', 'date' => $this->yaeom, 'transactionCurrency' => $euro, 'category' => $house, 'budget' => $groceries] diff --git a/pu.sh b/pu.sh index ad08d5c969..fac111b016 100755 --- a/pu.sh +++ b/pu.sh @@ -1,10 +1,13 @@ #!/bin/bash -# create DB if not exists +# backup .env file. +cp .env .env.backup -if [ ! -f tests/database/db.sqlite ]; then - touch tests/database/db.sqlite - php artisan migrate --seed -fi +# set testing environment +cp .env.testing .env +# test! phpunit --verbose + +# restore .env file +mv .env.backup .env diff --git a/public/css/sb-admin-2.css b/public/css/sb-admin-2.css index 4a2cd36ca3..2123c0b1fd 100755 --- a/public/css/sb-admin-2.css +++ b/public/css/sb-admin-2.css @@ -299,7 +299,7 @@ table.dataTable thead .sorting:after { font-size: 40px; } .large { - font-size: 30px; + font-size: 20px; } .panel-green { diff --git a/resources/views/accounts/create.blade.php b/resources/views/accounts/create.blade.php index e6ae51dd37..f8c9a2d403 100644 --- a/resources/views/accounts/create.blade.php +++ b/resources/views/accounts/create.blade.php @@ -36,7 +36,8 @@ {!! ExpandedForm::balance('openingBalance') !!} {!! ExpandedForm::date('openingBalanceDate', date('Y-m-d')) !!} - {!! ExpandedForm::select('accountRole',Config::get('firefly.accountRoles')) !!} + {!! ExpandedForm::select('accountRole',Config::get('firefly.accountRoles'),null,['helpText' => 'Any extra options resulting from your choice can be set later.']) !!} + {!! ExpandedForm::balance('virtualBalance') !!} diff --git a/resources/views/accounts/delete.blade.php b/resources/views/accounts/delete.blade.php index d32bc352f1..b45c9e86de 100644 --- a/resources/views/accounts/delete.blade.php +++ b/resources/views/accounts/delete.blade.php @@ -10,16 +10,19 @@

- Are you sure? + Are you sure that you want to delete the {{strtolower($account->accountType->type)}} "{{$account->name}}"?

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

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

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

+ @endif + @if($account->piggyBanks()->count() > 0) +

+ {{ucfirst($account->accountType->type)}} "{{{$account->name}}}" still has {{$account->piggyBanks()->count()}} piggy bank(s) associated to it. These will be deleted as well.

@endif -

Cancel diff --git a/resources/views/accounts/edit.blade.php b/resources/views/accounts/edit.blade.php index d714a0dc86..166aab8f0f 100644 --- a/resources/views/accounts/edit.blade.php +++ b/resources/views/accounts/edit.blade.php @@ -2,6 +2,9 @@ @section('content') {!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $account) !!} {!! Form::model($account, ['class' => 'form-horizontal','id' => 'update','url' => route('accounts.update',$account->id)]) !!} + + +

@@ -28,12 +31,26 @@ {!! ExpandedForm::balance('openingBalance',null, ['currency' => $openingBalance ? $openingBalance->transactionCurrency : null]) !!} {!! ExpandedForm::date('openingBalanceDate') !!} {!! ExpandedForm::select('accountRole',Config::get('firefly.accountRoles')) !!} + {!! ExpandedForm::balance('virtualBalance',null) !!} {!! Form::hidden('id',$account->id) !!} @endif {!! ExpandedForm::checkbox('active','1') !!}
+ + @if(Session::get('preFilled')['accountRole'] == 'ccAsset') +
+
+ Credit card options +
+
+ {!! ExpandedForm::select('ccType',Config::get('firefly.ccTypes')) !!} + {!! ExpandedForm::date('ccMonthlyPaymentDate',null,['helpText' => 'Select any year and any month, it will be ignored anway. Only the day of the month is relevant.']) !!} +
+
+ @endif +
diff --git a/resources/views/bills/delete.blade.php b/resources/views/bills/delete.blade.php index fd9f04d5ad..b78505c8ee 100644 --- a/resources/views/bills/delete.blade.php +++ b/resources/views/bills/delete.blade.php @@ -10,9 +10,16 @@

- Are you sure? + Are you sure that you want to delete bill "{{{$bill->name}}}"?

+ @if($bill->transactionjournals()->count() > 0) +

+ Bill "{{{$bill->name}}}" still has {{$bill->transactionjournals()->count()}} transactions connected + to it. These will not be removed but will lose their connection to this bill. +

+ @endif +

Cancel diff --git a/resources/views/budgets/delete.blade.php b/resources/views/budgets/delete.blade.php index d4acb2d795..c5f4870785 100644 --- a/resources/views/budgets/delete.blade.php +++ b/resources/views/budgets/delete.blade.php @@ -10,9 +10,16 @@

- Are you sure? + Are you sure that you want to delete budget "{{{$budget->name}}}"?

+ @if($budget->transactionjournals()->count() > 0) +

+ Budget "{{{$budget->name}}}" still has {{$budget->transactionjournals()->count()}} transactions connected + to it. These will not be removed but will lose their connection to this budget. +

+ @endif +

Cancel diff --git a/resources/views/budgets/edit.blade.php b/resources/views/budgets/edit.blade.php index a2860bc391..51883f5319 100644 --- a/resources/views/budgets/edit.blade.php +++ b/resources/views/budgets/edit.blade.php @@ -16,6 +16,7 @@ Mandatory fields

+ {!! ExpandedForm::checkbox('active') !!} {!! ExpandedForm::text('name') !!}
diff --git a/resources/views/budgets/index.blade.php b/resources/views/budgets/index.blade.php index 6d1642f930..e9dc51d410 100644 --- a/resources/views/budgets/index.blade.php +++ b/resources/views/budgets/index.blade.php @@ -64,6 +64,7 @@
+
@@ -142,7 +143,27 @@
Create new budget
+
+ @if($inactive->count() > 0) +
+
+
+ + Inactive budgets +
+
+ @foreach($inactive as $index => $budget) + @if($index != count($inactive)-1) + {{$budget->name}}, + @else + {{$budget->name}} + @endif + @endforeach +
+
+
+ @endif diff --git a/resources/views/budgets/show.blade.php b/resources/views/budgets/show.blade.php index bb218f1246..b3554aa66f 100644 --- a/resources/views/budgets/show.blade.php +++ b/resources/views/budgets/show.blade.php @@ -6,6 +6,21 @@
Overview + + + +
+
+ + +
+
@@ -46,7 +61,8 @@ ?> @if($overspent) amount / $rep->spentInRepetition()*100; + $spent = floatval($rep->spentInRepetition()); + $pct = $spent != 0 ? ($rep->amount / $spent)*100 : 0; ?>
@@ -54,7 +70,8 @@
@else spentInRepetition() / $rep->amount*100; + $amount = floatval($rep->amount); + $pct = $amount != 0 ? ($rep->spentInRepetition() / $amount)*100 : 0; ?>
diff --git a/resources/views/categories/delete.blade.php b/resources/views/categories/delete.blade.php index 77353b9faf..04bd21f0fd 100644 --- a/resources/views/categories/delete.blade.php +++ b/resources/views/categories/delete.blade.php @@ -10,9 +10,16 @@

- Are you sure? + Are you sure that you want to delete category "{{$category->name}}"?

+ @if($category->transactionjournals()->count() > 0) +

+ Category "{{{$category->name}}}" still has {{$category->transactionjournals()->count()}} transactions connected + to it. These will not be removed but will lose their connection to this category. +

+ @endif +

Cancel diff --git a/resources/views/currency/delete.blade.php b/resources/views/currency/delete.blade.php index b3c0e70e64..3245be5894 100644 --- a/resources/views/currency/delete.blade.php +++ b/resources/views/currency/delete.blade.php @@ -10,7 +10,7 @@

- Are you sure? + Are you sure that you want to delete currency "{{{$currency->name}}}"?

diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php index c8d767b00a..0847a1c967 100644 --- a/resources/views/errors/503.blade.php +++ b/resources/views/errors/503.blade.php @@ -1,6 +1,6 @@ - +