From 3598780d542c88e534f671600eac4186975f3138 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 14 Oct 2016 19:18:00 +0200 Subject: [PATCH 01/45] This should at least catch #357 --- app/Import/Importer/CsvImporter.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Import/Importer/CsvImporter.php b/app/Import/Importer/CsvImporter.php index 797335cb1d..0cc869fcd4 100644 --- a/app/Import/Importer/CsvImporter.php +++ b/app/Import/Importer/CsvImporter.php @@ -111,9 +111,14 @@ class CsvImporter implements ImporterInterface // set some vars: $object->setUser($this->job->user); $config = $this->job->configuration; + $json = json_encode($row); + + if ($json === false) { + throw new FireflyException(sprintf('Could not process row #%d. Are you sure the uploaded file is encoded as "UTF-8"?', $index)); + } // hash the row: - $hash = hash('sha256', json_encode($row)); + $hash = hash('sha256', $json); $object->importValue('hash', 100, $hash); // and this is the point where the specifix go to work. From ec8cf2c459d66b3a08335d117478e912ef8cb11e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 14 Oct 2016 19:48:19 +0200 Subject: [PATCH 02/45] New preferences screen. --- public/css/firefly.css | 11 +- resources/lang/en_US/firefly.php | 4 + resources/views/preferences/index.twig | 404 +++++++++++++------------ 3 files changed, 225 insertions(+), 194 deletions(-) diff --git a/public/css/firefly.css b/public/css/firefly.css index 354ea0b15b..bd46d1e920 100644 --- a/public/css/firefly.css +++ b/public/css/firefly.css @@ -20,6 +20,13 @@ body.waiting * { height: 1px; } +.preferences-box { + border:1px #ddd solid; + border-radius: 4px 4px 0 0; + padding: 15px; + margin: 15px; +} + #map-canvas { height: 100%; margin: 0; @@ -76,6 +83,6 @@ body.waiting * { } .loading { - background:url('/images/loading-small.gif') no-repeat center center; - min-height:30px; + background: url('/images/loading-small.gif') no-repeat center center; + min-height: 30px; } \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 4530d8b6cd..6ff9107ef4 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -273,6 +273,10 @@ return [ 'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)', 'pref_save_settings' => 'Save settings', 'saved_preferences' => 'Preferences saved!', + 'preferences_general' => 'General', + 'preferences_frontpage' => 'Frontpage', + 'preferences_security' => 'Security', + 'preferences_layout' => 'Layout', 'transaction_page_size_title' => 'Page size', 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', 'transaction_page_size_label' => 'Page size', diff --git a/resources/views/preferences/index.twig b/resources/views/preferences/index.twig index 33a8534f55..6ec4b36343 100644 --- a/resources/views/preferences/index.twig +++ b/resources/views/preferences/index.twig @@ -7,217 +7,237 @@ {% block content %}
+
-
-
-
-

{{ 'pref_home_screen_accounts'|_ }}

-
-
-

{{ 'pref_home_screen_accounts_help'|_ }}

- {% for account in accounts %} -
-
-
-
+ + +
+
+ +
+
+

Transactions

+
+ + + + + + + + + + + + + + + {% for transaction in transactions %} + + + + + + + + + + + {# + + + + + + + + #} + {% endfor %} + +
{{ trans('list.description') }}{{ trans('list.source_account') }}Δ{{ trans('list.destination_account') }}Δ{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}
#{{ transaction.source_id }} + {% if transaction.description == "" %} + {{ journal.description }} + {% else %} + {{ transaction.description }} + {% endif %} + + {{ transaction.source_account_name }} + + {{ formatAmountWithCode(transaction.source_account_before,journal.transactionCurrency.code) }} + ⟶ {{ formatAmountWithCode(transaction.source_account_after,journal.transactionCurrency.code) }} + + {{ transaction.destination_account_name }} + + {{ formatAmountWithCode(transaction.destination_account_before,journal.transactionCurrency.code) }} + ⟶ {{ formatAmountWithCode(transaction.destination_account_after,journal.transactionCurrency.code) }} + + {% if journal.transactiontype.type == 'Deposit' %} + + {{ formatAmountWithCode(transaction.destination_amount, journal.transactionCurrency.code) }} + + {% endif %} + {% if journal.transactiontype.type == 'Withdrawal' %} + + {{ formatAmountWithCode(transaction.source_amount, journal.transactionCurrency.code) }} + + {% endif %} + {% if journal.transactiontype.type == 'Transfer' %} + + {{ formatAmountPlainWithCode(transaction.destination_amount, journal.transactionCurrency.code) }} + {% endif %} + + {{ transactionIdBudgets(transaction.source_id) }} + + {{ transactionIdCategories(transaction.source_id) }} +
+ {% if (index+1) != transactions|length or what == 'transfer' %} + {{ t.description }} + {% endif %} + {{ t.account.name }} ({{ t.account.accounttype.type|_ }}){{ t.sum|formatAmount }}{{ t.before|formatAmount }} → {{ (t.sum+t.before)|formatAmount }} + {% if (index+1) != transactions|length or what == 'transfer' %} + {{ transactionBudgets(t)|raw }} + {% endif %} + + {% if (index+1) != transactions|length or what == 'transfer' %} + {{ transactionCategories(t)|raw }} + {% endif %} +
+
+
+
+ {% endblock %} From 92553cbc7ec39157eea03abddc43fa5c82e6bf7c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 15 Oct 2016 12:41:45 +0200 Subject: [PATCH 11/45] Add icon, missing translation [skip ci] --- resources/lang/en_US/firefly.php | 1 + resources/views/transactions/show.twig | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 90a0a33d8a..04bab8676f 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -714,6 +714,7 @@ return [ 'transaction_journal_information' => 'Transaction information', 'transaction_journal_meta' => 'Meta information', + 'total_amount' => 'Total amount', // administration 'administration' => 'Administration', diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index aa4729a440..dffc57e658 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -47,8 +47,8 @@
From da60bfbcff6ea7765d3a83f367222d04a8d0c4e0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 15 Oct 2016 14:05:56 +0200 Subject: [PATCH 12/45] Better text [skip ci] --- resources/views/transactions/show.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index dffc57e658..5a7e7f42d3 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -122,14 +122,14 @@ {{ journal.getMeta('invoice_date').formatLocalized(monthAndDayFormat) }} {% endif %} - {% if journal.hasMeta('internal_reference') %} + {% if journal.hasMeta('internal_reference') and journal.getMeta('internal_reference') != "" %} {{ trans('list.internal_reference') }} {{ journal.getMeta('internal_reference') }} {% endif %} - {% if journal.hasMeta('notes') %} + {% if journal.hasMeta('notes') and journal.getMeta('notes') != "" %} {{ trans('list.notes') }} {{ journal.getMeta('notes')|nl2br }} @@ -237,7 +237,7 @@ {% for transaction in transactions %} - #{{ transaction.source_id }} + {% if transaction.description == "" %} {{ journal.description }} {% else %} From 9bf980431e628593cdd949e058610d512a51b592 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 15 Oct 2016 14:07:51 +0200 Subject: [PATCH 13/45] Remove unused methods. --- .../Journal/JournalRepository.php | 94 ------------------- .../Journal/JournalRepositoryInterface.php | 16 ---- 2 files changed, 110 deletions(-) diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index fce3033619..c92b27c681 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -56,42 +56,6 @@ class JournalRepository implements JournalRepositoryInterface $this->user = $user; } - /** - * Returns the amount in the account before the specified transaction took place. - * - * @param Transaction $transaction - * - * @return string - */ - public function balanceBeforeTransaction(Transaction $transaction): string - { - // some dates from journal - $journal = $transaction->transactionJournal; - $query = Transaction:: - leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $transaction->account_id) - ->where('transaction_journals.user_id', $this->user->id) - ->where( - function (Builder $q) use ($journal) { - $q->where('transaction_journals.date', '<', $journal->date->format('Y-m-d')); - $q->orWhere( - function (Builder $qq) use ($journal) { - $qq->where('transaction_journals.date', '=', $journal->date->format('Y-m-d')); - $qq->where('transaction_journals.order', '>', $journal->order); - } - ); - - } - ) - ->where('transactions.id', '!=', $transaction->id) - ->whereNull('transactions.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->groupBy('transaction_journals.id'); - $sum = $query->sum('transactions.amount'); - - return strval($sum); - } - /** * @param TransactionJournal $journal * @@ -219,64 +183,6 @@ class JournalRepository implements JournalRepositoryInterface return $events; } - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public function getTransactions(TransactionJournal $journal): Collection - { - $transactions = new Collection; - $fields = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id', - 'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount', - DB::raw('SUM(transactions.amount) AS sum')]; - $groupBy = ['transactions.id', 'transactions.created_at', 'transactions.updated_at', 'transactions.deleted_at', 'transactions.account_id', - 'transactions.transaction_journal_id', 'transactions.description', 'transactions.amount']; - switch ($journal->transactionType->type) { - case TransactionType::DEPOSIT: - /** @var Collection $transactions */ - $transactions = $journal->transactions() - ->groupBy('transactions.account_id') - ->where('amount', '<', 0) - ->groupBy($groupBy) - ->orderBy('amount', 'ASC')->get($fields); - $final = $journal->transactions() - ->groupBy($groupBy) - ->where('amount', '>', 0) - ->orderBy('amount', 'ASC')->first($fields); - $transactions->push($final); - break; - case TransactionType::TRANSFER: - - /** @var Collection $transactions */ - $transactions = $journal->transactions() - ->groupBy($groupBy) - ->orderBy('transactions.id')->get($fields); - break; - case TransactionType::WITHDRAWAL: - - /** @var Collection $transactions */ - $transactions = $journal->transactions() - ->where('amount', '>', 0) - ->groupBy($groupBy) - ->orderBy('amount', 'ASC')->get($fields); - $final = $journal->transactions() - ->where('amount', '<', 0) - ->groupBy($groupBy) - ->orderBy('amount', 'ASC')->first($fields); - $transactions->push($final); - break; - } - // foreach do balance thing - $transactions->each( - function (Transaction $t) { - $t->before = $this->balanceBeforeTransaction($t); - } - ); - - return $transactions; - } - /** * @param array $data * diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index cc73ea9771..e12acda50a 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -27,15 +27,6 @@ use Illuminate\Support\Collection; interface JournalRepositoryInterface { - /** - * Returns the amount in the account before the specified transaction took place. - * - * @param Transaction $transaction - * - * @return string - */ - public function balanceBeforeTransaction(Transaction $transaction): string; - /** * Deletes a journal. * @@ -90,13 +81,6 @@ interface JournalRepositoryInterface */ public function getPiggyBankEvents(TransactionJournal $journal): Collection; - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public function getTransactions(TransactionJournal $journal): Collection; - /** * @param array $data * From 5dcbdec4914b6248e9dab1e48497dc1488c28089 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 15 Oct 2016 18:52:21 +0200 Subject: [PATCH 14/45] Update composer file and update routine. [skip ci] --- app/Console/Commands/UpgradeDatabase.php | 19 +- composer.json | 1 + composer.lock | 221 ++++++++++++----------- 3 files changed, 122 insertions(+), 119 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 7b90144bd1..e525531d2f 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -104,15 +104,16 @@ class UpgradeDatabase extends Command $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); break 2; } - - // give both a new identifier: - $transaction->identifier = $identifier; - $transaction->save(); - $opposing->identifier = $identifier; - $opposing->save(); - $processed[] = $transaction->id; - $processed[] = $opposing->id; - $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id)); + if (!is_null($opposing)) { + // give both a new identifier: + $transaction->identifier = $identifier; + $transaction->save(); + $opposing->identifier = $identifier; + $opposing->save(); + $processed[] = $transaction->id; + $processed[] = $opposing->id; + $this->line(sprintf('Database upgrade for journal #%d, transactions #%d and #%d', $journalId, $transaction->id, $opposing->id)); + } $identifier++; } } diff --git a/composer.json b/composer.json index 5bfe78718a..ee3c0c430e 100755 --- a/composer.json +++ b/composer.json @@ -76,6 +76,7 @@ "post-update-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postUpdate", "php artisan firefly:upgrade-instructions", + "php artisan firefly:upgrade-database", "php artisan firefly:verify", "php artisan optimize" ] diff --git a/composer.lock b/composer.lock index 16feffb837..e54440d03f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ce5008a06e815bab7e4027161939b57e", - "content-hash": "8a35f78c306f3ddd1080d7c1b687f77d", + "hash": "c370a68602f3922d07ad8b6a5722718a", + "content-hash": "ab1379ea8b6e5f438eba8f5771b49a59", "packages": [ { "name": "bacon/bacon-qr-code", @@ -1024,16 +1024,16 @@ }, { "name": "laravel/framework", - "version": "v5.3.10", + "version": "v5.3.18", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6febb0ee61999cde3bc7b2963c8903032bb22691" + "reference": "9bee167d173857c25966c19afdaa66f127ca6784" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6febb0ee61999cde3bc7b2963c8903032bb22691", - "reference": "6febb0ee61999cde3bc7b2963c8903032bb22691", + "url": "https://api.github.com/repos/laravel/framework/zipball/9bee167d173857c25966c19afdaa66f127ca6784", + "reference": "9bee167d173857c25966c19afdaa66f127ca6784", "shasum": "" }, "require": { @@ -1148,7 +1148,7 @@ "framework", "laravel" ], - "time": "2016-09-20 13:46:16" + "time": "2016-10-08 01:51:20" }, { "name": "laravelcollective/html", @@ -1332,16 +1332,16 @@ }, { "name": "league/flysystem", - "version": "1.0.27", + "version": "1.0.28", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9" + "reference": "a9663643ff2d16d7f66ed1e0d3212c5491bc9044" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/50e2045ed70a7e75a5e30bc3662904f3b67af8a9", - "reference": "50e2045ed70a7e75a5e30bc3662904f3b67af8a9", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a9663643ff2d16d7f66ed1e0d3212c5491bc9044", + "reference": "a9663643ff2d16d7f66ed1e0d3212c5491bc9044", "shasum": "" }, "require": { @@ -1411,7 +1411,7 @@ "sftp", "storage" ], - "time": "2016-08-10 08:55:11" + "time": "2016-10-07 12:20:37" }, { "name": "maximebf/debugbar", @@ -1805,16 +1805,16 @@ }, { "name": "psr/log", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "5277094ed527a1c4477177d102fe4c53551953e0" + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0", - "reference": "5277094ed527a1c4477177d102fe4c53551953e0", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, "require": { @@ -1848,7 +1848,7 @@ "psr", "psr-3" ], - "time": "2016-09-19 16:02:08" + "time": "2016-10-10 12:19:37" }, { "name": "psy/psysh", @@ -1924,16 +1924,16 @@ }, { "name": "ramsey/uuid", - "version": "3.5.0", + "version": "3.5.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786" + "reference": "a07797b986671b0dc823885a81d5e3516b931599" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/a6d15c8618ea3951fd54d34e326b68d3d0bc0786", - "reference": "a6d15c8618ea3951fd54d34e326b68d3d0bc0786", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/a07797b986671b0dc823885a81d5e3516b931599", + "reference": "a07797b986671b0dc823885a81d5e3516b931599", "shasum": "" }, "require": { @@ -2000,7 +2000,7 @@ "identifier", "uuid" ], - "time": "2016-08-02 18:39:32" + "time": "2016-10-02 15:51:17" }, { "name": "rcrowe/twigbridge", @@ -2068,23 +2068,23 @@ }, { "name": "rmccue/requests", - "version": "v1.6.1", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/rmccue/Requests.git", - "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea" + "reference": "87932f52ffad70504d93f04f15690cf16a089546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea", - "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546", + "reference": "87932f52ffad70504d93f04f15690cf16a089546", "shasum": "" }, "require": { "php": ">=5.2" }, "require-dev": { - "satooshi/php-coveralls": "dev-master" + "requests/test-server": "dev-master" }, "type": "library", "autoload": { @@ -2113,7 +2113,7 @@ "iri", "sockets" ], - "time": "2014-05-18 04:59:02" + "time": "2016-10-13 00:11:37" }, { "name": "swiftmailer/swiftmailer", @@ -2170,16 +2170,16 @@ }, { "name": "symfony/class-loader", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba" + "reference": "bcb072aba46ddf3b1a496438c63be6be647739aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/2d0ba77c46ecc96a6641009a98f72632216811ba", - "reference": "2d0ba77c46ecc96a6641009a98f72632216811ba", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/bcb072aba46ddf3b1a496438c63be6be647739aa", + "reference": "bcb072aba46ddf3b1a496438c63be6be647739aa", "shasum": "" }, "require": { @@ -2222,24 +2222,25 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2016-08-23 13:39:15" + "time": "2016-09-06 23:30:54" }, { "name": "symfony/console", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563" + "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563", - "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563", + "url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0", + "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0", "shasum": "" }, "require": { "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { @@ -2282,20 +2283,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-08-19 06:48:39" + "time": "2016-09-28 00:11:12" }, { "name": "symfony/debug", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11" + "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/34f6ac18c2974ca5fce68adf419ee7d15def6f11", - "reference": "34f6ac18c2974ca5fce68adf419ee7d15def6f11", + "url": "https://api.github.com/repos/symfony/debug/zipball/e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", + "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", "shasum": "" }, "require": { @@ -2339,11 +2340,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-08-23 13:39:15" + "time": "2016-09-06 11:02:40" }, { "name": "symfony/event-dispatcher", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2403,16 +2404,16 @@ }, { "name": "symfony/finder", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577" + "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577", - "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577", + "url": "https://api.github.com/repos/symfony/finder/zipball/205b5ffbb518a98ba2ae60a52656c4a31ab00c6f", + "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f", "shasum": "" }, "require": { @@ -2448,20 +2449,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-08-26 12:04:02" + "time": "2016-09-28 00:11:12" }, { "name": "symfony/http-foundation", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4" + "reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/63592e00fd90632b57ee50220a1ddb29b6bf3bb4", - "reference": "63592e00fd90632b57ee50220a1ddb29b6bf3bb4", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5114f1becca9f29e3af94374f1689c83c1aa3d97", + "reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97", "shasum": "" }, "require": { @@ -2501,20 +2502,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-08-22 12:11:19" + "time": "2016-09-21 20:55:10" }, { "name": "symfony/http-kernel", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3" + "reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aeda215d6b01f119508c090d2a09ebb5b0bc61f3", - "reference": "aeda215d6b01f119508c090d2a09ebb5b0bc61f3", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/dc339d6eebadfa6e39c52868b4d4a715eff13c69", + "reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69", "shasum": "" }, "require": { @@ -2583,7 +2584,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-09-03 15:28:24" + "time": "2016-10-03 19:01:06" }, { "name": "symfony/polyfill-mbstring", @@ -2754,16 +2755,16 @@ }, { "name": "symfony/process", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e64e93041c80e77197ace5ab9385dedb5a143697" + "reference": "66de154ae86b1a07001da9fbffd620206e4faf94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e64e93041c80e77197ace5ab9385dedb5a143697", - "reference": "e64e93041c80e77197ace5ab9385dedb5a143697", + "url": "https://api.github.com/repos/symfony/process/zipball/66de154ae86b1a07001da9fbffd620206e4faf94", + "reference": "66de154ae86b1a07001da9fbffd620206e4faf94", "shasum": "" }, "require": { @@ -2799,11 +2800,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-08-16 14:58:24" + "time": "2016-09-29 14:13:09" }, { "name": "symfony/routing", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", @@ -2878,16 +2879,16 @@ }, { "name": "symfony/translation", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a35edc277513c9bc0f063ca174c36b346f974528" + "reference": "93013a18d272e59dab8e67f583155b78c68947eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a35edc277513c9bc0f063ca174c36b346f974528", - "reference": "a35edc277513c9bc0f063ca174c36b346f974528", + "url": "https://api.github.com/repos/symfony/translation/zipball/93013a18d272e59dab8e67f583155b78c68947eb", + "reference": "93013a18d272e59dab8e67f583155b78c68947eb", "shasum": "" }, "require": { @@ -2938,20 +2939,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-08-05 08:37:39" + "time": "2016-09-06 11:02:40" }, { "name": "symfony/var-dumper", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "62ee73706c421654a4c840028954510277f7dfc8" + "reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/62ee73706c421654a4c840028954510277f7dfc8", - "reference": "62ee73706c421654a4c840028954510277f7dfc8", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/70bfe927b86ba9999aeebd829715b0bb2cd39a10", + "reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10", "shasum": "" }, "require": { @@ -3001,20 +3002,20 @@ "debug", "dump" ], - "time": "2016-08-31 09:05:42" + "time": "2016-09-29 14:13:09" }, { "name": "twig/twig", - "version": "v1.25.0", + "version": "v1.26.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "f16a634ab08d87e520da5671ec52153d627f10f6" + "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/f16a634ab08d87e520da5671ec52153d627f10f6", - "reference": "f16a634ab08d87e520da5671ec52153d627f10f6", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d", + "reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d", "shasum": "" }, "require": { @@ -3027,7 +3028,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.25-dev" + "dev-master": "1.26-dev" } }, "autoload": { @@ -3062,7 +3063,7 @@ "keywords": [ "templating" ], - "time": "2016-09-21 23:05:12" + "time": "2016-10-05 18:57:41" }, { "name": "vlucas/phpdotenv", @@ -3481,16 +3482,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", "shasum": "" }, "require": { @@ -3522,7 +3523,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-06-10 09:48:41" + "time": "2016-09-30 07:12:33" }, { "name": "phpdocumentor/type-resolver", @@ -3879,16 +3880,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.5.5", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a57126dc681b08289fef6ac96a48e30656f84350" + "reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a57126dc681b08289fef6ac96a48e30656f84350", - "reference": "a57126dc681b08289fef6ac96a48e30656f84350", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60c32c5b5e79c2248001efa2560f831da11cc2d7", + "reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7", "shasum": "" }, "require": { @@ -3922,7 +3923,6 @@ "ext-pdo": "*" }, "suggest": { - "ext-tidy": "*", "ext-xdebug": "*", "phpunit/php-invoker": "~1.1" }, @@ -3932,7 +3932,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.5.x-dev" + "dev-master": "5.6.x-dev" } }, "autoload": { @@ -3958,20 +3958,20 @@ "testing", "xunit" ], - "time": "2016-09-21 14:40:13" + "time": "2016-10-07 13:03:26" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.2.7", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a" + "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", - "reference": "546898a2c0c356ef2891b39dd7d07f5d82c8ed0a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", + "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2", "shasum": "" }, "require": { @@ -4017,7 +4017,7 @@ "mock", "xunit" ], - "time": "2016-09-06 16:07:45" + "time": "2016-10-09 07:01:45" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -4534,16 +4534,16 @@ }, { "name": "symfony/css-selector", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2851e1932d77ce727776154d659b232d061e816a" + "reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2851e1932d77ce727776154d659b232d061e816a", - "reference": "2851e1932d77ce727776154d659b232d061e816a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ca809c64072e0fe61c1c7fb3c76cdc32265042ac", + "reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac", "shasum": "" }, "require": { @@ -4583,11 +4583,11 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-09-06 11:02:40" }, { "name": "symfony/dom-crawler", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -4643,16 +4643,16 @@ }, { "name": "symfony/yaml", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d" + "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/f291ed25eb1435bddbe8a96caaef16469c2a092d", - "reference": "f291ed25eb1435bddbe8a96caaef16469c2a092d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3", + "reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3", "shasum": "" }, "require": { @@ -4688,7 +4688,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-09-02 02:12:52" + "time": "2016-09-25 08:27:07" }, { "name": "webmozart/assert", @@ -4747,7 +4747,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.0.0" + "php": ">=7.0.0", + "ext-intl": "*" }, "platform-dev": [] } From 7d247897ed31067ef96aa9373162498d292a0f44 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 18 Oct 2016 06:47:48 +0200 Subject: [PATCH 15/45] Should correctly show user info. [skip ci] --- resources/views/admin/users/show.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/admin/users/show.twig b/resources/views/admin/users/show.twig index 734ca4e32b..e008708de1 100644 --- a/resources/views/admin/users/show.twig +++ b/resources/views/admin/users/show.twig @@ -59,7 +59,7 @@ {{ trans('list.is_activated') }} - {% if information.activated %} + {% if information.is_activated %} Yes {% else %} No @@ -69,7 +69,7 @@ {{ trans('list.is_blocked') }} - {% if information.blocked == 1 %} + {% if information.blocked %} Yes: {% if information.blocked_code == "" %} From 78b577bc9d1153761d09d46380cd03c73913cda8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 18 Oct 2016 06:50:35 +0200 Subject: [PATCH 16/45] Better ip info [skip ci] --- app/Http/Controllers/Admin/UserController.php | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 322668323c..65f7148c32 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -86,13 +86,24 @@ class UserController extends Controller */ public function show(UserRepositoryInterface $repository, User $user) { - $title = strval(trans('firefly.administration')); - $mainTitleIcon = 'fa-hand-spock-o'; - $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email])); - $subTitleIcon = 'fa-user'; - $defaultIp = '0.0.0.0'; - $registration = Preferences::getForUser($user, 'registration_ip_address', $defaultIp)->data; - $confirmation = Preferences::getForUser($user, 'confirmation_ip_address', $defaultIp)->data; + $title = strval(trans('firefly.administration')); + $mainTitleIcon = 'fa-hand-spock-o'; + $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email])); + $subTitleIcon = 'fa-user'; + + // get IP info: + $defaultIp = '0.0.0.0'; + $registrationPreference = Preferences::getForUser($user, 'registration_ip_address'); + $registration = $defaultIp; + $confirmationPreference = Preferences::getForUser($user, 'confirmation_ip_address'); + $confirmation = $defaultIp; + if (!is_null($registrationPreference)) { + $registration = $registrationPreference->data; + } + if (!is_null($confirmationPreference)) { + $confirmation = $confirmationPreference->data; + } + $registrationHost = ''; $confirmationHost = ''; From b9308cd74a48ef4173b385455a87fbe9713cbece Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 20 Oct 2016 16:51:05 +0200 Subject: [PATCH 17/45] Test flash messages. --- app/Http/Controllers/HomeController.php | 13 ++++++++++++- routes/web.php | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 3222ec9467..fa7d003bb6 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -153,7 +153,7 @@ class HomeController extends Controller } return view( - 'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions','showDepositsFrontpage') + 'index', compact('count', 'showTour', 'title', 'subTitle', 'mainTitleIcon', 'transactions', 'showDepositsFrontpage') ); } @@ -196,6 +196,17 @@ class HomeController extends Controller return '
'; } + /** + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function testFlash() + { + Session::flash('success', 'This is a success message.'); + Session::flash('info', 'This is an info message.'); + Session::flash('warning', 'This is a warning.'); + Session::flash('error', 'This is an error!'); + return redirect(route('home')); + } /** * @param array $array diff --git a/routes/web.php b/routes/web.php index d2fd4d81a8..d9610b5675 100755 --- a/routes/web.php +++ b/routes/web.php @@ -80,6 +80,7 @@ Route::group( * Home Controller */ Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']); + Route::get('/flash', ['uses' => 'HomeController@testFlash', 'as' => 'testFlash']); Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']); Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']); Route::get('/routes', ['uses' => 'HomeController@routes']); From d92768ecbf157eafaf469addcf715734a5c96ffc Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 20 Oct 2016 19:10:43 +0200 Subject: [PATCH 18/45] This code fixes #349 --- app/Console/Commands/CreateImport.php | 153 ++++++++++++++++++ app/Console/Commands/Import.php | 6 +- app/Console/Kernel.php | 2 + app/Http/Controllers/ProfileController.php | 8 +- app/Import/Converter/OpposingAccountIban.php | 20 ++- app/Repositories/User/UserRepository.php | 15 ++ .../User/UserRepositoryInterface.php | 7 + resources/lang/en_US/firefly.php | 82 +++++----- resources/views/profile/index.twig | 3 + 9 files changed, 248 insertions(+), 48 deletions(-) create mode 100644 app/Console/Commands/CreateImport.php diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php new file mode 100644 index 0000000000..093ab03c3f --- /dev/null +++ b/app/Console/Commands/CreateImport.php @@ -0,0 +1,153 @@ +argument('file'); + $configuration = $this->argument('configuration'); + $user = $userRepository->find(intval($this->option('user'))); + $cwd = getcwd(); + $type = strtolower($this->option('type')); + + if (!$this->validArguments()) { + return; + } + + // try to parse configuration data: + $configurationData = json_decode(file_get_contents($configuration)); + if (is_null($configurationData)) { + $this->error(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd)); + + return; + } + + $this->info(sprintf('Going to create a job to import file: %s', $file)); + $this->info(sprintf('Using configuration file: %s', $configuration)); + $this->info(sprintf('Import into user: #%d (%s)', $user->id, $user->email)); + $this->info(sprintf('Type of import: %s', $type)); + + /** @var ImportJobRepositoryInterface $jobRepository */ + $jobRepository = app(ImportJobRepositoryInterface::class, [$user]); + + $job = $jobRepository->create($type); + $this->line(sprintf('Created job "%s"...', $job->key)); + + // put the file in the proper place: + Artisan::call('firefly:encrypt', ['file' => $file, 'key' => $job->key]); + $this->line('Stored import data...'); + + // store the configuration in the job: + $job->configuration = $configurationData; + $job->status = 'settings_complete'; + $job->save(); + $this->line('Stored configuration...'); + + // if user wants to run it, do! + if ($this->option('start') === true) { + $this->line('The import will start in a moment. This process is not visible...'); + Log::debug('Go for import!'); + Artisan::call('firefly:start-import', ['key' => $job->key]); + $this->line('Done!'); + } + + return; + } + + /** + * @return bool + */ + private function validArguments(): bool + { + // find the file + /** @var UserRepositoryInterface $userRepository */ + $userRepository = app(UserRepositoryInterface::class); + $file = $this->argument('file'); + $configuration = $this->argument('configuration'); + $user = $userRepository->find(intval($this->option('user'))); + $cwd = getcwd(); + $validTypes = array_keys(config('firefly.import_formats')); + $type = strtolower($this->option('type')); + + if (is_null($user->id)) { + $this->error(sprintf('There is no user with ID %d.', $this->option('user'))); + + return false; + } + if (!in_array($type, $validTypes)) { + $this->error(sprintf('Cannot import file of type "%s"', $type)); + + return false; + } + + if (!file_exists($file)) { + $this->error(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd)); + + return false; + } + + if (!file_exists($configuration)) { + $this->error(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd)); + + return false; + } + + return true; + } +} diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index 7b8c77e9f3..8d7c23b0ac 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -32,14 +32,14 @@ class Import extends Command * * @var string */ - protected $description = 'Import stuff into Firefly III.'; + protected $description = 'This will start a new import.'; /** * The name and signature of the console command. * * @var string */ - protected $signature = 'firefly:import {key}'; + protected $signature = 'firefly:start-import {key}'; /** * Create a new command instance. @@ -57,9 +57,11 @@ class Import extends Command */ public function handle() { + Log::debug('Start start-import command'); $jobKey = $this->argument('key'); $job = ImportJob::whereKey($jobKey)->first(); if (!$this->isValid($job)) { + Log::error('Job is not valid for some reason. Exit.'); return; } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index f52e0c1570..d62f5371ca 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Console; +use FireflyIII\Console\Commands\CreateImport; use FireflyIII\Console\Commands\EncryptFile; use FireflyIII\Console\Commands\Import; use FireflyIII\Console\Commands\ScanAttachments; @@ -58,6 +59,7 @@ class Kernel extends ConsoleKernel UpgradeFireflyInstructions::class, VerifyDatabase::class, Import::class, + CreateImport::class, EncryptFile::class, ScanAttachments::class, UpgradeDatabase::class, diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 24939b2406..b03420b592 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -35,6 +35,9 @@ class ProfileController extends Controller public function __construct() { parent::__construct(); + + View::share('title', trans('firefly.profile')); + View::share('mainTitleIcon', 'fa-user'); } /** @@ -63,7 +66,10 @@ class ProfileController extends Controller */ public function index() { - return view('profile.index')->with('title', trans('firefly.profile'))->with('subTitle', auth()->user()->email)->with('mainTitleIcon', 'fa-user'); + $subTitle = auth()->user()->email; + $userId = auth()->user()->id; + + return view('profile.index', compact('subTitle', 'userId')); } /** diff --git a/app/Import/Converter/OpposingAccountIban.php b/app/Import/Converter/OpposingAccountIban.php index b1f8dc63e7..c3dacbd712 100644 --- a/app/Import/Converter/OpposingAccountIban.php +++ b/app/Import/Converter/OpposingAccountIban.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Import\Converter; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Log; @@ -69,11 +70,20 @@ class OpposingAccountIban extends BasicConverter implements ConverterInterface return $account; } - $account = $repository->store( - ['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true, - 'openingBalance' => 0] - ); - $this->setCertainty(100); + // the IBAN given may not be a valid IBAN. If not, we cannot store by + // iban and we have no opposing account. There should be some kind of fall back + // routine. + try { + $account = $repository->store( + ['name' => $value, 'iban' => $value, 'user' => $this->user->id, 'accountType' => 'import', 'virtualBalance' => 0, 'active' => true, + 'openingBalance' => 0] + ); + $this->setCertainty(100); + } catch (FireflyException $e) { + Log::error($e); + + $account = new Account; + } return $account; } diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index d382708be8..97642b2ea9 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -59,6 +59,21 @@ class UserRepository implements UserRepositoryInterface return $this->all()->count(); } + /** + * @param int $userId + * + * @return User + */ + public function find(int $userId): User + { + $user = User::find($userId); + if (!is_null($user)) { + return $user; + } + + return new User; + } + /** * Return basic user information. * diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php index 349fc0cdb4..a5fce26bad 100644 --- a/app/Repositories/User/UserRepositoryInterface.php +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -31,6 +31,13 @@ interface UserRepositoryInterface */ public function all(): Collection; + /** + * @param int $userId + * + * @return User + */ + public function find(int $userId): User; + /** * Return basic user information. * diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 04bab8676f..773bef3979 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -69,6 +69,7 @@ return [ 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'source_accounts' => 'Source account(s)', 'destination_accounts' => 'Destination account(s)', + 'user_id_is' => 'Your user id is :user', // repeat frequencies: 'repeat_freq_monthly' => 'monthly', @@ -669,48 +670,49 @@ return [ 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', 'add' => 'Add', - 'remove' => 'Remove', - 'max_amount_add' => 'The maximum amount you can add is', - 'max_amount_remove' => 'The maximum amount you can remove is', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'updated_piggy_bank' => 'Updated piggy bank ":name"', - 'details' => 'Details', - 'events' => 'Events', - 'target_amount' => 'Target amount', - 'start_date' => 'Start date', - 'target_date' => 'Target date', - 'no_target_date' => 'No target date', - 'todo' => 'to do', - 'table' => 'Table', - 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', - 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', - 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', - 'delete_piggy_bank' => 'Delete piggy bank ":name"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".', + + 'remove' => 'Remove', + 'max_amount_add' => 'The maximum amount you can add is', + 'max_amount_remove' => 'The maximum amount you can remove is', + 'update_piggy_button' => 'Update piggy bank', + 'update_piggy_title' => 'Update piggy bank ":name"', + 'updated_piggy_bank' => 'Updated piggy bank ":name"', + 'details' => 'Details', + 'events' => 'Events', + 'target_amount' => 'Target amount', + 'start_date' => 'Start date', + 'target_date' => 'Target date', + 'no_target_date' => 'No target date', + 'todo' => 'to do', + 'table' => 'Table', + 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', + 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', + 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', + 'delete_piggy_bank' => 'Delete piggy bank ":name"', + 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', + 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', + 'added_amount_to_piggy' => 'Added :amount to ":name"', + 'removed_amount_from_piggy' => 'Removed :amount from ":name"', + 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".', // tags - 'regular_tag' => 'Just a regular tag.', - 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', - 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', - 'delete_tag' => 'Delete tag ":tag"', - 'deleted_tag' => 'Deleted tag ":tag"', - 'new_tag' => 'Make new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'updated_tag' => 'Updated tag ":tag"', - 'created_tag' => 'Tag ":tag" has been created!', - 'no_year' => 'No year set', - 'no_month' => 'No month set', - 'tag_title_nothing' => 'Default tags', - 'tag_title_balancingAct' => 'Balancing act tags', - 'tag_title_advancePayment' => 'Advance payment tags', - 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', - 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', - 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', + 'regular_tag' => 'Just a regular tag.', + 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', + 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', + 'delete_tag' => 'Delete tag ":tag"', + 'deleted_tag' => 'Deleted tag ":tag"', + 'new_tag' => 'Make new tag', + 'edit_tag' => 'Edit tag ":tag"', + 'updated_tag' => 'Updated tag ":tag"', + 'created_tag' => 'Tag ":tag" has been created!', + 'no_year' => 'No year set', + 'no_month' => 'No month set', + 'tag_title_nothing' => 'Default tags', + 'tag_title_balancingAct' => 'Balancing act tags', + 'tag_title_advancePayment' => 'Advance payment tags', + 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', + 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', 'transaction_journal_information' => 'Transaction information', 'transaction_journal_meta' => 'Meta information', diff --git a/resources/views/profile/index.twig b/resources/views/profile/index.twig index 697b8f7fb0..2be9d63b54 100644 --- a/resources/views/profile/index.twig +++ b/resources/views/profile/index.twig @@ -12,6 +12,9 @@

Options

+

+ {{ trans('firefly.user_id_is',{user: userId})|raw }} +

  • {{ 'change_your_password'|_ }}
  • {{ 'delete_account'|_ }}
  • From f83aaf77f150ee0dcd1492a01823ec200941df8a Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 20 Oct 2016 21:40:45 +0200 Subject: [PATCH 19/45] Improve bill things for issue #341 --- app/Http/Controllers/BillController.php | 4 +- app/Repositories/Bill/BillRepository.php | 66 +++++++-------- .../Bill/BillRepositoryInterface.php | 5 +- resources/views/list/bills.twig | 2 +- storage/database/seed.bill-test.json | 84 +++++++++++++++++++ storage/database/seed.testing.json | 24 ++---- 6 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 storage/database/seed.bill-test.json diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index fc0e907480..aef8266959 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -137,7 +137,7 @@ class BillController extends Controller $bills = $repository->getBills(); $bills->each( function (Bill $bill) use ($repository, $start, $end) { - $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); + $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon); $bill->lastFoundMatch = $repository->lastFoundMatch($bill); $journals = $repository->getJournalsInRange($bill, $start, $end); // loop journals, find average: @@ -204,7 +204,7 @@ class BillController extends Controller $yearAverage = $repository->getYearAverage($bill, $date); $overallAverage = $repository->getOverallAverage($bill); $journals->setPath('/bills/show/' . $bill->id); - $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill); + $bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, new Carbon); $hideBill = true; $subTitle = e($bill->name); diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 2e12076d10..62000b620f 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -18,10 +18,12 @@ use DB; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use FireflyIII\Support\CacheProperties; use FireflyIII\User; use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; +use Log; use Navigation; /** @@ -461,52 +463,44 @@ class BillRepository implements BillRepositoryInterface } /** - * @param Bill $bill + * @param Bill $bill + * @param Carbon $date * - * @return \Carbon\Carbon + * @return Carbon */ - public function nextExpectedMatch(Bill $bill): Carbon + public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon { - - $finalDate = Carbon::now(); - $finalDate->year = 1900; - if ($bill->active == 0) { - return $finalDate; + $cache = new CacheProperties; + $cache->addProperty($bill->id); + $cache->addProperty('nextExpectedMatch'); + if ($cache->has()) { + return $cache->get(); } + // find the most recent date for this bill NOT in the future. Cache this date: + $start = clone $bill->date; + $now = new Carbon; + Log::debug('Start is ' . $start->format('Y-m-d')); - /* - * $today is the start of the next period, to make sure FF3 won't miss anything - * when the current period has a transaction journal. - */ - /** @var \Carbon\Carbon $obj */ - $obj = new Carbon; - $today = Navigation::addPeriod($obj, $bill->repeat_freq, 0); + do { + $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + Log::debug('Start is now ' . $start->format('Y-m-d')); + } while ($start <= $now); - $skip = $bill->skip + 1; - $start = Navigation::startOfPeriod($obj, $bill->repeat_freq); - /* - * go back exactly one month/week/etc because FF3 does not care about 'next' - * bills if they're too far into the past. - */ + $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + Log::debug('Final start is ' . $start->format('Y-m-d')); + Log::debug('Matching end is ' . $end->format('Y-m-d')); - $counter = 0; - while ($start <= $today) { - if (($counter % $skip) == 0) { - // do something. - $end = Navigation::endOfPeriod(clone $start, $bill->repeat_freq); - $journalCount = $bill->transactionJournals()->before($end)->after($start)->count(); - if ($journalCount == 0) { - $finalDate = new Carbon($start->format('Y-m-d')); - break; - } - } + // see if the bill was paid in this period. + $journalCount = $bill->transactionJournals()->before($end)->after($start)->count(); - // add period for next round! - $start = Navigation::addPeriod($start, $bill->repeat_freq, 0); - $counter++; + if ($journalCount > 0) { + // this period had in fact a bill. The new start is the current end, and we create a new end. + $start = clone $end; + $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); } + $cache->store($start); - return $finalDate; + return $start; } /** diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index f740921cec..195df50889 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -168,11 +168,12 @@ interface BillRepositoryInterface /** - * @param Bill $bill + * @param Bill $bill + * @param Carbon $date * * @return \Carbon\Carbon */ - public function nextExpectedMatch(Bill $bill): Carbon; + public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon; /** * @param Bill $bill diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig index 380878a14e..1f2a8a51f7 100644 --- a/resources/views/list/bills.twig +++ b/resources/views/list/bills.twig @@ -6,7 +6,7 @@ {{ trans('list.matchesOn') }} {{ trans('list.matchingAmount') }} {{ trans('list.paid_current_period') }} - {{ trans('list.expectedMatch') }} + {{ trans('list.next_expected_match') }} {{ trans('list.active') }} {{ trans('list.automatch') }} {{ trans('list.repeat_freq') }} diff --git a/storage/database/seed.bill-test.json b/storage/database/seed.bill-test.json new file mode 100644 index 0000000000..857a357951 --- /dev/null +++ b/storage/database/seed.bill-test.json @@ -0,0 +1,84 @@ +{ + "users": [ + { + "email": "thegrumpydictator@gmail.com", + "password": "james" + }, + { + "email": "thegrumpydictator+empty@gmail.com", + "password": "james" + }, + { + "email": "thegrumpydictator+deleteme@gmail.com", + "password": "james" + } + ], + "roles": [ + { + "user_id": 1, + "role": 1 + } + ], + "accounts": [ + { + "user_id": 1, + "account_type_id": 3, + "name": "Checking Account", + "iban": "NL11XOLA6707795988" + } + ], + "account-meta": [ + ], + "bills": [ + { + "name": "Some weird weekly bill", + "match": "weird,weekly,bill", + "amount_min": 30, + "amount_max": 35, + "user_id": 1, + "date": "2016-06-12", + "active": 1, + "automatch": 1, + "repeat_freq": "weekly", + "skip": 0 + } + ], + "budgets": [ + ], + "budget-limits": [ + ], + "monthly-limits": [ + ], + "categories": [ + ], + "piggy-banks": [ + ], + "piggy-events": [ + ], + "rule-groups": [ + ], + "rules": [ + ], + "rule-triggers": [ + ], + "rule-actions": [ + ], + "tags": [ + ], + "monthly-deposits": [ + ], + "monthly-transfers": [ + ], + "monthly-withdrawals": [ + ], + "attachments": [ + ], + "multi-withdrawals": [ + ], + "multi-deposits": [ + ], + "multi-transfers": [ + ], + "import-jobs": [], + "currencies": [] +} \ No newline at end of file diff --git a/storage/database/seed.testing.json b/storage/database/seed.testing.json index 847832ee07..d6f9dfe2ea 100644 --- a/storage/database/seed.testing.json +++ b/storage/database/seed.testing.json @@ -245,27 +245,15 @@ ], "bills": [ { - "name": "Rent", - "match": "rent,land,lord", - "amount_min": 795, - "amount_max": 805, + "name": "Some weird weekly bill", + "match": "weird,weekly,bill", + "amount_min": 30, + "amount_max": 35, "user_id": 1, - "date": "2015-01-01", + "date": "2016-06-12", "active": 1, "automatch": 1, - "repeat_freq": "monthly", - "skip": 0 - }, - { - "name": "Health insurance", - "match": "insurer,insurance,health", - "amount_min": 120, - "amount_max": 140, - "user_id": 1, - "date": "2015-01-01", - "active": 1, - "automatch": 1, - "repeat_freq": "monthly", + "repeat_freq": "weekly", "skip": 0 } ], From a38e057fa79a30e60366242b8a527061a7f3bb03 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 06:26:12 +0200 Subject: [PATCH 20/45] Rewrote some methods to fix #341 --- app/Repositories/Bill/BillRepository.php | 166 +++++++++++++----- .../Bill/BillRepositoryInterface.php | 11 ++ storage/database/seed.bill-test.json | 53 ++++++ 3 files changed, 184 insertions(+), 46 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 62000b620f..fffa3b3a0b 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -16,6 +16,7 @@ namespace FireflyIII\Repositories\Bill; use Carbon\Carbon; use DB; use FireflyIII\Models\Bill; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Support\CacheProperties; @@ -224,7 +225,8 @@ class BillRepository implements BillRepositoryInterface /** * Get the total amount of money paid for the users active bills in the date range given. - * This amount will be negative (they're expenses). + * This amount will be negative (they're expenses). This method is equal to + * getBillsUnpaidInRange. So the debug comments are gone. * * @param Carbon $start * @param Carbon $end @@ -233,29 +235,37 @@ class BillRepository implements BillRepositoryInterface */ public function getBillsPaidInRange(Carbon $start, Carbon $end): string { - $amount = '0'; - $bills = $this->getActiveBills(); - + $bills = $this->getActiveBills(); + $sum = '0'; /** @var Bill $bill */ foreach ($bills as $bill) { - $ranges = $this->getRanges($bill, $start, $end); - - foreach ($ranges as $range) { - $paid = $bill->transactionJournals() - ->before($range['end']) - ->after($range['start']) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); - } - ) - ->first([DB::raw('SUM(transactions.amount) AS sum_amount')]); - $sumAmount = $paid->sum_amount ?? '0'; - $amount = bcadd($amount, $sumAmount); + $currentStart = clone $start; + while ($currentStart <= $end) { + $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + if ($nextExpectedMatch > $end) { + break; + } + /** @var Collection $set */ + $set = $bill->transactionJournals()->after($currentStart)->before($nextExpectedMatch)->get(['transaction_journals.*']); + if ($set->count() > 0) { + $journalIds = $set->pluck('id')->toArray(); + $amount = strval(Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount')); + $sum = bcadd($sum, $amount); + Log::info( + sprintf( + 'Bill "%s" is PAID in period %s to %s (%d transaction(s)), add %f to sum (sum is now %f).', $bill->name, + $currentStart->format('Y-m-d'), + $nextExpectedMatch->format('Y-m-d'), + $set->count(), + $amount, $sum + ) + ); + } + $currentStart = clone $nextExpectedMatch; } } - return $amount; + return $sum; } /** @@ -268,32 +278,56 @@ class BillRepository implements BillRepositoryInterface */ public function getBillsUnpaidInRange(Carbon $start, Carbon $end): string { - $amount = '0'; - $bills = $this->getActiveBills(); - + $bills = $this->getActiveBills(); + $sum = '0'; /** @var Bill $bill */ foreach ($bills as $bill) { - $ranges = $this->getRanges($bill, $start, $end); - $paidBill = '0'; - foreach ($ranges as $range) { - $paid = $bill->transactionJournals() - ->before($range['end']) - ->after($range['start']) - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '>', 0); - } - ) - ->first([DB::raw('SUM(transactions.amount) AS sum_amount')]); - $sumAmount = $paid->sum_amount ?? '0'; - $paidBill = bcadd($sumAmount, $paidBill); - } - if ($paidBill == 0) { - $amount = bcadd($amount, $bill->expectedAmount); - } - } + Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq)); - return $amount; + /* + * Start at 2016-10-01, see when we expect the bill to hit: + */ + $currentStart = clone $start; + Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d'))); + + while ($currentStart <= $end) { + Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); + $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + Log::debug(sprintf('next Expected match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + /* + * If $nextExpectedMatch is after $end, we continue: + */ + if ($nextExpectedMatch > $end) { + Log::debug(sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch, $end)); + break; + } + /* + * If it is not, we search for transactions between $currentStart and $nextExpectedMatch + */ + $count = $bill->transactionJournals()->after($currentStart)->before($nextExpectedMatch)->count(); + Log::debug(sprintf('%d transactions found', $count)); + + if ($count === 0) { + $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2', 4); + $sum = bcadd($sum, $average); + Log::info( + sprintf( + 'Bill "%s" is unpaid in period %s to %s, add %f to sum (sum is now %f).', $bill->name, + $currentStart->format('Y-m-d'), + $nextExpectedMatch->format('Y-m-d'), + $average, $sum + ) + ); + } + + Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + $currentStart = clone $nextExpectedMatch; + } + Log::debug(sprintf('end of bill "%s"', $bill->name)); + } + Log::debug(sprintf('Sum became %f', $sum)); + + return $sum; } /** @@ -463,6 +497,44 @@ class BillRepository implements BillRepositoryInterface } /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction. Whether or not it is there already, is not relevant. + * + * @param Bill $bill + * @param Carbon $date + * + * @return \Carbon\Carbon + */ + public function nextDateMatch(Bill $bill, Carbon $date): Carbon + { + $cache = new CacheProperties; + $cache->addProperty($bill->id); + $cache->addProperty('nextDateMatch'); + $cache->addProperty($date); + if ($cache->has()) { + return $cache->get(); + } + // find the most recent date for this bill NOT in the future. Cache this date: + $start = clone $bill->date; + Log::debug('NextDatematch: Start is ' . $start->format('Y-m-d')); + + + while ($start <= $date) { + $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + Log::debug('NextDateMatch: Start is now ' . $start->format('Y-m-d')); + } + + $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + Log::debug('NextDateMatch: Final start is ' . $start->format('Y-m-d')); + Log::debug('NextDateMatch: Matching end is ' . $end->format('Y-m-d')); + $cache->store($start); + + return $start; + } + + /** + * Given the date in $date, this method will return a moment in the future where the bill is expected to be paid. + * * @param Bill $bill * @param Carbon $date * @@ -473,18 +545,19 @@ class BillRepository implements BillRepositoryInterface $cache = new CacheProperties; $cache->addProperty($bill->id); $cache->addProperty('nextExpectedMatch'); + $cache->addProperty($date); if ($cache->has()) { return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; - $now = new Carbon; Log::debug('Start is ' . $start->format('Y-m-d')); - do { + + while ($start <= $date) { $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); Log::debug('Start is now ' . $start->format('Y-m-d')); - } while ($start <= $now); + } $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); Log::debug('Final start is ' . $start->format('Y-m-d')); @@ -495,8 +568,9 @@ class BillRepository implements BillRepositoryInterface if ($journalCount > 0) { // this period had in fact a bill. The new start is the current end, and we create a new end. + Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d'))); $start = clone $end; - $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + //$end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); } $cache->store($start); diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 195df50889..0beda0b24e 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -167,6 +167,17 @@ interface BillRepositoryInterface public function lastFoundMatch(Bill $bill): Carbon; + /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction. Whether or not it is there already, is not relevant. + * + * @param Bill $bill + * @param Carbon $date + * + * @return \Carbon\Carbon + */ + public function nextDateMatch(Bill $bill, Carbon $date): Carbon; + /** * @param Bill $bill * @param Carbon $date diff --git a/storage/database/seed.bill-test.json b/storage/database/seed.bill-test.json index 857a357951..be9d599077 100644 --- a/storage/database/seed.bill-test.json +++ b/storage/database/seed.bill-test.json @@ -41,6 +41,59 @@ "automatch": 1, "repeat_freq": "weekly", "skip": 0 + }, + + + { + "name": "Rent", + "match": "rent,land,lord", + "amount_min": 795, + "amount_max": 805, + "user_id": 1, + "date": "2015-01-02", + "active": 1, + "automatch": 1, + "repeat_freq": "monthly", + "skip": 0 + }, + + { + "name": "Netflix subscription per quarter", + "match": "netflix,subscription", + "amount_min": 36, + "amount_max": 36, + "user_id": 1, + "date": "2016-09-06", + "active": 1, + "automatch": 1, + "repeat_freq": "quarterly", + "skip": 0 + }, + + { + "name": "Travel insurance", + "match": "abn,travel,insurance", + "amount_min": 55, + "amount_max": 62, + "user_id": 1, + "date": "2015-03-04", + "active": 1, + "automatch": 1, + "repeat_freq": "yearly", + "skip": 0 + }, + + { + "name": "Health insurance", + "match": "insurer,insurance,health", + "amount_min": 120, + "amount_max": 140, + "user_id": 1, + "date": "2015-01-07", + "active": 1, + "automatch": 1, + "repeat_freq": "monthly", + "skip": 0 } ], "budgets": [ From a9c3992331213a536f3b8128d66d1346e1f0f34b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 06:33:56 +0200 Subject: [PATCH 21/45] Back to old method [skip ci] --- app/Repositories/Bill/BillRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index fffa3b3a0b..99178d4fa7 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -241,7 +241,7 @@ class BillRepository implements BillRepositoryInterface foreach ($bills as $bill) { $currentStart = clone $start; while ($currentStart <= $end) { - $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + $nextExpectedMatch = $this->nextExpectedMatch($bill, $currentStart); if ($nextExpectedMatch > $end) { break; } @@ -253,7 +253,7 @@ class BillRepository implements BillRepositoryInterface $sum = bcadd($sum, $amount); Log::info( sprintf( - 'Bill "%s" is PAID in period %s to %s (%d transaction(s)), add %f to sum (sum is now %f).', $bill->name, + 'getBillsPaidInRange: Bill "%s" is PAID in period %s to %s (%d transaction(s)), add %f to sum (sum is now %f).', $bill->name, $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'), $set->count(), @@ -292,7 +292,7 @@ class BillRepository implements BillRepositoryInterface while ($currentStart <= $end) { Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); - $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + $nextExpectedMatch = $this->nextExpectedMatch($bill, $currentStart); Log::debug(sprintf('next Expected match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); /* * If $nextExpectedMatch is after $end, we continue: @@ -312,7 +312,7 @@ class BillRepository implements BillRepositoryInterface $sum = bcadd($sum, $average); Log::info( sprintf( - 'Bill "%s" is unpaid in period %s to %s, add %f to sum (sum is now %f).', $bill->name, + 'getBillsUnpaidInRange: Bill "%s" is unpaid in period %s to %s, add %f to sum (sum is now %f).', $bill->name, $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'), $average, $sum From 97c0fb389d40dd8baf2c246bf2a77217634c5446 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 06:38:00 +0200 Subject: [PATCH 22/45] More logs [skip ci] --- app/Repositories/Bill/BillRepository.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 99178d4fa7..3757160ece 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -298,7 +298,9 @@ class BillRepository implements BillRepositoryInterface * If $nextExpectedMatch is after $end, we continue: */ if ($nextExpectedMatch > $end) { - Log::debug(sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch, $end)); + Log::debug( + sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d')) + ); break; } /* @@ -319,6 +321,9 @@ class BillRepository implements BillRepositoryInterface ) ); } + if ($count != 0) { + Log::info(sprintf('getBillsUnpaidInRange: Bill "%s" is paid (%d) so ignore it.', $bill->name, $count)); + } Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); $currentStart = clone $nextExpectedMatch; From fcdb6fd2a76cbc8b8c14f804f72e31491a5c9d4b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 06:41:33 +0200 Subject: [PATCH 23/45] Do loop instead of while loop [skip ci] --- app/Repositories/Bill/BillRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 3757160ece..913e6990e3 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -556,13 +556,13 @@ class BillRepository implements BillRepositoryInterface } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; - Log::debug('Start is ' . $start->format('Y-m-d')); + Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d')); - - while ($start <= $date) { + // do loop instead of while loop: + do { $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); Log::debug('Start is now ' . $start->format('Y-m-d')); - } + } while ($start <= $date); $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); Log::debug('Final start is ' . $start->format('Y-m-d')); From af1ee9db931f30b803ded967889ccf2f7b2a525b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 07:29:25 +0200 Subject: [PATCH 24/45] This fixes bills unpaid. --- app/Repositories/Bill/BillRepository.php | 37 +++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 913e6990e3..99a614b2d1 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -260,6 +260,11 @@ class BillRepository implements BillRepositoryInterface $amount, $sum ) ); + // add day to make sure we jump to the next period. + $nextExpectedMatch->addDay(); + } + if ($currentStart->diffInDays($nextExpectedMatch) === 0) { + $nextExpectedMatch->addDay(); } $currentStart = clone $nextExpectedMatch; } @@ -322,10 +327,27 @@ class BillRepository implements BillRepositoryInterface ); } if ($count != 0) { - Log::info(sprintf('getBillsUnpaidInRange: Bill "%s" is paid (%d) so ignore it.', $bill->name, $count)); + Log::info(sprintf('getBillsUnpaidInRange: Bill "%s" is paid (%d) between %s and %s so ignore it.', $bill->name, $count, + $currentStart->format('Y-m-d'), + $nextExpectedMatch->format('Y-m-d') + )); + // add day to make sure we jump to the next period. + $nextExpectedMatch->addDay(); + } + + // if $currentStart and $nextExpectedMatch are equal, add a day for good measure. + if ($currentStart->diffInDays($nextExpectedMatch) === 0) { + Log::debug( + sprintf( + '$nextExpectedMatch (%s) is equal to $currentStart (%s). Added a day.', $nextExpectedMatch->format('Y-m-d'), + $currentStart->format('Y-m-d') + ) + ); + $nextExpectedMatch->addDay(); } Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + $currentStart = clone $nextExpectedMatch; } Log::debug(sprintf('end of bill "%s"', $bill->name)); @@ -558,15 +580,13 @@ class BillRepository implements BillRepositoryInterface $start = clone $bill->date; Log::debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d')); - // do loop instead of while loop: - do { + while ($start < $date) { + Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d'))); $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); Log::debug('Start is now ' . $start->format('Y-m-d')); - } while ($start <= $date); + } $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); - Log::debug('Final start is ' . $start->format('Y-m-d')); - Log::debug('Matching end is ' . $end->format('Y-m-d')); // see if the bill was paid in this period. $journalCount = $bill->transactionJournals()->before($end)->after($start)->count(); @@ -575,8 +595,11 @@ class BillRepository implements BillRepositoryInterface // this period had in fact a bill. The new start is the current end, and we create a new end. Log::debug(sprintf('Journal count is %d, so start becomes %s', $journalCount, $end->format('Y-m-d'))); $start = clone $end; - //$end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); + $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); } + Log::debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d')); + Log::debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d')); + $cache->store($start); return $start; From a95a4e783a08f9e782881df961f0b711ad2639bd Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 13:20:51 +0200 Subject: [PATCH 25/45] Fix and simplify bill repos --- app/Repositories/Bill/BillRepository.php | 181 ++++++++---------- .../Bill/BillRepositoryInterface.php | 11 ++ 2 files changed, 95 insertions(+), 97 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 99a614b2d1..8ce33c0d85 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -239,35 +239,15 @@ class BillRepository implements BillRepositoryInterface $sum = '0'; /** @var Bill $bill */ foreach ($bills as $bill) { - $currentStart = clone $start; - while ($currentStart <= $end) { - $nextExpectedMatch = $this->nextExpectedMatch($bill, $currentStart); - if ($nextExpectedMatch > $end) { - break; - } - /** @var Collection $set */ - $set = $bill->transactionJournals()->after($currentStart)->before($nextExpectedMatch)->get(['transaction_journals.*']); - if ($set->count() > 0) { - $journalIds = $set->pluck('id')->toArray(); - $amount = strval(Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount')); - $sum = bcadd($sum, $amount); - Log::info( - sprintf( - 'getBillsPaidInRange: Bill "%s" is PAID in period %s to %s (%d transaction(s)), add %f to sum (sum is now %f).', $bill->name, - $currentStart->format('Y-m-d'), - $nextExpectedMatch->format('Y-m-d'), - $set->count(), - $amount, $sum - ) - ); - // add day to make sure we jump to the next period. - $nextExpectedMatch->addDay(); - } - if ($currentStart->diffInDays($nextExpectedMatch) === 0) { - $nextExpectedMatch->addDay(); - } - $currentStart = clone $nextExpectedMatch; + /** @var Collection $set */ + $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); + if ($set->count() > 0) { + $journalIds = $set->pluck('id')->toArray(); + $amount = strval(Transaction::whereIn('transaction_journal_id', $journalIds)->where('amount', '<', 0)->sum('amount')); + $sum = bcadd($sum, $amount); + Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $amount, $sum)); } + Log::debug('---'); } return $sum; @@ -287,72 +267,22 @@ class BillRepository implements BillRepositoryInterface $sum = '0'; /** @var Bill $bill */ foreach ($bills as $bill) { - Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq)); + Log::debug(sprintf('Now at bill #%d (%s)', $bill->id, $bill->name)); + $dates = $this->getPayDatesInRange($bill, $start, $end); + $count = $bill->transactionJournals()->after($start)->before($end)->count(); + $total = $dates->count() - $count; - /* - * Start at 2016-10-01, see when we expect the bill to hit: - */ - $currentStart = clone $start; - Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d'))); + Log::debug(sprintf('Dates = %d, journalCount = %d, total = %d', $dates->count(), $count, $total)); - while ($currentStart <= $end) { - Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); - $nextExpectedMatch = $this->nextExpectedMatch($bill, $currentStart); - Log::debug(sprintf('next Expected match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); - /* - * If $nextExpectedMatch is after $end, we continue: - */ - if ($nextExpectedMatch > $end) { - Log::debug( - sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d')) - ); - break; - } - /* - * If it is not, we search for transactions between $currentStart and $nextExpectedMatch - */ - $count = $bill->transactionJournals()->after($currentStart)->before($nextExpectedMatch)->count(); - Log::debug(sprintf('%d transactions found', $count)); + if ($total > 0) { - if ($count === 0) { - $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2', 4); - $sum = bcadd($sum, $average); - Log::info( - sprintf( - 'getBillsUnpaidInRange: Bill "%s" is unpaid in period %s to %s, add %f to sum (sum is now %f).', $bill->name, - $currentStart->format('Y-m-d'), - $nextExpectedMatch->format('Y-m-d'), - $average, $sum - ) - ); - } - if ($count != 0) { - Log::info(sprintf('getBillsUnpaidInRange: Bill "%s" is paid (%d) between %s and %s so ignore it.', $bill->name, $count, - $currentStart->format('Y-m-d'), - $nextExpectedMatch->format('Y-m-d') - )); - // add day to make sure we jump to the next period. - $nextExpectedMatch->addDay(); - } - - // if $currentStart and $nextExpectedMatch are equal, add a day for good measure. - if ($currentStart->diffInDays($nextExpectedMatch) === 0) { - Log::debug( - sprintf( - '$nextExpectedMatch (%s) is equal to $currentStart (%s). Added a day.', $nextExpectedMatch->format('Y-m-d'), - $currentStart->format('Y-m-d') - ) - ); - $nextExpectedMatch->addDay(); - } - - Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); - - $currentStart = clone $nextExpectedMatch; + $average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2'); + $multi = bcmul($average, strval($total)); + $sum = bcadd($sum, $multi); + Log::debug(sprintf('Total > 0, so add to sum %f, which becomes %f', $multi, $sum)); } - Log::debug(sprintf('end of bill "%s"', $bill->name)); + Log::debug('---'); } - Log::debug(sprintf('Sum became %f', $sum)); return $sum; } @@ -417,6 +347,61 @@ class BillRepository implements BillRepositoryInterface return $avg; } + /** + * Between start and end, tells you on which date(s) the bill is expected to hit. + * + * @param Bill $bill + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection + { + $set = new Collection; + Log::debug(sprintf('Now at bill "%s" (%s)', $bill->name, $bill->repeat_freq)); + + /* + * Start at 2016-10-01, see when we expect the bill to hit: + */ + $currentStart = clone $start; + Log::debug(sprintf('First currentstart is %s', $currentStart->format('Y-m-d'))); + + while ($currentStart <= $end) { + Log::debug(sprintf('Currentstart is now %s.', $currentStart->format('Y-m-d'))); + $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); + Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + /* + * If $nextExpectedMatch is after $end, we continue: + */ + if ($nextExpectedMatch > $end) { + Log::debug( + sprintf('nextExpectedMatch %s is after %s, so we skip this bill now.', $nextExpectedMatch->format('Y-m-d'), $end->format('Y-m-d')) + ); + break; + } + // add to set + $set->push(clone $nextExpectedMatch); + Log::debug(sprintf('Now %d dates in set.', $set->count())); + + // add day if necessary. + $nextExpectedMatch->addDay(); + + Log::debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); + + $currentStart = clone $nextExpectedMatch; + } + $simple = $set->each( + function (Carbon $date) { + return $date->format('Y-m-d'); + } + ); + Log::debug(sprintf('Found dates between %s and %s:', $start->format('Y-m-d'), $end->format('Y-m-d')), $simple->toArray()); + + + return $set; + } + /** * @param Bill $bill * @@ -539,21 +524,23 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextDateMatch'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + //return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; - Log::debug('NextDatematch: Start is ' . $start->format('Y-m-d')); + Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d')); - - while ($start <= $date) { + while ($start < $date) { + Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d'))); $start = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); - Log::debug('NextDateMatch: Start is now ' . $start->format('Y-m-d')); + Log::debug('Start is now ' . $start->format('Y-m-d')); } $end = Navigation::addPeriod($start, $bill->repeat_freq, $bill->skip); - Log::debug('NextDateMatch: Final start is ' . $start->format('Y-m-d')); - Log::debug('NextDateMatch: Matching end is ' . $end->format('Y-m-d')); + + Log::debug('nextDateMatch: Final start is ' . $start->format('Y-m-d')); + Log::debug('nextDateMatch: Matching end is ' . $end->format('Y-m-d')); + $cache->store($start); return $start; @@ -574,7 +561,7 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextExpectedMatch'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); + //return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 0beda0b24e..3ada1396e7 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -131,6 +131,17 @@ interface BillRepositoryInterface */ public function getOverallAverage($bill): string; + /** + * Between start and end, tells you on which date(s) the bill is expected to hit. + * + * @param Bill $bill + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getPayDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection; + /** * @param Bill $bill * From 801c7c0ab6e1912d95199e7d5a0a9a992d574c1f Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 13:22:45 +0200 Subject: [PATCH 26/45] Remove unused function. --- app/Repositories/Bill/BillRepository.php | 42 ------------------- .../Bill/BillRepositoryInterface.php | 13 ------ 2 files changed, 55 deletions(-) diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 8ce33c0d85..f303fd1365 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -425,48 +425,6 @@ class BillRepository implements BillRepositoryInterface return $journals; } - /** - * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself) - * 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 Carbon $start - * @param Carbon $end - * - * @return array - */ - public function getRanges(Bill $bill, Carbon $start, Carbon $end): array - { - $startOfBill = Navigation::startOfPeriod($start, $bill->repeat_freq); - - - // all periods of this bill up until the current period: - $billStarts = []; - while ($startOfBill < $end) { - - $endOfBill = Navigation::endOfPeriod($startOfBill, $bill->repeat_freq); - - $billStarts[] = [ - 'start' => clone $startOfBill, - 'end' => clone $endOfBill, - ]; - // actually the next one: - $startOfBill = Navigation::addPeriod($startOfBill, $bill->repeat_freq, $bill->skip); - - } - // for each - $validRanges = []; - foreach ($billStarts as $dateEntry) { - if ($dateEntry['end'] > $start && $dateEntry['start'] < $end) { - // count transactions for bill in this range (not relevant yet!): - $validRanges[] = $dateEntry; - } - } - - return $validRanges; - } - /** * @param Bill $bill * @param Carbon $date diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 3ada1396e7..82f849c468 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -149,19 +149,6 @@ interface BillRepositoryInterface */ public function getPossiblyRelatedJournals(Bill $bill): Collection; - /** - * Every bill repeats itself weekly, monthly or yearly (or whatever). This method takes a date-range (usually the view-range of Firefly itself) - * 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 (from the 14th to the 31th). - * - * @param Bill $bill - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function getRanges(Bill $bill, Carbon $start, Carbon $end): array; - /** * @param Bill $bill * @param Carbon $date From 9a3cd277008ba39fe8d6858d8382c93ab78bedb2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 19:06:22 +0200 Subject: [PATCH 27/45] Many updates to get split transactions and normal transactions working side by side. --- app/Export/Processor.php | 7 +- app/Http/Controllers/JsonController.php | 8 +- .../Controllers/TransactionController.php | 178 +++++---- app/Http/Requests/JournalFormRequest.php | 174 ++++++--- ...ExecuteRuleGroupOnExistingTransactions.php | 8 +- app/Models/TransactionJournal.php | 3 + app/Providers/FireflyServiceProvider.php | 6 + .../Journal/JournalRepository.php | 349 +++++++++--------- .../Journal/JournalRepositoryInterface.php | 32 -- app/Repositories/Journal/JournalTasker.php | 89 ++++- .../Journal/JournalTaskerInterface.php | 31 ++ app/Rules/TransactionMatcher.php | 14 +- app/Support/ExpandedForm.php | 2 + app/Support/ExpandedMultiForm.php | 188 ++++++++++ app/Support/Facades/ExpandedMultiForm.php | 35 ++ app/Validation/FireflyValidator.php | 22 ++ config/app.php | 93 ++--- config/twigbridge.php | 5 + public/js/ff/firefly.js | 44 +++ resources/views/form/date.twig | 7 +- resources/views/form/multi/amount.twig | 30 ++ resources/views/form/multi/feedback.twig | 4 + resources/views/form/multi/select.twig | 10 + resources/views/form/multi/text.twig | 9 + resources/views/transactions/create.twig | 47 +-- 25 files changed, 960 insertions(+), 435 deletions(-) create mode 100644 app/Support/ExpandedMultiForm.php create mode 100644 app/Support/Facades/ExpandedMultiForm.php create mode 100644 resources/views/form/multi/amount.twig create mode 100644 resources/views/form/multi/feedback.twig create mode 100644 resources/views/form/multi/select.twig create mode 100644 resources/views/form/multi/text.twig diff --git a/app/Export/Processor.php b/app/Export/Processor.php index f5c69be451..bfb7076006 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -20,6 +20,7 @@ use FireflyIII\Export\Entry\Entry; use FireflyIII\Models\ExportJob; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalTaskerInterface; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Collection; use Storage; @@ -95,9 +96,9 @@ class Processor */ public function collectJournals(): bool { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $this->journals = $repository->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']); + /** @var JournalTaskerInterface $tasker */ + $tasker = app(JournalTaskerInterface::class); + $this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']); return true; } diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 65465a8e9f..5eea913966 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -20,7 +20,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalTaskerInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; use Input; @@ -270,17 +270,17 @@ class JsonController extends Controller } /** - * @param JournalRepositoryInterface $repository + * @param JournalTaskerInterface $tasker * @param $what * * @return \Symfony\Component\HttpFoundation\Response */ - public function transactionJournals(JournalRepositoryInterface $repository, $what) + public function transactionJournals(JournalTaskerInterface $tasker, $what) { $descriptions = []; $type = config('firefly.transactionTypesByWhat.' . $what); $types = [$type]; - $journals = $repository->getJournals($types, 1, 50); + $journals = $tasker->getJournals($types, 1, 50); foreach ($journals as $j) { $descriptions[] = $j->description; } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index dc00364967..f2b30018df 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -23,9 +23,12 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Http\Request; +use Log; use Preferences; use Response; use Session; @@ -40,6 +43,14 @@ use View; */ class TransactionController extends Controller { + /** @var AccountRepositoryInterface */ + private $accounts; + private $attachments; + /** @var BudgetRepositoryInterface */ + private $budgets; + /** @var PiggyBankRepositoryInterface */ + private $piggyBanks; + /** * */ @@ -52,8 +63,21 @@ class TransactionController extends Controller $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); $uploadSize = min($maxFileSize, $maxPostSize); - View::share('uploadSize', $uploadSize); + + // some useful repositories: + $this->middleware( + function ($request, $next) { + $this->accounts = app(AccountRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + $this->piggyBanks = app(PiggyBankRepositoryInterface::class); + $this->attachments = app(AttachmentHelperInterface::class); + + return $next($request); + } + ); + + } /** @@ -63,20 +87,16 @@ class TransactionController extends Controller */ public function create(string $what = TransactionType::DEPOSIT) { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); - $what = strtolower($what); - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getActiveAccountsByType(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBanks = $piggyRepository->getPiggyBanksWithAmount(); - $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); - $preFilled = Session::has('preFilled') ? session('preFilled') : []; - $subTitle = trans('form.add_new_' . $what); - $subTitleIcon = 'fa-plus'; - $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; + $what = strtolower($what); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); + $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); + $preFilled = Session::has('preFilled') ? session('preFilled') : []; + $subTitle = trans('form.add_new_' . $what); + $subTitleIcon = 'fa-plus'; + $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; Session::put('preFilled', $preFilled); @@ -91,7 +111,6 @@ class TransactionController extends Controller asort($piggies); - return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields')); } @@ -145,19 +164,12 @@ class TransactionController extends Controller { $count = $journal->transactions()->count(); if ($count > 2) { - return redirect(route('split.journal.edit', [$journal->id])); + return redirect(route('journal.edit-split', [$journal->id])); } - // code to get list data: - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); - - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - - $assetAccounts = ExpandedForm::makeSelectList($repository->getAccountsByType(['Default account', 'Asset account'])); - $budgetList = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBankList = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanks()); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType(['Default account', 'Asset account'])); + $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks()); // view related code $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); @@ -216,20 +228,20 @@ class TransactionController extends Controller } /** - * @param Request $request - * @param JournalRepositoryInterface $repository - * @param string $what + * @param Request $request + * @param JournalTaskerInterface $tasker + * @param string $what * * @return View */ - public function index(Request $request, JournalRepositoryInterface $repository, string $what) + public function index(Request $request, JournalTaskerInterface $tasker, string $what) { $pageSize = intval(Preferences::get('transactionPageSize', 50)->data); $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what); $subTitle = trans('firefly.title_' . $what); $page = intval($request->get('page')); - $journals = $repository->getJournals($types, $page, $pageSize); + $journals = $tasker->getJournals($types, $page, $pageSize); $journals->setPath('transactions/' . $what); @@ -266,14 +278,14 @@ class TransactionController extends Controller } /** - * @param TransactionJournal $journal - * @param JournalRepositoryInterface $repository + * @param TransactionJournal $journal + * @param JournalTaskerInterface $tasker * * @return View */ - public function show(TransactionJournal $journal, JournalRepositoryInterface $repository, JournalTaskerInterface $tasker) + public function show(TransactionJournal $journal, JournalTaskerInterface $tasker) { - $events = $repository->getPiggyBankEvents($journal); + $events = $tasker->getPiggyBankEvents($journal); $transactions = $tasker->getTransactionsOverview($journal); $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; @@ -291,63 +303,47 @@ class TransactionController extends Controller */ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) { - $att = app('FireflyIII\Helpers\Attachments\AttachmentHelperInterface'); - $doSplit = intval($request->get('split_journal')) === 1; - $journalData = $request->getJournalData(); + $doSplit = intval($request->get('split_journal')) === 1; + $createAnother = intval($request->get('create_another')) === 1; + $data = $request->getJournalData(); + $journal = $repository->store($data); + if (is_null($journal->id)) { + // error! + Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray()); + Session::flash('error', $journal->getErrors()->first()); + + return redirect(route('transactions.create', [$request->input('what')]))->withInput(); + } + + $this->attachments->saveAttachmentsForModel($journal); // store the journal only, flash the rest. - if ($doSplit) { - $journal = $repository->storeJournal($journalData); - $journal->completed = false; - $journal->save(); - - // store attachments: - $att->saveAttachmentsForModel($journal); - - // flash errors - if (count($att->getErrors()->get('attachments')) > 0) { - Session::flash('error', $att->getErrors()->get('attachments')); - } - // flash messages - if (count($att->getMessages()->get('attachments')) > 0) { - Session::flash('info', $att->getMessages()->get('attachments')); - } - - Session::put('journal-data', $journalData); - - return redirect(route('split.journal.create', [$journal->id])); - } - - - // if not withdrawal, unset budgetid. - if ($journalData['what'] != strtolower(TransactionType::WITHDRAWAL)) { - $journalData['budget_id'] = 0; - } - - $journal = $repository->store($journalData); - $att->saveAttachmentsForModel($journal); - - // flash errors - if (count($att->getErrors()->get('attachments')) > 0) { - Session::flash('error', $att->getErrors()->get('attachments')); + if (count($this->attachments->getErrors()->get('attachments')) > 0) { + Session::flash('error', $this->attachments->getErrors()->get('attachments')); } // flash messages - if (count($att->getMessages()->get('attachments')) > 0) { - Session::flash('info', $att->getMessages()->get('attachments')); + if (count($this->attachments->getMessages()->get('attachments')) > 0) { + Session::flash('info', $this->attachments->getMessages()->get('attachments')); } - event(new TransactionJournalStored($journal, intval($journalData['piggy_bank_id']))); + event(new TransactionJournalStored($journal, $data['piggy_bank_id'])); Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); Preferences::mark(); - if (intval($request->get('create_another')) === 1) { + if ($createAnother === true) { // set value so create routine will not overwrite URL: Session::put('transactions.create.fromStore', true); return redirect(route('transactions.create', [$request->input('what')]))->withInput(); } + if ($doSplit === true) { + // redirect to edit screen: + return redirect(route('transactions.edit', [$journal->id])); + } + + // redirect to previous URL. return redirect(session('transactions.create.url')); @@ -355,35 +351,31 @@ class TransactionController extends Controller /** - * @param JournalFormRequest $request - * @param JournalRepositoryInterface $repository - * @param AttachmentHelperInterface $att - * @param TransactionJournal $journal + * @param JournalFormRequest $request + * @param TransactionJournal $journal * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, AttachmentHelperInterface $att, TransactionJournal $journal) + public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { - $journalData = $request->getJournalData(); - $repository->update($journal, $journalData); - - // save attachments: - $att->saveAttachmentsForModel($journal); + $data = $request->getJournalData(); + $journal = $repository->update($journal, $data); + $this->attachments->saveAttachmentsForModel($journal); // flash errors - if (count($att->getErrors()->get('attachments')) > 0) { - Session::flash('error', $att->getErrors()->get('attachments')); + if (count($this->attachments->getErrors()->get('attachments')) > 0) { + Session::flash('error', $this->attachments->getErrors()->get('attachments')); } // flash messages - if (count($att->getMessages()->get('attachments')) > 0) { - Session::flash('info', $att->getMessages()->get('attachments')); + if (count($this->attachments->getMessages()->get('attachments')) > 0) { + Session::flash('info', $this->attachments->getMessages()->get('attachments')); } event(new TransactionJournalUpdated($journal)); // update, get events by date and sort DESC - $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal)); - Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($journalData['description'])]))); + $type = strtolower(TransactionJournal::transactionTypeStr($journal)); + Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])]))); Preferences::mark(); if (intval($request->get('return_to_edit')) === 1) { diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 9f1a5fcdf5..0076f5e341 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -14,7 +14,6 @@ declare(strict_types = 1); namespace FireflyIII\Http\Requests; use Carbon\Carbon; -use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionType; use Input; @@ -37,86 +36,110 @@ class JournalFormRequest extends Request } /** + * Returns and validates the data required to store a new journal. Can handle both single transaction journals and split journals. + * * @return array */ public function getJournalData() { - $tags = $this->getFieldOrEmptyString('tags'); + $data = [ + 'what' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer' + 'user' => auth()->user()->id, + 'date' => new Carbon($this->get('date')), + 'tags' => explode(',', $this->getFieldOrEmptyString('tags')), + 'currency_id' => intval($this->get('amount_currency_id_amount')), - return [ - 'what' => $this->get('what'), - 'description' => trim($this->get('description')), - 'source_account_id' => intval($this->get('source_account_id')), - 'source_account_name' => trim($this->getFieldOrEmptyString('source_account_name')), - 'destination_account_id' => intval($this->get('destination_account_id')), - 'destination_account_name' => trim($this->getFieldOrEmptyString('destination_account_name')), - 'amount' => round($this->get('amount'), 2), - 'user' => auth()->user()->id, - 'amount_currency_id_amount' => intval($this->get('amount_currency_id_amount')), - 'date' => new Carbon($this->get('date')), - 'interest_date' => $this->getDateOrNull('interest_date'), - 'book_date' => $this->getDateOrNull('book_date'), - 'process_date' => $this->getDateOrNull('process_date'), - 'budget_id' => intval($this->get('budget_id')), - 'category' => trim($this->getFieldOrEmptyString('category')), - 'tags' => explode(',', $tags), - 'piggy_bank_id' => intval($this->get('piggy_bank_id')), + // all custom fields: + 'interest_date' => $this->getDateOrNull('interest_date'), + 'book_date' => $this->getDateOrNull('book_date'), + 'process_date' => $this->getDateOrNull('process_date'), + 'due_date' => $this->getDateOrNull('due_date'), + 'payment_date' => $this->getDateOrNull('payment_date'), + 'invoice_date' => $this->getDateOrNull('invoice_date'), + 'internal_reference' => trim(strval($this->get('internal_reference'))), + 'notes' => trim(strval($this->get('notes'))), - // new custom fields here: - 'due_date' => $this->getDateOrNull('due_date'), - 'payment_date' => $this->getDateOrNull('payment_date'), - 'invoice_date' => $this->getDateOrNull('invoice_date'), - 'internal_reference' => trim(strval($this->get('internal_reference'))), - 'notes' => trim(strval($this->get('notes'))), + // transaction / journal data: + 'description' => $this->getFieldOrEmptyString('description'), + 'amount' => round($this->get('amount'), 2), + 'budget_id' => intval($this->get('budget_id')), + 'category' => $this->getFieldOrEmptyString('category'), + 'source_account_id' => intval($this->get('source_account_id')), + 'source_account_name' => $this->getFieldOrEmptyString('source_account_name'), + 'destination_account_id' => $this->getFieldOrEmptyString('destination_account_id'), + 'destination_account_name' => $this->getFieldOrEmptyString('destination_account_name'), + 'piggy_bank_id' => intval($this->get('piggy_bank_id')), ]; + + return $data; } /** * @return array - * @throws Exception */ public function rules() { $what = Input::get('what'); $rules = [ - 'description' => 'required|min:1,max:255', - 'what' => 'required|in:withdrawal,deposit,transfer', - 'amount' => 'numeric|required|min:0.01', - 'date' => 'required|date', - 'process_date' => 'date', - 'book_date' => 'date', - 'interest_date' => 'date', - 'category' => 'between:1,255', - 'amount_currency_id_amount' => 'required|exists:transaction_currencies,id', - 'piggy_bank_id' => 'numeric', + 'what' => 'required|in:withdrawal,deposit,transfer', + 'date' => 'required|date', - // new custom fields here: - 'due_date' => 'date', - 'payment_date' => 'date', - 'internal_reference' => 'min:1,max:255', - 'notes' => 'min:1,max:65536', + // then, custom fields: + 'interest_date' => 'date', + 'book_date' => 'date', + 'process_date' => 'date', + 'due_date' => 'date', + 'payment_date' => 'date', + 'invoice_date' => 'date', + 'internal_reference' => 'min:1,max:255', + 'notes' => 'min:1,max:50000', + // and then transaction rules: + 'description' => 'required|between:1,255', + 'amount' => 'numeric|required|min:0.01', + 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id', + 'category' => 'between:1,255', + 'source_account_id' => 'numeric|belongsToUser:accounts,id', + 'source_account_name' => 'between:1,255', + 'destination_account_id' => 'numeric|belongsToUser:accounts,id', + 'destination_account_name' => 'between:1,255', + 'piggy_bank_id' => 'between:1,255', ]; + // some rules get an upgrade depending on the type of data: + $rules = $this->enhanceRules($what, $rules); + + return $rules; + } + + /** + * Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A + * + * @param string $what + * @param array $rules + * + * @return array + * @throws FireflyException + */ + private function enhanceRules(string $what, array $rules): array + { switch ($what) { case strtolower(TransactionType::WITHDRAWAL): $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; $rules['destination_account_name'] = 'between:1,255'; - if (intval(Input::get('budget_id')) != 0) { - $rules['budget_id'] = 'exists:budgets,id|belongsToUser:budgets'; - } break; case strtolower(TransactionType::DEPOSIT): $rules['source_account_name'] = 'between:1,255'; $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; break; case strtolower(TransactionType::TRANSFER): + // this may not work: $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; break; default: - throw new FireflyException('Cannot handle transaction type of type ' . e($what) . '.'); + throw new FireflyException('Cannot handle transaction type of type ' . e($what) . ' . '); } return $rules; @@ -141,4 +164,63 @@ class JournalFormRequest extends Request { return $this->get($field) ?? ''; } + // + // /** + // * @param int $index + // * @param string $field + // * + // * @return int + // */ + // private function getIntFromArray(int $index, string $field): int + // { + // $array = $this->get($field); + // if (isset($array[$index])) { + // return intval($array[$index]); + // } + // + // return 0; + // } + // + // /** + // * @param int $index + // * @param string $field + // * + // * @return string + // */ + // private function getStringFromArray(int $index, string $field): string + // { + // $array = $this->get($field); + // if (isset($array[$index])) { + // return trim($array[$index]); + // } + // + // return ''; + // } + // + // /** + // * @return array + // */ + // private function getTransactionData(): array + // { + // $transactions = []; + // $array = $this->get('amount'); + // if (is_array($array) && count($array) > 0) { + // foreach ($array as $index => $amount) { + // $transaction = [ + // 'description' => $this->getStringFromArray($index, 'description'), + // 'amount' => round($amount, 2), + // 'budget_id' => $this->getIntFromArray($index, 'budget_id'), + // 'category' => $this->getStringFromArray($index, 'category'), + // 'source_account_id' => $this->getIntFromArray($index, 'source_account_id'), + // 'source_account_name' => $this->getStringFromArray($index, 'source_account_name'), + // 'destination_account_id' => $this->getIntFromArray($index, 'destination_account_id'), + // 'destination_account_name' => $this->getStringFromArray($index, 'destination_account_name'), + // 'piggy_bank_id' => $this->getIntFromArray($index, 'piggy_bank_id'), + // ]; + // $transactions[] = $transaction; + // } + // } + // + // return $transactions; + // } } diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php index e6437ec74d..8048fe9cd7 100644 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -15,7 +15,7 @@ namespace FireflyIII\Jobs; use Carbon\Carbon; use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalTaskerInterface; use FireflyIII\Rules\Processor; use FireflyIII\User; use Illuminate\Contracts\Queue\ShouldQueue; @@ -155,10 +155,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue */ protected function collectJournals() { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); + /** @var JournalTaskerInterface $tasker */ + $tasker = app(JournalTaskerInterface::class); - return $repository->getJournalsInRange($this->accounts, $this->startDate, $this->endDate); + return $tasker->getJournalsInRange($this->accounts, $this->startDate, $this->endDate); } /** diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index e4e7020d0f..f6f17b9429 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -434,6 +434,9 @@ class TransactionJournal extends TransactionJournalSupport return new TransactionJournalMeta(); } + if (is_string($value) && strlen($value) === 0) { + return new TransactionJournalMeta(); + } if ($value instanceof Carbon) { $value = $value->toW3cString(); diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index b1530b08ca..e8a9d8c592 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -15,6 +15,7 @@ namespace FireflyIII\Providers; use FireflyIII\Support\Amount; use FireflyIII\Support\ExpandedForm; +use FireflyIII\Support\ExpandedMultiForm; use FireflyIII\Support\FireflyConfig; use FireflyIII\Support\Navigation; use FireflyIII\Support\Preferences; @@ -92,6 +93,11 @@ class FireflyServiceProvider extends ServiceProvider return new ExpandedForm; } ); + $this->app->bind( + 'expandedmultiform', function () { + return new ExpandedMultiForm; + } + ); $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository'); $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search'); diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index c92b27c681..d4cfd695d7 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -13,24 +13,18 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; -use Carbon\Carbon; use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; -use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Query\JoinClause; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; use Log; /** @@ -100,88 +94,6 @@ class JournalRepository implements JournalRepositoryInterface return $entry; } - /** - * @param array $types - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator - { - $offset = ($page - 1) * $pageSize; - $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); - $query->where('transaction_journals.completed', 1); - if (count($types) > 0) { - $query->transactionTypes($types); - } - $count = $this->user->transactionJournals()->transactionTypes($types)->count(); - $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); - $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); - - return $journals; - } - - /** - * Returns a collection of ALL journals, given a specific account and a date range. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection - { - $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); - $query->where('transaction_journals.completed', 1); - $query->before($end); - $query->after($start); - - if ($accounts->count() > 0) { - $ids = $accounts->pluck('id')->toArray(); - // join source and destination: - $query->leftJoin( - 'transactions as source', function (JoinClause $join) { - $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); - } - ); - $query->leftJoin( - 'transactions as destination', function (JoinClause $join) { - $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); - } - ); - - $query->where( - function (Builder $q) use ($ids) { - $q->whereIn('destination.account_id', $ids); - $q->orWhereIn('source.account_id', $ids); - } - ); - } - - $set = $query->get(TransactionJournal::queryFields()); - - return $set; - } - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public function getPiggyBankEvents(TransactionJournal $journal): Collection - { - /** @var Collection $set */ - $events = $journal->piggyBankEvents()->get(); - $events->each( - function (PiggyBankEvent $event) { - $event->piggyBank = $event->piggyBank()->withTrashed()->first(); - } - ); - - return $events; - } /** * @param array $data @@ -192,56 +104,47 @@ class JournalRepository implements JournalRepositoryInterface { // find transaction type. $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); - - // store actual journal. - $journal = new TransactionJournal( + $journal = new TransactionJournal( [ 'user_id' => $data['user'], 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $data['amount_currency_id_amount'], + 'transaction_currency_id' => $data['currency_id'], 'description' => $data['description'], 'completed' => 0, 'date' => $data['date'], - 'interest_date' => $data['interest_date'], - 'book_date' => $data['book_date'], - 'process_date' => $data['process_date'], ] ); $journal->save(); - // store or get category - if (strlen($data['category']) > 0) { - $category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]); - $journal->categories()->save($category); - } + // store stuff: + $this->storeCategoryWithJournal($journal, $data['category']); + $this->storeBudgetWithJournal($journal, $data['budget_id']); + $accounts = $this->storeAccounts($transactionType, $data); - // store or get budget - if (intval($data['budget_id']) > 0 && $transactionType->type !== TransactionType::TRANSFER) { - /** @var \FireflyIII\Models\Budget $budget */ - $budget = Budget::find($data['budget_id']); - $journal->budgets()->save($budget); - } + // store two transactions: + $one = [ + 'journal' => $journal, + 'account' => $accounts['source'], + 'amount' => bcmul(strval($data['amount']), '-1'), + 'description' => null, + 'category' => null, + 'budget' => null, + 'identifier' => 0, + ]; + $this->storeTransaction($one); - // store accounts (depends on type) - list($sourceAccount, $destinationAccount) = $this->storeAccounts($transactionType, $data); + $two = [ + 'journal' => $journal, + 'account' => $accounts['destination'], + 'amount' => $data['amount'], + 'description' => null, + 'category' => null, + 'budget' => null, + 'identifier' => 0, + ]; + + $this->storeTransaction($two); - // store accompanying transactions. - Transaction::create( // first transaction. - [ - 'account_id' => $sourceAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $data['amount'] * -1, - ] - ); - Transaction::create( // second transaction. - [ - 'account_id' => $destinationAccount->id, - 'transaction_journal_id' => $journal->id, - 'amount' => $data['amount'], - ] - ); - $journal->completed = 1; - $journal->save(); // store tags if (isset($data['tags']) && is_array($data['tags'])) { @@ -256,6 +159,9 @@ class JournalRepository implements JournalRepositoryInterface Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); } + $journal->completed = 1; + $journal->save(); + return $journal; } @@ -302,45 +208,24 @@ class JournalRepository implements JournalRepositoryInterface */ public function update(TransactionJournal $journal, array $data): TransactionJournal { - // update actual journal. - $journal->transaction_currency_id = $data['amount_currency_id_amount']; + // update actual journal: + $journal->transaction_currency_id = $data['currency_id']; $journal->description = $data['description']; $journal->date = $data['date']; // unlink all categories, recreate them: $journal->categories()->detach(); - if (strlen($data['category']) > 0) { - $category = Category::firstOrCreateEncrypted(['name' => $data['category'], 'user_id' => $data['user']]); - $journal->categories()->save($category); - } - - // unlink all budgets and recreate them: $journal->budgets()->detach(); - if (intval($data['budget_id']) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) { - /** @var \FireflyIII\Models\Budget $budget */ - $budget = Budget::where('user_id', $this->user->id)->where('id', $data['budget_id'])->first(); - $journal->budgets()->save($budget); - } - // store accounts (depends on type) - list($fromAccount, $toAccount) = $this->storeAccounts($journal->transactionType, $data); + $this->storeCategoryWithJournal($journal, $data['category']); + $this->storeBudgetWithJournal($journal, $data['budget_id']); + $accounts = $this->storeAccounts($journal->transactionType, $data); - // update the from and to transaction. - /** @var Transaction $transaction */ - foreach ($journal->transactions()->get() as $transaction) { - if ($transaction->amount < 0) { - // this is the from transaction, negative amount: - $transaction->amount = $data['amount'] * -1; - $transaction->account_id = $fromAccount->id; - $transaction->save(); - } - if ($transaction->amount > 0) { - $transaction->amount = $data['amount']; - $transaction->account_id = $toAccount->id; - $transaction->save(); - } - } + $sourceAmount = bcmul(strval($data['amount']), '-1'); + $this->updateSourceTransaction($journal, $accounts['source'], $sourceAmount); // negative because source loses money. + $amount = strval($data['amount']); + $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money. $journal->save(); @@ -402,38 +287,66 @@ class JournalRepository implements JournalRepositoryInterface */ private function storeAccounts(TransactionType $type, array $data): array { - $sourceAccount = null; - $destinationAccount = null; + $accounts = [ + 'source' => null, + 'destination' => null, + ]; switch ($type->type) { case TransactionType::WITHDRAWAL: - list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($data); + $accounts = $this->storeWithdrawalAccounts($data); break; case TransactionType::DEPOSIT: - list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($data); + $accounts = $this->storeDepositAccounts($data); break; case TransactionType::TRANSFER: - $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(); - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(); + + $accounts['source'] = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(); + $accounts['destination'] = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(); break; default: - throw new FireflyException('Did not recognise transaction type.'); + throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type)); } - if (is_null($destinationAccount)) { + if (is_null($accounts['source'])) { Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]); throw new FireflyException('"destination"-account is null, so we cannot continue!'); } - if (is_null($sourceAccount)) { + if (is_null($accounts['destination'])) { Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]); throw new FireflyException('"source"-account is null, so we cannot continue!'); } - return [$sourceAccount, $destinationAccount]; + return $accounts; + } + + /** + * @param TransactionJournal $journal + * @param int $budgetId + */ + private function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId) + { + if (intval($budgetId) > 0 && $journal->transactionType->type !== TransactionType::TRANSFER) { + /** @var \FireflyIII\Models\Budget $budget */ + $budget = Budget::find($budgetId); + $journal->budgets()->save($budget); + } + } + + /** + * @param TransactionJournal $journal + * @param string $category + */ + private function storeCategoryWithJournal(TransactionJournal $journal, string $category) + { + if (strlen($category) > 0) { + $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]); + $journal->categories()->save($category); + } } /** @@ -446,19 +359,50 @@ class JournalRepository implements JournalRepositoryInterface $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); if (strlen($data['source_account_name']) > 0) { - $fromType = AccountType::where('type', 'Revenue account')->first(); - $fromAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => $data['source_account_name'], 'active' => 1] + $sourceType = AccountType::where('type', 'Revenue account')->first(); + $sourceAccount = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1] ); - return [$fromAccount, $destinationAccount]; + return [ + 'source' => $sourceAccount, + 'destination' => $destinationAccount, + ]; } - $fromType = AccountType::where('type', 'Cash account')->first(); - $fromAccount = Account::firstOrCreateEncrypted( - ['user_id' => $data['user'], 'account_type_id' => $fromType->id, 'name' => 'Cash account', 'active' => 1] + $sourceType = AccountType::where('type', 'Cash account')->first(); + $sourceAccount = Account::firstOrCreateEncrypted( + ['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1] ); - return [$fromAccount, $destinationAccount]; + return [ + 'source' => $sourceAccount, + 'destination' => $destinationAccount, + ]; + } + + + private function storeTransaction(array $data): Transaction + { + /** @var Transaction $transaction */ + $transaction = Transaction::create( + [ + 'transaction_journal_id' => $data['journal']->id, + 'account_id' => $data['account']->id, + 'amount' => $data['amount'], + 'description' => $data['description'], + 'identifier' => $data['identifier'], + ] + ); + if (!is_null($data['category'])) { + $transaction->categories()->save($data['category']); + } + + if (!is_null($data['budget'])) { + $transaction->categories()->save($data['budget']); + } + + return $transaction; + } /** @@ -481,14 +425,69 @@ class JournalRepository implements JournalRepositoryInterface ] ); - return [$sourceAccount, $destinationAccount]; + return [ + 'source' => $sourceAccount, + 'destination' => $destinationAccount, + ]; } $destinationType = AccountType::where('type', 'Cash account')->first(); $destinationAccount = Account::firstOrCreateEncrypted( ['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1] ); - return [$sourceAccount, $destinationAccount]; + return [ + 'source' => $sourceAccount, + 'destination' => $destinationAccount, + ]; + + + } + + /** + * @param TransactionJournal $journal + * @param Account $account + * @param string $amount + * + * @throws FireflyException + */ + private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount) + { + // should be one: + $set = $journal->transactions()->where('amount', '>', 0)->get(); + if ($set->count() != 1) { + throw new FireflyException( + sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count()) + ); + } + /** @var Transaction $transaction */ + $transaction = $set->first(); + $transaction->amount = $amount; + $transaction->account_id = $account->id; + $transaction->save(); + + } + + /** + * @param TransactionJournal $journal + * @param Account $account + * @param string $amount + * + * @throws FireflyException + */ + private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount) + { + // should be one: + $set = $journal->transactions()->where('amount', '<', 0)->get(); + if ($set->count() != 1) { + throw new FireflyException( + sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count()) + ); + } + /** @var Transaction $transaction */ + $transaction = $set->first(); + $transaction->amount = $amount; + $transaction->account_id = $account->id; + $transaction->save(); } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index e12acda50a..48090345c6 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -13,11 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; -use Carbon\Carbon; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; /** * Interface JournalRepositoryInterface @@ -52,34 +48,6 @@ interface JournalRepositoryInterface */ public function first(): TransactionJournal; - /** - * Returns a page of a specific type(s) of journal. - * - * @param array $types - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator; - - /** - * Returns a collection of ALL journals, given a specific account and a date range. - * - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection; - - /** - * @param TransactionJournal $journal - * - * @return Collection - */ - public function getPiggyBankEvents(TransactionJournal $journal): Collection; /** * @param array $data diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index 122e4527df..669bcda926 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -13,6 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; +use Carbon\Carbon; use Crypt; use DB; use FireflyIII\Models\Transaction; @@ -20,6 +21,8 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; /** * Class JournalTasker @@ -42,6 +45,91 @@ class JournalTasker implements JournalTaskerInterface $this->user = $user; } + /** + * Returns a page of a specific type(s) of journal. + * + * @param array $types + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator + { + $offset = ($page - 1) * $pageSize; + $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); + $query->where('transaction_journals.completed', 1); + if (count($types) > 0) { + $query->transactionTypes($types); + } + $count = $this->user->transactionJournals()->transactionTypes($types)->count(); + $set = $query->take($pageSize)->offset($offset)->get(TransactionJournal::queryFields()); + $journals = new LengthAwarePaginator($set, $count, $pageSize, $page); + + return $journals; + } + + /** + * Returns a collection of ALL journals, given a specific account and a date range. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection + { + $query = $this->user->transactionJournals()->expanded()->sortCorrectly(); + $query->where('transaction_journals.completed', 1); + $query->before($end); + $query->after($start); + + if ($accounts->count() > 0) { + $ids = $accounts->pluck('id')->toArray(); + // join source and destination: + $query->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0); + } + ); + $query->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0); + } + ); + + $query->where( + function (Builder $q) use ($ids) { + $q->whereIn('destination.account_id', $ids); + $q->orWhereIn('source.account_id', $ids); + } + ); + } + + $set = $query->get(TransactionJournal::queryFields()); + + return $set; + } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getPiggyBankEvents(TransactionJournal $journal): Collection + { + /** @var Collection $set */ + $events = $journal->piggyBankEvents()->get(); + $events->each( + function (PiggyBankEvent $event) { + $event->piggyBank = $event->piggyBank()->withTrashed()->first(); + } + ); + + return $events; + } + /** * Get an overview of the transactions of a journal, tailored to the view * that shows a transaction (transaction/show/xx). @@ -138,7 +226,6 @@ class JournalTasker implements JournalTaskerInterface return $transactions; } - /** * Collect the balance of an account before the given transaction has hit. This is tricky, because * the balance does not depend on the transaction itself but the journal it's part of. And of course diff --git a/app/Repositories/Journal/JournalTaskerInterface.php b/app/Repositories/Journal/JournalTaskerInterface.php index e84fa09c33..feada074f5 100644 --- a/app/Repositories/Journal/JournalTaskerInterface.php +++ b/app/Repositories/Journal/JournalTaskerInterface.php @@ -14,7 +14,10 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; +use Carbon\Carbon; use FireflyIII\Models\TransactionJournal; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; /** * Interface JournalTaskerInterface @@ -23,6 +26,34 @@ use FireflyIII\Models\TransactionJournal; */ interface JournalTaskerInterface { + /** + * Returns a page of a specific type(s) of journal. + * + * @param array $types + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getJournals(array $types, int $page, int $pageSize = 50): LengthAwarePaginator; + + /** + * Returns a collection of ALL journals, given a specific account and a date range. + * + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getJournalsInRange(Collection $accounts, Carbon $start, Carbon $end): Collection; + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getPiggyBankEvents(TransactionJournal $journal): Collection; /** * Get an overview of the transactions of a journal, tailored to the view diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php index 82b5429777..7491bbbb88 100644 --- a/app/Rules/TransactionMatcher.php +++ b/app/Rules/TransactionMatcher.php @@ -15,7 +15,7 @@ namespace FireflyIII\Rules; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalTaskerInterface; use Illuminate\Support\Collection; use Log; @@ -31,8 +31,8 @@ class TransactionMatcher private $limit = 10; /** @var int Maximum number of transaction to search in (for performance reasons) * */ private $range = 200; - /** @var JournalRepositoryInterface */ - private $repository; + /** @var JournalTaskerInterface */ + private $tasker; /** @var array */ private $transactionTypes = [TransactionType::DEPOSIT, TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; /** @var array List of triggers to match */ @@ -41,11 +41,11 @@ class TransactionMatcher /** * TransactionMatcher constructor. Typehint the repository. * - * @param JournalRepositoryInterface $repository + * @param JournalTaskerInterface $tasker */ - public function __construct(JournalRepositoryInterface $repository) + public function __construct(JournalTaskerInterface $tasker) { - $this->repository = $repository; + $this->tasker = $tasker; } @@ -76,7 +76,7 @@ class TransactionMatcher // - the maximum number of transactions to search in have been searched do { // Fetch a batch of transactions from the database - $paginator = $this->repository->getJournals($this->transactionTypes, $page, $pagesize); + $paginator = $this->tasker->getJournals($this->transactionTypes, $page, $pagesize); $set = $paginator->getCollection(); diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 36682352d3..495fd2d75c 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -427,6 +427,7 @@ class ExpandedForm */ protected function expandOptionArray(string $name, $label, array $options): array { + $name = str_replace('[]', '', $name); $options['class'] = 'form-control'; $options['id'] = 'ffInput_' . $name; $options['autocomplete'] = 'off'; @@ -494,6 +495,7 @@ class ExpandedForm if (isset($options['label'])) { return $options['label']; } + $name = str_replace('[]', '', $name); return strval(trans('form.' . $name)); diff --git a/app/Support/ExpandedMultiForm.php b/app/Support/ExpandedMultiForm.php new file mode 100644 index 0000000000..e8340c7b9b --- /dev/null +++ b/app/Support/ExpandedMultiForm.php @@ -0,0 +1,188 @@ +label($name, $options); + $options = $this->expandOptionArray($name, $index, $label, $options); + $classes = $this->getHolderClasses($name, $index); + $value = $this->fillFieldValue($name, $index, $value); + $options['step'] = 'any'; + $options['min'] = '0.01'; + $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); + $currencies = Amt::getAllCurrencies(); + $options['data-hiddenfield'] = 'amount_currency_id_' . $name . '_' . $index; + unset($options['currency']); + unset($options['placeholder']); + $html = view('form.multi.amount', compact('defaultCurrency', 'index', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); + + return $html; + + } + + /** + * @param string $name + * @param int $index + * @param array $list + * @param null $selected + * @param array $options + * + * @return string + */ + public function select(string $name, int $index, array $list = [], $selected = null, array $options = []): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $index, $label, $options); + $classes = $this->getHolderClasses($name, $index); + $selected = $this->fillFieldValue($name, $index, $selected); + unset($options['autocomplete']); + unset($options['placeholder']); + $html = view('form.multi.select', compact('classes', 'index', 'name', 'label', 'selected', 'options', 'list'))->render(); + + return $html; + } + + /** + * @param string $name + * @param int $index + * @param null $value + * @param array $options + * + * @return string + */ + public function text(string $name, int $index, $value = null, array $options = []): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $index, $label, $options); + $classes = $this->getHolderClasses($name, $index); + $value = $this->fillFieldValue($name, $index, $value); + $html = view('form.multi.text', compact('classes', 'name', 'index', 'label', 'value', 'options'))->render(); + + return $html; + + } + + /** + * @param string $name + * @param int $index + * @param string $label + * @param array $options + * + * @return array + */ + protected function expandOptionArray(string $name, int $index, string $label, array $options): array + { + $options['class'] = 'form-control'; + $options['id'] = 'ffInput_' . $name . '_' . $index; + $options['autocomplete'] = 'off'; + $options['placeholder'] = ucfirst($label); + + return $options; + } + + /** + * @param string $name + * @param int $index + * @param $value + * + * @return mixed + */ + protected function fillFieldValue(string $name, int $index, $value) + { + if (Session::has('preFilled')) { + $preFilled = session('preFilled'); + $value = isset($preFilled[$name][$index]) && is_null($value) ? $preFilled[$name][$index] : $value; + } + try { + if (!is_null(Input::old($name)[$index])) { + $value = Input::old($name)[$index]; + } + } catch (RuntimeException $e) { + // don't care about session errors. + } + if ($value instanceof Carbon) { + $value = $value->format('Y-m-d'); + } + + + return $value; + } + + /** + * @param string $name + * @param int $index + * + * @return string + */ + protected function getHolderClasses(string $name, int $index): string + { + /* + * Get errors from session: + */ + /** @var MessageBag $errors */ + $errors = session('errors'); + $classes = 'form-group'; + $set = []; + + if (!is_null($errors)) { + $set = $errors->get($name . '.' . $index); + } + + if (!is_null($errors) && count($set) > 0) { + $classes = 'form-group has-error has-feedback'; + } + + return $classes; + } + + /** + * @param string $name + * @param array $options + * + * @return string + */ + protected function label(string $name, array $options): string + { + if (isset($options['label'])) { + return $options['label']; + } + + return strval(trans('form.' . $name)); + + } +} \ No newline at end of file diff --git a/app/Support/Facades/ExpandedMultiForm.php b/app/Support/Facades/ExpandedMultiForm.php new file mode 100644 index 0000000000..064f3bb305 --- /dev/null +++ b/app/Support/Facades/ExpandedMultiForm.php @@ -0,0 +1,35 @@ +where($field, $value)->count(); + if ($count === 1) { + return true; + } + + return false; + } + /** * @param $attribute * diff --git a/config/app.php b/config/app.php index 0dfd97b9f8..d60998fa86 100755 --- a/config/app.php +++ b/config/app.php @@ -188,8 +188,8 @@ return [ // own stuff: - //Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, - //Barryvdh\Debugbar\ServiceProvider::class, + Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, + Barryvdh\Debugbar\ServiceProvider::class, DaveJamesMiller\Breadcrumbs\ServiceProvider::class, TwigBridge\ServiceProvider::class, 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider', @@ -226,50 +226,51 @@ return [ 'aliases' => [ - 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - 'Twig' => 'TwigBridge\Facade\Twig', - 'Form' => Collective\Html\FormFacade::class, - 'Html' => Collective\Html\HtmlFacade::class, - 'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade', - 'Preferences' => 'FireflyIII\Support\Facades\Preferences', - 'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig', - 'Navigation' => 'FireflyIII\Support\Facades\Navigation', - 'Amount' => 'FireflyIII\Support\Facades\Amount', - 'Steam' => 'FireflyIII\Support\Facades\Steam', - 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', - 'Entrust' => 'Zizaco\Entrust\EntrustFacade', - 'Input' => 'Illuminate\Support\Facades\Input', - 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', + 'App' => Illuminate\Support\Facades\App::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Notification' => Illuminate\Support\Facades\Notification::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'View' => Illuminate\Support\Facades\View::class, + 'Twig' => 'TwigBridge\Facade\Twig', + 'Form' => Collective\Html\FormFacade::class, + 'Html' => Collective\Html\HtmlFacade::class, + 'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade', + 'Preferences' => 'FireflyIII\Support\Facades\Preferences', + 'FireflyConfig' => 'FireflyIII\Support\Facades\FireflyConfig', + 'Navigation' => 'FireflyIII\Support\Facades\Navigation', + 'Amount' => 'FireflyIII\Support\Facades\Amount', + 'Steam' => 'FireflyIII\Support\Facades\Steam', + 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', + 'ExpandedMultiForm' => 'FireflyIII\Support\Facades\ExpandedMultiForm', + 'Entrust' => 'Zizaco\Entrust\EntrustFacade', + 'Input' => 'Illuminate\Support\Facades\Input', + 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', ], diff --git a/config/twigbridge.php b/config/twigbridge.php index 886f44d6e1..5a4822e22f 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -162,6 +162,11 @@ return [ 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', ], ], + 'ExpandedMultiForm' => [ + 'is_safe' => [ + 'text','select','amount' + ], + ], 'Form' => [ 'is_safe' => [ 'input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea', 'file', diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index d63eddbcc3..b3e4b7bf93 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -5,6 +5,9 @@ $(function () { // when you click on a currency, this happens: $('.currency-option').click(currencySelect); + // when you click on a multi currency, this happens: + $('.multi-currency-option').click(multiCurrencySelect); + var ranges = {}; ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])]; ranges[dateRangeConfig.previousPeriod] = [moment(dateRangeConfig.ranges.previous[0]), moment(dateRangeConfig.ranges.previous[1])]; @@ -57,6 +60,47 @@ $(function () { }); +function multiCurrencySelect(e) { + "use strict"; + // clicked on + var target = $(e.target); // target is the tag. + + // name of the field in question: + var name = target.data('name'); + + // index of the field in question: + var index = target.data('index'); + console.log('name is ' + name + ':' + index); + + // id of menu button (used later on): + var menuID = 'currency_dropdown_' + name + '_' + index; + + // the hidden input with the actual value of the selected currency: + var hiddenInputName = 'amount_currency_id_' + name + '_' + index; + console.log('Looking for hidden input: ' + hiddenInputName); + + // span with the current selection (next to the caret): + var spanId = 'currency_select_symbol_' + name + '_' + index; + + // the selected currency symbol: + var symbol = target.data('symbol'); + + // id of the selected currency. + var id = target.data('id'); + + // update the hidden input: + $('input[name="' + hiddenInputName + '"]').val(id); + + // update the symbol: + $('#' + spanId).text(symbol); + + // close the menu (hack hack) + $('#' + menuID).click(); + + + return false; +} + function currencySelect(e) { "use strict"; // clicked on diff --git a/resources/views/form/date.twig b/resources/views/form/date.twig index f14289d1fe..3f08f20e3b 100644 --- a/resources/views/form/date.twig +++ b/resources/views/form/date.twig @@ -2,7 +2,12 @@
    - {{ Form.input('date', name, value, options) }} +
    +
    + +
    + {{ Form.input('date', name, value, options) }} +
    {% include 'form/help.twig' %} {% include 'form/feedback.twig' %}
    diff --git a/resources/views/form/multi/amount.twig b/resources/views/form/multi/amount.twig new file mode 100644 index 0000000000..4e6fc1590f --- /dev/null +++ b/resources/views/form/multi/amount.twig @@ -0,0 +1,30 @@ +
    + +
    +
    + + {{ Form.input('number', name~'['~index~']', value, options) }} +
    + {% include 'form.multi.feedback.twig' %} +
    + + +
    diff --git a/resources/views/form/multi/feedback.twig b/resources/views/form/multi/feedback.twig new file mode 100644 index 0000000000..92c00d26d9 --- /dev/null +++ b/resources/views/form/multi/feedback.twig @@ -0,0 +1,4 @@ +{% if errors.has(name~'.'~index) %} + +

    {{ errors.first(name~'.'~index) }}

    +{% endif %} diff --git a/resources/views/form/multi/select.twig b/resources/views/form/multi/select.twig new file mode 100644 index 0000000000..1c6d81b31a --- /dev/null +++ b/resources/views/form/multi/select.twig @@ -0,0 +1,10 @@ +
    + + +
    + {{ Form.select(name~'['~index~']', list, selected , options ) }} + {% include 'form.help.twig' %} + {% include 'form.multi.feedback.twig' %} + +
    +
    diff --git a/resources/views/form/multi/text.twig b/resources/views/form/multi/text.twig new file mode 100644 index 0000000000..5f0b154fbd --- /dev/null +++ b/resources/views/form/multi/text.twig @@ -0,0 +1,9 @@ +
    + + +
    + {{ Form.input('text', name~'['~index~']', value, options) }} + {% include 'form/help.twig' %} + {% include 'form.multi.feedback.twig' %} +
    +
    diff --git a/resources/views/transactions/create.twig b/resources/views/transactions/create.twig index afa0b4b78c..fc1dabfcab 100644 --- a/resources/views/transactions/create.twig +++ b/resources/views/transactions/create.twig @@ -29,23 +29,22 @@
- + {# DESCRIPTION IS ALWAYS AVAILABLE #} {{ ExpandedForm.text('description') }} - - {{ ExpandedForm.select('source_account_id',assetAccounts, null, {label: trans('form.asset_source_account')}) }} + {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} + {{ ExpandedForm.select('source_account_id', assetAccounts, null, {label: trans('form.asset_source_account')}) }} - - {{ ExpandedForm.text('source_account_name',null, {label: trans('form.revenue_account')}) }} + {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} + {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} - - {{ ExpandedForm.text('destination_account_name',null, {label: trans('form.expense_account')}) }} + {# FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES #} + {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} - - {{ ExpandedForm.select('destination_account_id',assetAccounts, null, {label: trans('form.asset_destination_account')} ) }} + {# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #} + {{ ExpandedForm.select('destination_account_id', assetAccounts, null, {label: trans('form.asset_destination_account')} ) }} - - + {# ALWAYS SHOW AMOUNT #} {{ ExpandedForm.amount('amount') }} @@ -66,9 +65,9 @@
{% if budgets|length > 1 %} - {{ ExpandedForm.select('budget_id',budgets,0) }} + {{ ExpandedForm.select('budget_id', budgets, 0) }} {% else %} - {{ ExpandedForm.select('budget_id',budgets,0, {helpText: trans('firefly.no_budget_pointer')}) }} + {{ ExpandedForm.select('budget_id', budgets, 0, {helpText: trans('firefly.no_budget_pointer')}) }} {% endif %} @@ -78,7 +77,7 @@ {{ ExpandedForm.text('tags') }} - {{ ExpandedForm.select('piggy_bank_id',piggies) }} + {{ ExpandedForm.select('piggy_bank_id', piggies) }}
@@ -108,33 +107,33 @@
+ {# INTEREST DATE #} {% if optionalFields.interest_date %} - {{ ExpandedForm.date('interest_date') }} {% endif %} + {# BOOK DATE #} {% if optionalFields.book_date %} - {{ ExpandedForm.date('book_date') }} {% endif %} + {# PROCESSING DATE #} {% if optionalFields.process_date %} - {{ ExpandedForm.date('process_date') }} {% endif %} + {# DUE DATE #} {% if optionalFields.due_date %} - {{ ExpandedForm.date('due_date') }} {% endif %} + {# PAYMENT DATE #} {% if optionalFields.payment_date %} - {{ ExpandedForm.date('payment_date') }} {% endif %} + {# INVOICE DATE #} {% if optionalFields.invoice_date %} - {{ ExpandedForm.date('invoice_date') }} {% endif %} @@ -149,13 +148,14 @@

{{ 'optional_field_meta_business'|_ }}

+ + {# REFERENCE #} {% if optionalFields.internal_reference %} - {{ ExpandedForm.text('internal_reference') }} {% endif %} + {# NOTES #} {% if optionalFields.notes %} - {{ ExpandedForm.textarea('notes') }} {% endif %} @@ -170,8 +170,8 @@

{{ 'optional_field_attachments'|_ }}

+ {# ATTACHMENTS #} {% if optionalFields.attachments %} - {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} {% endif %}
@@ -193,6 +193,7 @@
+ #}
From a74cef439ba13ff951e7d48641b155f013eff504 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 19:20:03 +0200 Subject: [PATCH 28/45] For simplicity, split controller. --- .../Transaction/SingleController.php | 327 ++++++++++++++++++ .../Controllers/TransactionController.php | 284 +-------------- routes/web.php | 29 +- 3 files changed, 342 insertions(+), 298 deletions(-) create mode 100644 app/Http/Controllers/Transaction/SingleController.php diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php new file mode 100644 index 0000000000..a831e9529e --- /dev/null +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -0,0 +1,327 @@ +middleware( + function ($request, $next) { + $this->accounts = app(AccountRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + $this->piggyBanks = app(PiggyBankRepositoryInterface::class); + $this->attachments = app(AttachmentHelperInterface::class); + + return $next($request); + } + ); + + + } + + /** + * @param string $what + * + * @return View + */ + public function create(string $what = TransactionType::DEPOSIT) + { + $what = strtolower($what); + $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType(['Default account', 'Asset account'])); + $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); + $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); + $preFilled = Session::has('preFilled') ? session('preFilled') : []; + $subTitle = trans('form.add_new_' . $what); + $subTitleIcon = 'fa-plus'; + $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; + + Session::put('preFilled', $preFilled); + + // put previous url in session if not redirect from store (not "create another"). + if (session('transactions.create.fromStore') !== true) { + $url = URL::previous(); + Session::put('transactions.create.url', $url); + } + Session::forget('transactions.create.fromStore'); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'create-' . $what); + + asort($piggies); + + return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields')); + } + + /** + * Shows the form that allows a user to delete a transaction journal. + * + * @param TransactionJournal $journal + * + * @return View + */ + public function delete(TransactionJournal $journal) + { + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]); + + // put previous url in session + Session::put('transactions.delete.url', URL::previous()); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'delete-' . $what); + + return view('transactions.delete', compact('journal', 'subTitle', 'what')); + + + } + + /** + * @param JournalRepositoryInterface $repository + * @param TransactionJournal $transactionJournal + * + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal) + { + $type = TransactionJournal::transactionTypeStr($transactionJournal); + Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)]))); + + $repository->delete($transactionJournal); + + Preferences::mark(); + + // redirect to previous URL: + return redirect(session('transactions.delete.url')); + } + + /** + * @param TransactionJournal $journal + * + * @return mixed + */ + public function edit(TransactionJournal $journal) + { + $count = $journal->transactions()->count(); + if ($count > 2) { + return redirect(route('journal.edit-split', [$journal->id])); + } + + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType(['Default account', 'Asset account'])); + $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks()); + + // view related code + $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); + $what = strtolower(TransactionJournal::transactionTypeStr($journal)); + + // journal related code + $sourceAccounts = TransactionJournal::sourceAccountList($journal); + $destinationAccounts = TransactionJournal::destinationAccountList($journal); + $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; + $preFilled = [ + 'date' => TransactionJournal::dateAsString($journal), + 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), + 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), + 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), + 'category' => TransactionJournal::categoryAsString($journal), + 'budget_id' => TransactionJournal::budgetId($journal), + 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), + 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), + 'source_account_id' => $sourceAccounts->first()->id, + 'source_account_name' => $sourceAccounts->first()->name, + 'destination_account_id' => $destinationAccounts->first()->id, + 'destination_account_name' => $destinationAccounts->first()->name, + 'amount' => TransactionJournal::amountPositive($journal), + + // new custom fields: + 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'), + 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'), + 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'), + 'interal_reference' => $journal->getMeta('internal_reference'), + 'notes' => $journal->getMeta('notes'), + ]; + + if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { + $preFilled['destination_account_name'] = ''; + } + + if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { + $preFilled['source_account_name'] = ''; + } + + + Session::flash('preFilled', $preFilled); + Session::flash('gaEventCategory', 'transactions'); + Session::flash('gaEventAction', 'edit-' . $what); + + // put previous url in session if not redirect from store (not "return_to_edit"). + if (session('transactions.edit.fromUpdate') !== true) { + Session::put('transactions.edit.url', URL::previous()); + } + Session::forget('transactions.edit.fromUpdate'); + + return view( + 'transactions.edit', + compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle') + )->with('data', $preFilled); + } + + /** + * @param JournalFormRequest $request + * @param JournalRepositoryInterface $repository + * + * @return \Illuminate\Http\RedirectResponse + */ + public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) + { + $doSplit = intval($request->get('split_journal')) === 1; + $createAnother = intval($request->get('create_another')) === 1; + $data = $request->getJournalData(); + $journal = $repository->store($data); + if (is_null($journal->id)) { + // error! + Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray()); + Session::flash('error', $journal->getErrors()->first()); + + return redirect(route('transactions.create', [$request->input('what')]))->withInput(); + } + + $this->attachments->saveAttachmentsForModel($journal); + + // store the journal only, flash the rest. + if (count($this->attachments->getErrors()->get('attachments')) > 0) { + Session::flash('error', $this->attachments->getErrors()->get('attachments')); + } + // flash messages + if (count($this->attachments->getMessages()->get('attachments')) > 0) { + Session::flash('info', $this->attachments->getMessages()->get('attachments')); + } + + event(new TransactionJournalStored($journal, $data['piggy_bank_id'])); + + Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); + Preferences::mark(); + + if ($createAnother === true) { + // set value so create routine will not overwrite URL: + Session::put('transactions.create.fromStore', true); + + return redirect(route('transactions.create', [$request->input('what')]))->withInput(); + } + + if ($doSplit === true) { + // redirect to edit screen: + return redirect(route('transactions.edit', [$journal->id])); + } + + + // redirect to previous URL. + return redirect(session('transactions.create.url')); + + } + + /** + * @param JournalFormRequest $request + * @param TransactionJournal $journal + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) + { + $data = $request->getJournalData(); + $journal = $repository->update($journal, $data); + $this->attachments->saveAttachmentsForModel($journal); + + // flash errors + if (count($this->attachments->getErrors()->get('attachments')) > 0) { + Session::flash('error', $this->attachments->getErrors()->get('attachments')); + } + // flash messages + if (count($this->attachments->getMessages()->get('attachments')) > 0) { + Session::flash('info', $this->attachments->getMessages()->get('attachments')); + } + + event(new TransactionJournalUpdated($journal)); + // update, get events by date and sort DESC + + $type = strtolower(TransactionJournal::transactionTypeStr($journal)); + Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])]))); + Preferences::mark(); + + if (intval($request->get('return_to_edit')) === 1) { + // 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]); + } + + // redirect to previous URL. + return redirect(session('transactions.edit.url')); + + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index f2b30018df..8cd732a617 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -14,26 +14,12 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use ExpandedForm; -use FireflyIII\Events\TransactionJournalStored; -use FireflyIII\Events\TransactionJournalUpdated; -use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; -use FireflyIII\Http\Requests\JournalFormRequest; -use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; -use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Http\Request; -use Log; use Preferences; use Response; -use Session; -use Steam; -use URL; use View; /** @@ -43,16 +29,8 @@ use View; */ class TransactionController extends Controller { - /** @var AccountRepositoryInterface */ - private $accounts; - private $attachments; - /** @var BudgetRepositoryInterface */ - private $budgets; - /** @var PiggyBankRepositoryInterface */ - private $piggyBanks; - /** - * + * TransactionController constructor. */ public function __construct() { @@ -60,171 +38,6 @@ class TransactionController extends Controller View::share('title', trans('firefly.transactions')); View::share('mainTitleIcon', 'fa-repeat'); - $maxFileSize = Steam::phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = Steam::phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - View::share('uploadSize', $uploadSize); - - // some useful repositories: - $this->middleware( - function ($request, $next) { - $this->accounts = app(AccountRepositoryInterface::class); - $this->budgets = app(BudgetRepositoryInterface::class); - $this->piggyBanks = app(PiggyBankRepositoryInterface::class); - $this->attachments = app(AttachmentHelperInterface::class); - - return $next($request); - } - ); - - - } - - /** - * @param string $what - * - * @return View - */ - public function create(string $what = TransactionType::DEPOSIT) - { - $what = strtolower($what); - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType(['Default account', 'Asset account'])); - $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); - $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); - $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); - $preFilled = Session::has('preFilled') ? session('preFilled') : []; - $subTitle = trans('form.add_new_' . $what); - $subTitleIcon = 'fa-plus'; - $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - - Session::put('preFilled', $preFilled); - - // put previous url in session if not redirect from store (not "create another"). - if (session('transactions.create.fromStore') !== true) { - $url = URL::previous(); - Session::put('transactions.create.url', $url); - } - Session::forget('transactions.create.fromStore'); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'create-' . $what); - - asort($piggies); - - return view('transactions.create', compact('assetAccounts', 'subTitleIcon', 'uploadSize', 'budgets', 'what', 'piggies', 'subTitle', 'optionalFields')); - } - - /** - * Shows the form that allows a user to delete a transaction journal. - * - * @param TransactionJournal $journal - * - * @return View - */ - public function delete(TransactionJournal $journal) - { - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = trans('firefly.delete_' . $what, ['description' => $journal->description]); - - // put previous url in session - Session::put('transactions.delete.url', URL::previous()); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'delete-' . $what); - - return view('transactions.delete', compact('journal', 'subTitle', 'what')); - - - } - - /** - * @param JournalRepositoryInterface $repository - * @param TransactionJournal $transactionJournal - * - * @return \Illuminate\Http\RedirectResponse - */ - public function destroy(JournalRepositoryInterface $repository, TransactionJournal $transactionJournal) - { - $type = TransactionJournal::transactionTypeStr($transactionJournal); - Session::flash('success', strval(trans('firefly.deleted_' . $type, ['description' => e($transactionJournal->description)]))); - - $repository->delete($transactionJournal); - - Preferences::mark(); - - // redirect to previous URL: - return redirect(session('transactions.delete.url')); - } - - /** - * @param TransactionJournal $journal - * - * @return mixed - */ - public function edit(TransactionJournal $journal) - { - $count = $journal->transactions()->count(); - if ($count > 2) { - return redirect(route('journal.edit-split', [$journal->id])); - } - - $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType(['Default account', 'Asset account'])); - $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); - $piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks()); - - // view related code - $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - $what = strtolower(TransactionJournal::transactionTypeStr($journal)); - - // journal related code - $sourceAccounts = TransactionJournal::sourceAccountList($journal); - $destinationAccounts = TransactionJournal::destinationAccountList($journal); - $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - $preFilled = [ - 'date' => TransactionJournal::dateAsString($journal), - 'interest_date' => TransactionJournal::dateAsString($journal, 'interest_date'), - 'book_date' => TransactionJournal::dateAsString($journal, 'book_date'), - 'process_date' => TransactionJournal::dateAsString($journal, 'process_date'), - 'category' => TransactionJournal::categoryAsString($journal), - 'budget_id' => TransactionJournal::budgetId($journal), - 'piggy_bank_id' => TransactionJournal::piggyBankId($journal), - 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), - 'source_account_id' => $sourceAccounts->first()->id, - 'source_account_name' => $sourceAccounts->first()->name, - 'destination_account_id' => $destinationAccounts->first()->id, - 'destination_account_name' => $destinationAccounts->first()->name, - 'amount' => TransactionJournal::amountPositive($journal), - - // new custom fields: - 'due_date' => TransactionJournal::dateAsString($journal, 'due_date'), - 'payment_date' => TransactionJournal::dateAsString($journal, 'payment_date'), - 'invoice_date' => TransactionJournal::dateAsString($journal, 'invoice_date'), - 'interal_reference' => $journal->getMeta('internal_reference'), - 'notes' => $journal->getMeta('notes'), - ]; - - if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { - $preFilled['destination_account_name'] = ''; - } - - if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { - $preFilled['source_account_name'] = ''; - } - - - Session::flash('preFilled', $preFilled); - Session::flash('gaEventCategory', 'transactions'); - Session::flash('gaEventAction', 'edit-' . $what); - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (session('transactions.edit.fromUpdate') !== true) { - Session::put('transactions.edit.url', URL::previous()); - } - Session::forget('transactions.edit.fromUpdate'); - - return view( - 'transactions.edit', - compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'piggyBankList', 'subTitle') - )->with('data', $preFilled); } /** @@ -294,99 +107,4 @@ class TransactionController extends Controller } - - /** - * @param JournalFormRequest $request - * @param JournalRepositoryInterface $repository - * - * @return \Illuminate\Http\RedirectResponse - */ - public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) - { - $doSplit = intval($request->get('split_journal')) === 1; - $createAnother = intval($request->get('create_another')) === 1; - $data = $request->getJournalData(); - $journal = $repository->store($data); - if (is_null($journal->id)) { - // error! - Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray()); - Session::flash('error', $journal->getErrors()->first()); - - return redirect(route('transactions.create', [$request->input('what')]))->withInput(); - } - - $this->attachments->saveAttachmentsForModel($journal); - - // store the journal only, flash the rest. - if (count($this->attachments->getErrors()->get('attachments')) > 0) { - Session::flash('error', $this->attachments->getErrors()->get('attachments')); - } - // flash messages - if (count($this->attachments->getMessages()->get('attachments')) > 0) { - Session::flash('info', $this->attachments->getMessages()->get('attachments')); - } - - event(new TransactionJournalStored($journal, $data['piggy_bank_id'])); - - Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); - Preferences::mark(); - - if ($createAnother === true) { - // set value so create routine will not overwrite URL: - Session::put('transactions.create.fromStore', true); - - return redirect(route('transactions.create', [$request->input('what')]))->withInput(); - } - - if ($doSplit === true) { - // redirect to edit screen: - return redirect(route('transactions.edit', [$journal->id])); - } - - - // redirect to previous URL. - return redirect(session('transactions.create.url')); - - } - - - /** - * @param JournalFormRequest $request - * @param TransactionJournal $journal - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) - { - $data = $request->getJournalData(); - $journal = $repository->update($journal, $data); - $this->attachments->saveAttachmentsForModel($journal); - - // flash errors - if (count($this->attachments->getErrors()->get('attachments')) > 0) { - Session::flash('error', $this->attachments->getErrors()->get('attachments')); - } - // flash messages - if (count($this->attachments->getMessages()->get('attachments')) > 0) { - Session::flash('info', $this->attachments->getMessages()->get('attachments')); - } - - event(new TransactionJournalUpdated($journal)); - // update, get events by date and sort DESC - - $type = strtolower(TransactionJournal::transactionTypeStr($journal)); - Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])]))); - Preferences::mark(); - - if (intval($request->get('return_to_edit')) === 1) { - // 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]); - } - - // redirect to previous URL. - return redirect(session('transactions.edit.url')); - - } } diff --git a/routes/web.php b/routes/web.php index d9610b5675..b790792436 100755 --- a/routes/web.php +++ b/routes/web.php @@ -390,29 +390,28 @@ Route::group( /** * Transaction Controller */ - Route::get('/transactions/{what}', ['uses' => 'TransactionController@index', 'as' => 'transactions.index'])->where( - ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] - ); - Route::get('/transactions/create/{what}', ['uses' => 'TransactionController@create', 'as' => 'transactions.create'])->where( - ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] - ); - Route::get('/transaction/edit/{tj}', ['uses' => 'TransactionController@edit', 'as' => 'transactions.edit']); - Route::get('/transaction/delete/{tj}', ['uses' => 'TransactionController@delete', 'as' => 'transactions.delete']); + + // normal controller + Route::get('/transactions/{what}', ['uses' => 'TransactionController@index', 'as' => 'transactions.index'])->where(['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers']); Route::get('/transaction/show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'transactions.show']); - // transaction controller: - Route::post('/transactions/store/{what}', ['uses' => 'TransactionController@store', 'as' => 'transactions.store'])->where( - ['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers'] - ); - Route::post('/transaction/update/{tj}', ['uses' => 'TransactionController@update', 'as' => 'transactions.update']); - Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']); Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']); - // mass edit and mass delete. + // single controller + Route::get('/transactions/create/{what}', ['uses' => 'Transaction\SingleController@create', 'as' => 'transactions.create'])->where(['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers']); + Route::get('/transaction/edit/{tj}', ['uses' => 'Transaction\SingleController@edit', 'as' => 'transactions.edit']); + Route::get('/transaction/delete/{tj}', ['uses' => 'Transaction\SingleController@delete', 'as' => 'transactions.delete']); + Route::post('/transactions/store/{what}', ['uses' => 'Transaction\SingleController@store', 'as' => 'transactions.store'])->where(['what' => 'expenses|revenue|withdrawal|deposit|transfer|transfers']); + Route::post('/transaction/update/{tj}', ['uses' => 'Transaction\SingleController@update', 'as' => 'transactions.update']); + Route::post('/transaction/destroy/{tj}', ['uses' => 'Transaction\SingleController@destroy', 'as' => 'transactions.destroy']); + + // mass controller: Route::get('/transactions/mass-edit/{journalList}', ['uses' => 'Transaction\MassController@massEdit', 'as' => 'transactions.mass-edit']); Route::get('/transactions/mass-delete/{journalList}', ['uses' => 'Transaction\MassController@massDelete', 'as' => 'transactions.mass-delete']); Route::post('/transactions/mass-update', ['uses' => 'Transaction\MassController@massUpdate', 'as' => 'transactions.mass-update']); Route::post('/transactions/mass-destroy', ['uses' => 'Transaction\MassController@massDestroy', 'as' => 'transactions.mass-destroy']); + // split (will be here): + /** * POPUP Controllers */ From 6a553f77f3f2c74460e206db945aeefcecdefd03 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 21:41:31 +0200 Subject: [PATCH 29/45] Large update to fix split journals. --- .../Transaction/SingleController.php | 8 +- .../Transaction/SplitController.php | 362 +++++++----------- app/Models/Transaction.php | 2 +- .../Journal/JournalRepository.php | 181 ++++++++- .../Journal/JournalRepositoryInterface.php | 8 + app/Repositories/Journal/JournalTasker.php | 44 +-- public/js/ff/split/journal/from-store.js | 32 +- resources/views/split/journals/create.twig | 270 ------------- resources/views/transactions/create.twig | 2 +- .../edit-split.twig} | 215 +++++++---- resources/views/transactions/edit.twig | 8 +- routes/web.php | 10 +- 12 files changed, 510 insertions(+), 632 deletions(-) delete mode 100644 resources/views/split/journals/create.twig rename resources/views/{split/journals/edit.twig => transactions/edit-split.twig} (57%) diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index a831e9529e..169de3b707 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -49,7 +49,7 @@ class SingleController extends Controller /** @var BudgetRepositoryInterface */ private $budgets; - + /** @var PiggyBankRepositoryInterface */ private $piggyBanks; @@ -91,7 +91,7 @@ class SingleController extends Controller { $what = strtolower($what); $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); $piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks); @@ -166,10 +166,10 @@ class SingleController extends Controller { $count = $journal->transactions()->count(); if ($count > 2) { - return redirect(route('journal.edit-split', [$journal->id])); + return redirect(route('transactions.edit-split', [$journal->id])); } - $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType(['Default account', 'Asset account'])); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $piggyBankList = ExpandedForm::makeSelectListWithEmpty($this->piggyBanks->getPiggyBanks()); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 17f5994d39..8bdda780f8 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -15,19 +15,17 @@ namespace FireflyIII\Http\Controllers\Transaction; use ExpandedForm; -use FireflyIII\Crud\Split\JournalInterface; use FireflyIII\Events\TransactionJournalUpdated; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Http\Requests\SplitJournalFormRequest; -use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; +use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalTaskerInterface; use Illuminate\Http\Request; -use Illuminate\Support\Collection; -use Log; use Preferences; use Session; use Steam; @@ -42,6 +40,22 @@ use View; */ class SplitController extends Controller { + + /** @var AccountRepositoryInterface */ + private $accounts; + /** @var AttachmentHelperInterface */ + private $attachments; + /** @var BudgetRepositoryInterface */ + private $budgets; + /** @var CurrencyRepositoryInterface */ + private $currencies; + + /** @var JournalTaskerInterface */ + private $tasker; + // + // /** @var PiggyBankRepositoryInterface */ + // private $piggyBanks; + /** * */ @@ -50,47 +64,19 @@ class SplitController extends Controller parent::__construct(); View::share('mainTitleIcon', 'fa-share-alt'); View::share('title', trans('firefly.split-transactions')); - } - /** - * @param TransactionJournal $journal - * - * @return View - */ - public function create(TransactionJournal $journal) - { - $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - $piggyRepository = app('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface'); + // some useful repositories: + $this->middleware( + function ($request, $next) { + $this->accounts = app(AccountRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + $this->tasker = app(JournalTaskerInterface::class); + // $this->piggyBanks = app(PiggyBankRepositoryInterface::class); + $this->attachments = app(AttachmentHelperInterface::class); + $this->currencies = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); - $sessionData = session('journal-data', []); - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); - $piggyBanks = ExpandedForm::makeSelectListWithEmpty($piggyRepository->getPiggyBanksWithAmount()); - $subTitle = trans('form.add_new_' . $sessionData['what']); - $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - $subTitleIcon = 'fa-plus'; - $preFilled = [ - 'what' => $sessionData['what'] ?? 'withdrawal', - 'journal_amount' => $sessionData['amount'] ?? 0, - 'journal_source_account_id' => $sessionData['source_account_id'] ?? 0, - 'journal_source_account_name' => $sessionData['source_account_name'] ?? '', - 'description' => [$journal->description], - 'destination_account_name' => [$sessionData['destination_account_name']], - 'destination_account_id' => [$sessionData['destination_account_id']], - 'amount' => [$sessionData['amount']], - 'budget_id' => [$sessionData['budget_id']], - 'category' => [$sessionData['category']], - ]; - - return view( - 'split.journals.create', - compact('journal', 'piggyBanks', 'subTitle', 'optionalFields', 'subTitleIcon', 'preFilled', 'assetAccounts', 'currencies', 'budgets', 'uploadSize') + return $next($request); + } ); } @@ -102,17 +88,11 @@ class SplitController extends Controller */ public function edit(Request $request, TransactionJournal $journal) { - $currencyRepository = app('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface'); - $budgetRepository = app('FireflyIII\Repositories\Budget\BudgetRepositoryInterface'); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $currencies = ExpandedForm::makeSelectList($currencyRepository->get()); - $assetAccounts = ExpandedForm::makeSelectList($accountRepository->getAccountsByType(['Default account', 'Asset account'])); + $currencies = ExpandedForm::makeSelectList($this->currencies->get()); + $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - $budgets = ExpandedForm::makeSelectListWithEmpty($budgetRepository->getActiveBudgets()); + $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $preFilled = $this->arrayFromJournal($request, $journal); $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $subTitleIcon = 'fa-pencil'; @@ -127,7 +107,7 @@ class SplitController extends Controller Session::forget('transactions.edit-split.fromUpdate'); return view( - 'split.journals.edit', + 'transactions.edit-split', compact( 'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'amount', 'sourceAccounts', 'uploadSize', 'destinationAccounts', 'assetAccounts', @@ -136,63 +116,32 @@ class SplitController extends Controller ); } - /** - * @param JournalInterface $repository - * @param SplitJournalFormRequest $request - * @param TransactionJournal $journal - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function store(JournalInterface $repository, SplitJournalFormRequest $request, TransactionJournal $journal) - { - $data = $request->getSplitData(); - foreach ($data['transactions'] as $transaction) { - $repository->storeTransaction($journal, $transaction); - } - - $repository->markAsComplete($journal); - - Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); - Preferences::mark(); - - if (intval($request->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(); - } - - // redirect to previous URL. - return redirect(session('transactions.create.url')); - } /** - * @param TransactionJournal $journal - * @param SplitJournalFormRequest $request - * @param JournalInterface $repository - * @param AttachmentHelperInterface $att + * @param Request $request + * @param JournalRepositoryInterface $repository + * @param TransactionJournal $journal * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(TransactionJournal $journal, SplitJournalFormRequest $request, JournalInterface $repository, AttachmentHelperInterface $att) + public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { - $data = $request->getSplitData(); - $journal = $repository->updateJournal($journal, $data); + $data = $this->arrayFromInput($request, $journal); + $journal = $repository->updateSplitJournal($journal, $data); // save attachments: - $att->saveAttachmentsForModel($journal); + $this->attachments->saveAttachmentsForModel($journal); event(new TransactionJournalUpdated($journal)); // update, get events by date and sort DESC // flash messages - if (count($att->getMessages()->get('attachments')) > 0) { - Session::flash('info', $att->getMessages()->get('attachments')); + if (count($this->attachments->getMessages()->get('attachments')) > 0) { + Session::flash('info', $this->attachments->getMessages()->get('attachments')); } - - $type = strtolower($journal->transaction_type_type ?? TransactionJournal::transactionTypeStr($journal)); + $type = strtolower(TransactionJournal::transactionTypeStr($journal)); Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['journal_description'])]))); Preferences::mark(); @@ -200,7 +149,7 @@ class SplitController extends Controller // set value so edit routine will not overwrite URL: Session::put('transactions.edit-split.fromUpdate', true); - return redirect(route('split.journal.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); + return redirect(route('transactions.edit-split', [$journal->id]))->withInput(['return_to_edit' => 1]); } // redirect to previous URL. @@ -208,6 +157,40 @@ class SplitController extends Controller } + /** + * @param Request $request + * @param TransactionJournal $journal + * + * @return array + */ + private function arrayFromInput(Request $request, TransactionJournal $journal): array + { + $array = [ + 'journal_description' => $request->get('journal_description'), + 'journal_source_account_id' => $request->get('journal_source_account_id'), + 'journal_source_account_name' => $request->get('journal_source_account_name'), + 'journal_destination_account_id' => $request->get('journal_destination_account_id'), + 'transaction_currency_id' => $request->get('transaction_currency_id'), + 'what' => $request->get('what'), + 'date' => $request->get('date'), + // all custom fields: + 'interest_date' => $request->get('interest_date'), + 'book_date' => $request->get('book_date'), + 'process_date' => $request->get('process_date'), + 'due_date' => $request->get('due_date'), + 'payment_date' => $request->get('payment_date'), + 'invoice_date' => $request->get('invoice_date'), + 'internal_reference' => $request->get('internal_reference'), + 'notes' => $request->get('notes'), + 'tags' => explode(',', $request->get('tags')), + + // transactions. + 'transactions' => $this->getTransactionDataFromRequest($request), + ]; + + return $array; + } + /** * @param Request $request * @param TransactionJournal $journal @@ -222,149 +205,80 @@ class SplitController extends Controller 'journal_description' => $request->old('journal_description', $journal->description), 'journal_amount' => TransactionJournal::amountPositive($journal), 'sourceAccounts' => $sourceAccounts, - 'journal_source_account_id' => $sourceAccounts->first()->id, - 'journal_source_account_name' => $sourceAccounts->first()->name, - 'journal_destination_account_id' => $destinationAccounts->first()->id, + 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), + 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), + 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), 'destinationAccounts' => $destinationAccounts, 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), 'date' => $request->old('date', $journal->date), - 'interest_date' => $request->old('interest_date', $journal->interest_date), - 'book_date' => $request->old('book_date', $journal->book_date), - 'process_date' => $request->old('process_date', $journal->process_date), - 'description' => [], - 'source_account_id' => [], - 'source_account_name' => [], - 'destination_account_id' => [], - 'destination_account_name' => [], - 'amount' => [], - 'budget_id' => [], - 'category' => [], + 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), + + // all custom fields: + 'interest_date' => $request->old('interest_date', $journal->getMeta('interest_date')), + 'book_date' => $request->old('book_date', $journal->getMeta('book_date')), + 'process_date' => $request->old('process_date', $journal->getMeta('process_date')), + 'due_date' => $request->old('due_date', $journal->getMeta('due_date')), + 'payment_date' => $request->old('payment_date', $journal->getMeta('payment_date')), + 'invoice_date' => $request->old('invoice_date', $journal->getMeta('invoice_date')), + 'internal_reference' => $request->old('internal_reference', $journal->getMeta('internal_reference')), + 'notes' => $request->old('notes', $journal->getMeta('notes')), + + // transactions. + 'transactions' => $this->getTransactionDataFromJournal($journal), ]; - // number of transactions present in old input: - $previousCount = count($request->old('description')); - - if ($previousCount === 0) { - // build from scratch - $transactions = $this->transactionsFromJournal($request, $journal); - $array['description'] = $transactions['description']; - $array['source_account_id'] = $transactions['source_account_id']; - $array['source_account_name'] = $transactions['source_account_name']; - $array['destination_account_id'] = $transactions['destination_account_id']; - $array['destination_account_name'] = $transactions['destination_account_name']; - $array['amount'] = $transactions['amount']; - $array['budget_id'] = $transactions['budget_id']; - $array['category'] = $transactions['category']; - - return $array; - } - - $index = 0; - while ($index < $previousCount) { - $description = $request->old('description')[$index] ?? ''; - $destinationId = $request->old('destination_account_id')[$index] ?? 0; - $destinationName = $request->old('destination_account_name')[$index] ?? ''; - $sourceId = $request->old('source_account_id')[$index] ?? 0; - $sourceName = $request->old('source_account_name')[$index] ?? ''; - $amount = $request->old('amount')[$index] ?? ''; - $budgetId = $request->old('budget_id')[$index] ?? 0; - $categoryName = $request->old('category')[$index] ?? ''; - - - // any transfer not from the source: - $array['description'][] = $description; - $array['source_account_id'][] = $sourceId; - $array['source_account_name'][] = $sourceName; - $array['destination_account_id'][] = $destinationId; - $array['destination_account_name'][] = $destinationName; - $array['amount'][] = $amount; - $array['budget_id'][] = intval($budgetId); - $array['category'][] = $categoryName; - $index++; - } - return $array; } /** - * @param Request $request * @param TransactionJournal $journal * * @return array */ - private function transactionsFromJournal(Request $request, TransactionJournal $journal): array + private function getTransactionDataFromJournal(TransactionJournal $journal): array { - /** @var Collection $transactions */ - $transactions = $journal->transactions()->get(); - - /* - * Splitted journals always have ONE source OR ONE destination. - * Withdrawals have ONE source (asset account) - * Deposits have ONE destination (asset account) - * Transfers have ONE of both (asset account) - */ - /** @var Account $singular */ - $singular = TransactionJournal::sourceAccountList($journal)->first(); - if ($journal->transactionType->type == TransactionType::DEPOSIT) { - /** @var Account $singular */ - $singular = TransactionJournal::destinationAccountList($journal)->first(); + $transactions = $this->tasker->getTransactionsOverview($journal); + $return = []; + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = [ + 'description' => $transaction['description'], + 'source_account_id' => $transaction['source_account_id'], + 'source_account_name' => $transaction['source_account_name'], + 'destination_account_id' => $transaction['destination_account_id'], + 'destination_account_name' => $transaction['destination_account_name'], + 'amount' => round($transaction['destination_amount'], 2), + 'budget_id' => $transaction['budget_id'], + 'category' => $transaction['category'], + ]; } - /* - * Loop all transactions. Collect info ONLY from the transaction that is NOT related to - * the singular account. - */ - $index = 0; - $return = [ - 'description' => [], - 'source_account_id' => [], - 'source_account_name' => [], - 'destination_account_id' => [], - 'destination_account_name' => [], - 'amount' => [], - 'budget_id' => [], - 'category' => [], - ]; + return $return; + } - Log::debug('now at transactionsFromJournal'); - - /** - * @var int $current - * @var Transaction $transaction - */ - foreach ($transactions as $current => $transaction) { - $budget = $transaction->budgets()->first(); - $category = $transaction->categories()->first(); - $budgetId = 0; - $categoryName = ''; - if (!is_null($budget)) { - $budgetId = $budget->id; - } - - if (!is_null($category)) { - $categoryName = $category->name; - } - - $budgetId = $request->old('budget_id')[$index] ?? $budgetId; - $categoryName = $request->old('category')[$index] ?? $categoryName; - $amount = $request->old('amount')[$index] ?? $transaction->amount; - $description = $request->old('description')[$index] ?? $transaction->description; - $destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name; - $sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name; - $amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount; - - if ($transaction->account_id !== $singular->id) { - $return['description'][] = $description; - $return['destination_account_id'][] = $transaction->account_id; - $return['destination_account_name'][] = $destinationName; - $return['source_account_name'][] = $sourceName; - $return['amount'][] = $amount; - $return['budget_id'][] = intval($budgetId); - $return['category'][] = $categoryName; - // only add one when "valid" transaction - $index++; - } + /** + * @param Request $request + * + * @return array + */ + private function getTransactionDataFromRequest(Request $request): array + { + $return = []; + $transactions = $request->get('transactions'); + foreach ($transactions as $transaction) { + $return[] = [ + 'description' => $transaction['description'], + 'source_account_id' => $transaction['source_account_id'] ?? 0, + 'source_account_name' => $transaction['source_account_name'] ?? '', + 'destination_account_id' => $transaction['destination_account_id'] ?? 0, + 'destination_account_name' => $transaction['destination_account_name'] ?? '', + 'amount' => round($transaction['amount'] ?? 0, 2), + 'budget_id' => intval($transaction['budget_id']), + 'category' => $transaction['category'] ?? '', + 'user' => auth()->user()->id, // needed for accounts. + 'piggy_bank_id' => $transaction['piggy_bank_id'] ?? 0, + ]; } return $return; diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index d2d82d17b4..2c007aca1a 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -55,7 +55,7 @@ class Transaction extends Model { protected $dates = ['created_at', 'updated_at', 'deleted_at']; - protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount']; + protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount','identifier']; protected $hidden = ['encrypted']; protected $rules = [ diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index d4cfd695d7..01dfc3f100 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -14,6 +14,7 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; use DB; +use FireflyIII\Events\TransactionStored; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; @@ -25,6 +26,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\User; +use Illuminate\Support\Collection; use Log; /** @@ -251,6 +253,92 @@ class JournalRepository implements JournalRepositoryInterface return $journal; } + /** + * Same as above but for transaction journal with multiple transactions. + * + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal + { + // update actual journal: + $journal->transaction_currency_id = $data['transaction_currency_id']; + $journal->description = $data['journal_description']; + $journal->date = $data['date']; + + // unlink all categories: + $journal->categories()->detach(); + $journal->budgets()->detach(); + + // update meta fields: + $result = $journal->save(); + if ($result) { + foreach ($data as $key => $value) { + if (in_array($key, $this->validMetaFields)) { + $journal->setMeta($key, $value); + continue; + } + Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id)); + } + + return $journal; + } + + + // update tags: + if (isset($data['tags']) && is_array($data['tags'])) { + $this->updateTags($journal, $data['tags']); + } + + // delete original transactions, and recreate them. + $journal->transactions()->delete(); + + // store each transaction. + $identifier = 0; + foreach ($data['transactions'] as $transaction) { + Log::debug(sprintf('Split journal update split transaction %d', $identifier)); + $transaction = $this->appendTransactionData($transaction, $data); + $this->storeSplitTransaction($journal, $transaction, $identifier); + $identifier++; + } + + $journal->save(); + + return $journal; + } + + /** + * When the user edits a split journal, each line is missing crucial data: + * + * - Withdrawal lines are missing the source account ID + * - Deposit lines are missing the destination account ID + * - Transfers are missing both. + * + * We need to append the array. + * + * @param array $transaction + * @param array $data + * + * @return array + */ + private function appendTransactionData(array $transaction, array $data): array + { + switch ($data['what']) { + case strtolower(TransactionType::TRANSFER): + case strtolower(TransactionType::WITHDRAWAL): + $transaction['source_account_id'] = intval($data['journal_source_account_id']); + break; + case strtolower(TransactionType::TRANSFER): + case strtolower(TransactionType::DEPOSIT): + $transaction['destination_account_id'] = intval($data['journal_destination_account_id']); + break; + } + + return $transaction; + } + /** * * * Remember: a balancingAct takes at most one expense and one transfer. @@ -291,6 +379,7 @@ class JournalRepository implements JournalRepositoryInterface 'source' => null, 'destination' => null, ]; + Log::debug(sprintf('Going to store accounts for type %s', $type->type)); switch ($type->type) { case TransactionType::WITHDRAWAL: $accounts = $this->storeWithdrawalAccounts($data); @@ -310,13 +399,13 @@ class JournalRepository implements JournalRepositoryInterface } if (is_null($accounts['source'])) { - Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]); - throw new FireflyException('"destination"-account is null, so we cannot continue!'); + Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]); + throw new FireflyException('"source"-account is null, so we cannot continue!'); } if (is_null($accounts['destination'])) { - Log::error('"source"-account is null, so we cannot continue!', ['data' => $data]); - throw new FireflyException('"source"-account is null, so we cannot continue!'); + Log::error('"destination"-account is null, so we cannot continue!', ['data' => $data]); + throw new FireflyException('"destination"-account is null, so we cannot continue!'); } @@ -337,6 +426,19 @@ class JournalRepository implements JournalRepositoryInterface } } + /** + * @param Transaction $transaction + * @param int $budgetId + */ + private function storeBudgetWithTransaction(Transaction $transaction, int $budgetId) + { + if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) { + /** @var \FireflyIII\Models\Budget $budget */ + $budget = Budget::find($budgetId); + $transaction->budgets()->save($budget); + } + } + /** * @param TransactionJournal $journal * @param string $category @@ -349,6 +451,18 @@ class JournalRepository implements JournalRepositoryInterface } } + /** + * @param Transaction $transaction + * @param string $category + */ + private function storeCategoryWithTransaction(Transaction $transaction, string $category) + { + if (strlen($category) > 0) { + $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]); + $transaction->categories()->save($category); + } + } + /** * @param array $data * @@ -380,7 +494,61 @@ class JournalRepository implements JournalRepositoryInterface ]; } + /** + * @param TransactionJournal $journal + * @param array $transaction + * @param int $identifier + * + * @return Collection + */ + private function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection + { + // store source and destination accounts (depends on type) + $accounts = $this->storeAccounts($journal->transactionType, $transaction); + // store transaction one way: + $one = $this->storeTransaction( + [ + 'journal' => $journal, + 'account' => $accounts['source'], + 'amount' => bcmul(strval($transaction['amount']), '-1'), + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, + ] + ); + $this->storeCategoryWithTransaction($one, $transaction['category']); + $this->storeBudgetWithTransaction($one, $transaction['budget_id']); + + // and the other way: + $two = $this->storeTransaction( + [ + 'journal' => $journal, + 'account' => $accounts['destination'], + 'amount' => strval($transaction['amount']), + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, + ] + ); + $this->storeCategoryWithTransaction($two, $transaction['category']); + $this->storeBudgetWithTransaction($two, $transaction['budget_id']); + + if ($transaction['piggy_bank_id'] > 0) { + $transaction['date'] = $journal->date->format('Y-m-d'); + event(new TransactionStored($transaction)); + } + + return new Collection([$one, $two]); + } + + /** + * @param array $data + * + * @return Transaction + */ private function storeTransaction(array $data): Transaction { /** @var Transaction $transaction */ @@ -393,6 +561,9 @@ class JournalRepository implements JournalRepositoryInterface 'identifier' => $data['identifier'], ] ); + + Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id)); + if (!is_null($data['category'])) { $transaction->categories()->save($data['category']); } @@ -415,7 +586,7 @@ class JournalRepository implements JournalRepositoryInterface $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); if (strlen($data['destination_account_name']) > 0) { - $destinationType = AccountType::where('type', 'Expense account')->first(); + $destinationType = AccountType::where('type', AccountType::EXPENSE)->first(); $destinationAccount = Account::firstOrCreateEncrypted( [ 'user_id' => $data['user'], diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 48090345c6..5db74eb818 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -73,4 +73,12 @@ interface JournalRepositoryInterface */ public function update(TransactionJournal $journal, array $data): TransactionJournal; + /** + * @param TransactionJournal $journal + * @param array $data + * + * @return TransactionJournal + */ + public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal; + } diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index 669bcda926..cb8fa0a21f 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -175,12 +175,15 @@ class JournalTasker implements JournalTaskerInterface $join ->on('transactions.transaction_journal_id', '=', 'destination.transaction_journal_id') ->where('transactions.amount', '=', DB::raw('destination.amount * -1')) - ->where('transactions.identifier', '=', DB::raw('destination.identifier')); + ->where('transactions.identifier', '=', DB::raw('destination.identifier')) + ->whereNull('destination.deleted_at'); } ) + ->with(['budgets', 'categories']) ->leftJoin('accounts as source_accounts', 'transactions.account_id', '=', 'source_accounts.id') ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id') ->where('transactions.amount', '<', 0) + ->whereNull('transactions.deleted_at') ->get( [ 'transactions.id', @@ -202,6 +205,8 @@ class JournalTasker implements JournalTaskerInterface foreach ($set as $entry) { $sourceBalance = $this->getBalance($entry->id); $destinationBalance = $this->getBalance($entry->destination_id); + $budget = $entry->budgets->first(); + $category = $entry->categories->first(); $transaction = [ 'source_id' => $entry->id, 'source_amount' => $entry->amount, @@ -218,8 +223,11 @@ class JournalTasker implements JournalTaskerInterface intval($entry->destination_account_encrypted) === 1 ? Crypt::decrypt($entry->destination_account_name) : $entry->destination_account_name, 'destination_account_before' => $destinationBalance, 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')), + 'budget_id' => is_null($budget) ? 0 : $budget->id, + 'category' => is_null($category) ? '' : $category->name, ]; + $transactions[] = $transaction; } @@ -237,40 +245,6 @@ class JournalTasker implements JournalTaskerInterface */ private function getBalance(int $transactionId): string { - /* -select - --- transactions.*, transaction_journals.date, transaction_journals.order, transaction_journals.id, transactions.identifier -sum(transactions.amount) - -from transactions - - -and ( - -- first things first: remove all transaction journals that are newer by selecting only those that are earlier: - or - -- date is 03 but sorted lower: (fucntion 1) - ( - transaction_journals.date = "2016-09-20" - and transaction_journals.order > 2) - or - -- date is 03 and sort is the same but id is higher (func 2) - (transaction_journals.date = "2016-09-20" - and transaction_journals.order = 2 - and transaction_journals.id < 6966 - ) - -- date is 03 and sort is the same, and id is the same but identifier is 1 and not 0.(func 3) - or - (transaction_journals.date = "2016-09-20" - and transaction_journals.order = 2 - and transaction_journals.id = 6966 - and transactions.identifier > 1 - ) -) -- 14048 -and transactions.id != 14048 -- just in case - -order by transaction_journals.date DESC, transaction_journals.order ASC, transaction_journals.id DESC, transactions.identifier ASC - */ // find the transaction first: $transaction = Transaction::find($transactionId); $date = $transaction->transactionJournal->date->format('Y-m-d'); diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js index 5c8b0ca708..e20b1c5aac 100644 --- a/public/js/ff/split/journal/from-store.js +++ b/public/js/ff/split/journal/from-store.js @@ -18,40 +18,58 @@ $(function () { $.getJSON('json/expense-accounts').done(function (data) { destAccounts = data; console.log('destAccounts length is now ' + destAccounts.length); + $('input[name$="destination_account_name]"]').typeahead({source: destAccounts}); }); $.getJSON('json/revenue-accounts').done(function (data) { srcAccounts = data; console.log('srcAccounts length is now ' + srcAccounts.length); + $('input[name$="source_account_name]"]').typeahead({source: srcAccounts}); }); $.getJSON('json/categories').done(function (data) { categories = data; console.log('categories length is now ' + categories.length); + $('input[name$="category]"]').typeahead({source: categories}); }); - $('input[name="amount[]"]').on('input', calculateSum) + $('input[name$="][amount]"]').on('input', calculateSum); + + // add auto complete: + + + + }); function cloneRow() { "use strict"; var source = $('.initial-row').clone(); var count = $('.split-table tbody tr').length + 1; + var index = count - 1; source.removeClass('initial-row'); source.find('.count').text('#' + count); - source.find('input[name="amount[]"]').val("").on('input', calculateSum); + + // get each input, change the name? + $.each(source.find('input, select'), function (i, v) { + var obj = $(v); + var name = obj.attr('name').replace('[0]', '[' + index + ']'); + obj.attr('name', name); + }); + + source.find('input[name$="][amount]"]').val("").on('input', calculateSum); if (destAccounts.length > 0) { console.log('Will be able to extend dest-accounts.'); - source.find('input[name="destination_account_name[]"]').typeahead({source: destAccounts}); + source.find('input[name$="destination_account_name]"]').typeahead({source: destAccounts}); } if (destAccounts.length > 0) { console.log('Will be able to extend src-accounts.'); - source.find('input[name="source_account_name[]"]').typeahead({source: srcAccounts}); + source.find('input[name$="source_account_name]"]').typeahead({source: srcAccounts}); } - if(categories.length > 0) { + if (categories.length > 0) { console.log('Will be able to extend categories.'); - source.find('input[name="category[]"]').typeahead({source: categories}); + source.find('input[name$="category]"]').typeahead({source: categories}); } $('.split-table tbody').append(source); @@ -64,7 +82,7 @@ function cloneRow() { function calculateSum() { "use strict"; var sum = 0; - var set = $('input[name="amount[]"]'); + var set = $('input[name$="][amount]"]'); for (var i = 0; i < set.length; i++) { var current = $(set[i]); sum += (current.val() == "" ? 0 : parseFloat(current.val())); diff --git a/resources/views/split/journals/create.twig b/resources/views/split/journals/create.twig deleted file mode 100644 index 8656bf9870..0000000000 --- a/resources/views/split/journals/create.twig +++ /dev/null @@ -1,270 +0,0 @@ -{% extends "./layout/default.twig" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, preFilled.what) }} -{% endblock %} -{% block content %} -
- - - - - - - - {% if errors.all()|length > 0 %} -
-
-
-
-

{{ 'errors'|_ }}

-
-
-
    - {% for key, err in errors.all() %} -
  • {{ err }}
  • - {% endfor %} -
-
-
-
-
- {% endif %} -
-
-
-
-

{{ 'transaction_data'|_ }}

-
-
- {{ ExpandedForm.text('journal_description', journal.description) }} - {{ ExpandedForm.select('journal_currency_id', currencies, journal.transaction_currency_id) }} - {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} - - - - {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} - {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} - {% endif %} - - - {% if preFilled.what == 'deposit' %} - {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} - {% endif %} - - - {% if preFilled.what == 'transfer' %} - {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} - {% endif %} -
-
-
-
-
-
-

{{ 'transaction_meta_data'|_ }}

-
-
- {{ ExpandedForm.date('date', journal.date) }} - - {% if optionalFields.interest_date or journal.interest_date %} - - {{ ExpandedForm.date('interest_date', journal.interest_date) }} - {% endif %} - - {% if optionalFields.book_date or journal.book_date %} - - {{ ExpandedForm.date('book_date', journal.book_date) }} - {% endif %} - - {% if optionalFields.process_date or journal.process_date %} - - {{ ExpandedForm.date('process_date', journal.process_date) }} - {% endif %} - - {% if optionalFields.due_date or journal.due_date %} - - {{ ExpandedForm.date('due_date', journal.due_date) }} - {% endif %} - - {% if optionalFields.payment_date or journal.payment_date %} - - {{ ExpandedForm.date('payment_date', journal.payment_date) }} - {% endif %} - - {% if optionalFields.internal_reference or journal.internal_reference %} - - {{ ExpandedForm.text('internal_reference', journal.internal_reference) }} - {% endif %} - - {% if optionalFields.notes or journal.notes %} - - {{ ExpandedForm.textarea('notes', journal.notes) }} - {% endif %} -
-
- -
-
-
-
-
-
-

{{ 'splits'|_ }}

-
-
- - - - - - - - - {% if preFilled.what == 'withdrawal' %} - - {% endif %} - - {% if preFilled.what == 'deposit' %} - - {% endif %} - - - - - - {% if preFilled.what == 'withdrawal' %} - - {% endif %} - - - {% if preFilled.what == 'transfer' %} - - {% endif %} - - - - {% for index, descr in preFilled.description %} - - - - - - {% if preFilled.what == 'withdrawal' %} - - {% endif %} - - - {% if preFilled.what == 'deposit' %} - - {% endif %} - - - - {% if preFilled.what == 'withdrawal' %} - - {% endif %} - - {% if preFilled.what == 'transfer' %} - - {% endif %} - - {% endfor %} - -
{{ trans('list.split_number') }}{{ trans('list.description') }}{{ trans('list.destination') }}{{ trans('list.source') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}{{ trans('list.piggy_bank') }}
#{{ loop.index }} - - - - - - - - - - - - - - {{ Form.select('piggy_bank_id[]',piggyBanks, preFilled.piggy_bank_id[index], {class: 'form-control'}) }} -
-

-
- {{ 'add_another_split'|_ }} -

- -
-
-
-
-
- - {% if optionalFields.attachments %} -
-
-
-

{{ 'optionalFields'|_ }}

-
-
- - {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} -
-
-
- {% endif %} - - -
- -
-
-

{{ 'options'|_ }}

-
-
- {{ ExpandedForm.optionsList('create','split-transaction') }} -
- -
-
-
-
- -{% endblock %} -{% block scripts %} - - - - -{% endblock %} -{% block styles %} -{% endblock %} diff --git a/resources/views/transactions/create.twig b/resources/views/transactions/create.twig index fc1dabfcab..78de00ecd4 100644 --- a/resources/views/transactions/create.twig +++ b/resources/views/transactions/create.twig @@ -96,6 +96,7 @@

{{ trans('firefly.hidden_fields_preferences', {link: route('preferences')})|raw }}

{% endif %} + {% if optionalFields.interest_date or optionalFields.book_date or optionalFields.process_date @@ -136,7 +137,6 @@ {% if optionalFields.invoice_date %} {{ ExpandedForm.date('invoice_date') }} {% endif %} - {% endif %} diff --git a/resources/views/split/journals/edit.twig b/resources/views/transactions/edit-split.twig similarity index 57% rename from resources/views/split/journals/edit.twig rename to resources/views/transactions/edit-split.twig index 69a74a16b7..91488e0936 100644 --- a/resources/views/split/journals/edit.twig +++ b/resources/views/transactions/edit-split.twig @@ -36,73 +36,148 @@

{{ 'transaction_data'|_ }}

- {{ ExpandedForm.text('journal_description', journal.description) }} - {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }} - {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} - - + {# DESCRIPTION IS ALWAYS AVAILABLE #} + {{ ExpandedForm.text('journal_description', journal.description) }} + + {# CURRENCY IS NEW FOR SPLIT JOURNALS #} + {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }} + + {# show source if withdrawal or transfer #} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} {% endif %} - + {# show destination account id, if deposit (is asset): #} {% if preFilled.what == 'deposit' %} {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} {% endif %} - + {# show static destination if transfer #} {% if preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_destination_account_id', assetAccounts, preFilled.journal_destination_account_id) }} {% endif %} + + {# TOTAL AMOUNT IS STATIC TEXT #} + {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} + + + {# DATE #} + {{ ExpandedForm.date('date', journal.date) }}
-

{{ 'transaction_meta_data'|_ }}

+

{{ 'optional_field_meta_data'|_ }}

- {{ ExpandedForm.date('date', journal.date) }} + {# NO BUDGET #} + {# NO CATEGORY #} - {% if optionalFields.interest_date or journal.interest_date %} - - {{ ExpandedForm.date('interest_date', journal.interest_date) }} - {% endif %} + {# ALWAYS TAGS #} + {{ ExpandedForm.text('tags', preFilled.tags) }} - {% if optionalFields.book_date or journal.book_date %} - - {{ ExpandedForm.date('book_date', journal.book_date) }} - {% endif %} - - {% if optionalFields.process_date or journal.process_date %} - - {{ ExpandedForm.date('process_date', journal.process_date) }} - {% endif %} - - {% if optionalFields.due_date or journal.due_date %} - - {{ ExpandedForm.date('due_date', journal.due_date) }} - {% endif %} - - {% if optionalFields.payment_date or journal.payment_date %} - - {{ ExpandedForm.date('payment_date', journal.payment_date) }} - {% endif %} - - {% if optionalFields.internal_reference or journal.internal_reference %} - - {{ ExpandedForm.text('internal_reference', journal.internal_reference) }} - {% endif %} - - {% if optionalFields.notes or journal.notes %} - - {{ ExpandedForm.textarea('notes', journal.notes) }} - {% endif %} + {# NO PIGGY BANK #}
+ {# EXPLANATION IF NECESSARY: #} + {% if + not optionalFields.interest_date or + not optionalFields.book_date or + not optionalFields.process_date or + not optionalFields.due_date or + not optionalFields.payment_date or + not optionalFields.invoice_date or + not optionalFields.internal_reference or + not optionalFields.notes or + not optionalFields.attachments %} +

+ {{ trans('firefly.hidden_fields_preferences', {link: route('preferences')})|raw }}

+ {% endif %} + + {# BOX FOR DATES #} + {% if + optionalFields.interest_date or optionalFields.book_date or optionalFields.process_date + or optionalFields.due_date or optionalFields.payment_date + or optionalFields.invoice_date %} +
+
+

{{ 'optional_field_meta_dates'|_ }}

+
+
+ + {# INTEREST DATE #} + {% if optionalFields.interest_date or journal.interest_date %} + {{ ExpandedForm.date('interest_date', journal.interest_date) }} + {% endif %} + + {# BOOK DATE #} + {% if optionalFields.book_date or journal.book_date %} + {{ ExpandedForm.date('book_date', journal.book_date) }} + {% endif %} + + {# PROCESSING DATE #} + {% if optionalFields.process_date or journal.process_date %} + {{ ExpandedForm.date('process_date', journal.process_date) }} + {% endif %} + + {# DUE DATE #} + {% if optionalFields.due_date or journal.due_date %} + {{ ExpandedForm.date('due_date', journal.due_date) }} + {% endif %} + + {# PAYMENT DATE #} + {% if optionalFields.payment_date or journal.payment_date %} + {{ ExpandedForm.date('payment_date', journal.payment_date) }} + {% endif %} + + {# INVOICE DATE #} + {% if optionalFields.invoice_date or journal.invoice_date %} + {{ ExpandedForm.date('invoice_date', journal.invoice_date) }} + {% endif %} +
+
+ {% endif %} + + {# BOX FOR BUSINESS FIELDS #} + {% if optionalFields.internal_reference or optionalFields.notes %} +
+
+

{{ 'optional_field_meta_business'|_ }}

+
+
+ + {# INTERNAL REFERENCE #} + {% if optionalFields.internal_reference or journal.internal_reference %} + {{ ExpandedForm.text('internal_reference', journal.internal_reference) }} + {% endif %} + + {# NOTES #} + {% if optionalFields.notes or journal.notes %} + {{ ExpandedForm.textarea('notes', journal.notes) }} + {% endif %} +
+
+ {% endif %} + + {# BOX FOR ATTACHMENTS #} + {% if optionalFields.attachments %} +
+
+

{{ 'optional_field_attachments'|_ }}

+
+
+ {# ATTACHMENTS #} + {% if optionalFields.attachments %} + {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} + {% endif %} +
+
+ {% endif %} +
@@ -115,39 +190,47 @@ + - + {# withdrawal and deposit have a destination. #} {% if preFilled.what == 'withdrawal' %} {% endif %} + {# DEPOSIT HAS A SOURCE #} {% if preFilled.what == 'deposit' %} {% endif %} - + {# only withdrawal has budget #} {% if preFilled.what == 'withdrawal' %} {% endif %} + + + - {% for index, descr in preFilled.description %} + {% for index, transaction in preFilled.transactions %} + {% if preFilled.what == 'withdrawal' %} {% endif %} @@ -155,22 +238,24 @@ {% if preFilled.what == 'deposit' %} {% endif %} + {% if preFilled.what == 'withdrawal' %} {% endif %} {% endfor %} @@ -189,30 +275,11 @@
{{ 'add_another_split'|_ }}

-
- {% if optionalFields.attachments %} -
-
-
-

{{ 'optionalFields'|_ }}

-
-
- - {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} -
-
-
- {% endif %}
@@ -231,14 +298,16 @@ {% endblock %} +{% block styles %} + +{% endblock %} {% block scripts %} + {% endblock %} -{% block styles %} -{% endblock %} diff --git a/resources/views/transactions/edit.twig b/resources/views/transactions/edit.twig index 03ed04c1ed..3c3b948850 100644 --- a/resources/views/transactions/edit.twig +++ b/resources/views/transactions/edit.twig @@ -33,15 +33,15 @@

{{ 'mandatoryFields'|_ }}

- + {# ALWAYS AVAILABLE #} {{ ExpandedForm.text('description',journal.description) }} - + {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} {% if what == 'transfer' or what == 'withdrawal' %} {{ ExpandedForm.select('source_account_id',assetAccounts, data.source_account_id, {label: trans('form.asset_source_account')}) }} {% endif %} - + {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} {% if what == 'deposit' %} {{ ExpandedForm.text('source_account_name',data.source_account_name, {label: trans('form.revenue_account')}) }} {% endif %} @@ -99,8 +99,8 @@ not optionalFields.process_date or not optionalFields.due_date or not optionalFields.payment_date or - not optionalFields.internal_reference or not optionalFields.invoice_date or + not optionalFields.internal_reference or not optionalFields.notes or not optionalFields.attachments %}

diff --git a/routes/web.php b/routes/web.php index b790792436..c22422058d 100755 --- a/routes/web.php +++ b/routes/web.php @@ -363,14 +363,6 @@ Route::group( */ Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']); - /** - * Split controller - */ - - Route::get('/transaction/create-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@create', 'as' => 'split.journal.create']); - Route::post('/transaction/store-split/{unfinishedJournal}', ['uses' => 'Transaction\SplitController@store', 'as' => 'split.journal.store']); - Route::get('/transaction/edit-split/{tj}', ['uses' => 'Transaction\SplitController@edit', 'as' => 'split.journal.edit']); - Route::post('/transaction/edit-split/{tj}', ['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']); /** * Tag Controller */ @@ -411,6 +403,8 @@ Route::group( Route::post('/transactions/mass-destroy', ['uses' => 'Transaction\MassController@massDestroy', 'as' => 'transactions.mass-destroy']); // split (will be here): + Route::get('/transaction/split/edit/{tj}', ['uses' => 'Transaction\SplitController@edit', 'as' => 'transactions.edit-split']); + Route::post('/transaction/split/update/{tj}', ['uses' => 'Transaction\SplitController@update', 'as' => 'split.journal.update']); /** * POPUP Controllers From 0dc188b083b0cd887975417aedd8f6bee77fb8bf Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 21:41:50 +0200 Subject: [PATCH 30/45] Removed old code --- app/Crud/Split/Journal.php | 221 ---------------------------- app/Crud/Split/JournalInterface.php | 50 ------- 2 files changed, 271 deletions(-) delete mode 100644 app/Crud/Split/Journal.php delete mode 100644 app/Crud/Split/JournalInterface.php diff --git a/app/Crud/Split/Journal.php b/app/Crud/Split/Journal.php deleted file mode 100644 index 6fdc6b33ab..0000000000 --- a/app/Crud/Split/Journal.php +++ /dev/null @@ -1,221 +0,0 @@ -user = $user; - } - - /** - * @param $journal - * - * @return bool - */ - public function markAsComplete(TransactionJournal $journal) - { - $journal->completed = 1; - $journal->save(); - - return true; - } - - /** - * @param TransactionJournal $journal - * @param array $transaction - * @param int $identifier - * - * @return Collection - */ - public function storeTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection - { - // store accounts (depends on type) - list($sourceAccount, $destinationAccount) = $this->storeAccounts($journal->transactionType->type, $transaction); - - // store transaction one way: - /** @var Transaction $one */ - $one = Transaction::create( - ['account_id' => $sourceAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'] * -1, - 'description' => $transaction['description'], 'identifier' => $identifier] - ); - $two = Transaction::create( - ['account_id' => $destinationAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $transaction['amount'], - 'description' => $transaction['description'], 'identifier' => $identifier] - ); - - if (strlen($transaction['category']) > 0) { - $category = Category::firstOrCreateEncrypted(['name' => $transaction['category'], 'user_id' => $journal->user_id]); - $one->categories()->save($category); - $two->categories()->save($category); - } - if (intval($transaction['budget_id']) > 0) { - $budget = Budget::find($transaction['budget_id']); - $one->budgets()->save($budget); - $two->budgets()->save($budget); - } - - if ($transaction['piggy_bank_id'] > 0) { - $transaction['date'] = $journal->date->format('Y-m-d'); - event(new TransactionStored($transaction)); - } - - return new Collection([$one, $two]); - } - - /** - * @param TransactionJournal $journal - * @param array $data - * - * @return TransactionJournal - */ - public function updateJournal(TransactionJournal $journal, array $data): TransactionJournal - { - $journal->description = $data['journal_description']; - $journal->transaction_currency_id = $data['journal_currency_id']; - $journal->date = $data['date']; - $journal->interest_date = $data['interest_date']; - $journal->book_date = $data['book_date']; - $journal->process_date = $data['process_date']; - $journal->save(); - - // delete original transactions, and recreate them. - $journal->transactions()->delete(); - - $identifier = 0; - foreach ($data['transactions'] as $transaction) { - $this->storeTransaction($journal, $transaction, $identifier); - $identifier++; - } - - $journal->completed = true; - $journal->save(); - - return $journal; - } - - /** - * @param string $type - * @param array $transaction - * - * @return array - * @throws FireflyException - */ - private function storeAccounts(string $type, array $transaction): array - { - $sourceAccount = null; - $destinationAccount = null; - switch ($type) { - case TransactionType::WITHDRAWAL: - list($sourceAccount, $destinationAccount) = $this->storeWithdrawalAccounts($transaction); - break; - case TransactionType::DEPOSIT: - list($sourceAccount, $destinationAccount) = $this->storeDepositAccounts($transaction); - break; - case TransactionType::TRANSFER: - $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['source_account_id'])->first(); - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $transaction['destination_account_id'])->first(); - break; - default: - throw new FireflyException('Cannot handle ' . e($type)); - } - - return [$sourceAccount, $destinationAccount]; - } - - /** - * @param array $data - * - * @return array - */ - private function storeDepositAccounts(array $data): array - { - $destinationAccount = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']); - - - if (isset($data['source_account_name']) && strlen($data['source_account_name']) > 0) { - $sourceType = AccountType::where('type', 'Revenue account')->first(); - $sourceAccount = Account::firstOrCreateEncrypted( - ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1] - ); - - return [$sourceAccount, $destinationAccount]; - } - - $sourceType = AccountType::where('type', 'Cash account')->first(); - $sourceAccount = Account::firstOrCreateEncrypted( - ['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1] - ); - - return [$sourceAccount, $destinationAccount]; - } - - /** - * @param array $data - * - * @return array - */ - private function storeWithdrawalAccounts(array $data): array - { - $sourceAccount = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(['accounts.*']); - - if (strlen($data['destination_account_name']) > 0) { - $destinationType = AccountType::where('type', 'Expense account')->first(); - $destinationAccount = Account::firstOrCreateEncrypted( - [ - 'user_id' => $this->user->id, - 'account_type_id' => $destinationType->id, - 'name' => $data['destination_account_name'], - 'active' => 1, - ] - ); - - return [$sourceAccount, $destinationAccount]; - } - $destinationType = AccountType::where('type', 'Cash account')->first(); - $destinationAccount = Account::firstOrCreateEncrypted( - ['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1] - ); - - return [$sourceAccount, $destinationAccount]; - - - } -} diff --git a/app/Crud/Split/JournalInterface.php b/app/Crud/Split/JournalInterface.php deleted file mode 100644 index e296a1114c..0000000000 --- a/app/Crud/Split/JournalInterface.php +++ /dev/null @@ -1,50 +0,0 @@ - Date: Fri, 21 Oct 2016 21:43:12 +0200 Subject: [PATCH 31/45] Remove code no longer used. --- app/Providers/FireflyServiceProvider.php | 6 - app/Support/ExpandedMultiForm.php | 188 ---------------------- app/Support/Facades/ExpandedMultiForm.php | 35 ---- config/app.php | 1 - config/twigbridge.php | 5 - public/js/ff/firefly.js | 44 ----- resources/views/form/multi/amount.twig | 30 ---- resources/views/form/multi/feedback.twig | 4 - resources/views/form/multi/select.twig | 10 -- resources/views/form/multi/text.twig | 9 -- 10 files changed, 332 deletions(-) delete mode 100644 app/Support/ExpandedMultiForm.php delete mode 100644 app/Support/Facades/ExpandedMultiForm.php delete mode 100644 resources/views/form/multi/amount.twig delete mode 100644 resources/views/form/multi/feedback.twig delete mode 100644 resources/views/form/multi/select.twig delete mode 100644 resources/views/form/multi/text.twig diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index e8a9d8c592..b1530b08ca 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -15,7 +15,6 @@ namespace FireflyIII\Providers; use FireflyIII\Support\Amount; use FireflyIII\Support\ExpandedForm; -use FireflyIII\Support\ExpandedMultiForm; use FireflyIII\Support\FireflyConfig; use FireflyIII\Support\Navigation; use FireflyIII\Support\Preferences; @@ -93,11 +92,6 @@ class FireflyServiceProvider extends ServiceProvider return new ExpandedForm; } ); - $this->app->bind( - 'expandedmultiform', function () { - return new ExpandedMultiForm; - } - ); $this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository'); $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search'); diff --git a/app/Support/ExpandedMultiForm.php b/app/Support/ExpandedMultiForm.php deleted file mode 100644 index e8340c7b9b..0000000000 --- a/app/Support/ExpandedMultiForm.php +++ /dev/null @@ -1,188 +0,0 @@ -label($name, $options); - $options = $this->expandOptionArray($name, $index, $label, $options); - $classes = $this->getHolderClasses($name, $index); - $value = $this->fillFieldValue($name, $index, $value); - $options['step'] = 'any'; - $options['min'] = '0.01'; - $defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); - $currencies = Amt::getAllCurrencies(); - $options['data-hiddenfield'] = 'amount_currency_id_' . $name . '_' . $index; - unset($options['currency']); - unset($options['placeholder']); - $html = view('form.multi.amount', compact('defaultCurrency', 'index', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render(); - - return $html; - - } - - /** - * @param string $name - * @param int $index - * @param array $list - * @param null $selected - * @param array $options - * - * @return string - */ - public function select(string $name, int $index, array $list = [], $selected = null, array $options = []): string - { - $label = $this->label($name, $options); - $options = $this->expandOptionArray($name, $index, $label, $options); - $classes = $this->getHolderClasses($name, $index); - $selected = $this->fillFieldValue($name, $index, $selected); - unset($options['autocomplete']); - unset($options['placeholder']); - $html = view('form.multi.select', compact('classes', 'index', 'name', 'label', 'selected', 'options', 'list'))->render(); - - return $html; - } - - /** - * @param string $name - * @param int $index - * @param null $value - * @param array $options - * - * @return string - */ - public function text(string $name, int $index, $value = null, array $options = []): string - { - $label = $this->label($name, $options); - $options = $this->expandOptionArray($name, $index, $label, $options); - $classes = $this->getHolderClasses($name, $index); - $value = $this->fillFieldValue($name, $index, $value); - $html = view('form.multi.text', compact('classes', 'name', 'index', 'label', 'value', 'options'))->render(); - - return $html; - - } - - /** - * @param string $name - * @param int $index - * @param string $label - * @param array $options - * - * @return array - */ - protected function expandOptionArray(string $name, int $index, string $label, array $options): array - { - $options['class'] = 'form-control'; - $options['id'] = 'ffInput_' . $name . '_' . $index; - $options['autocomplete'] = 'off'; - $options['placeholder'] = ucfirst($label); - - return $options; - } - - /** - * @param string $name - * @param int $index - * @param $value - * - * @return mixed - */ - protected function fillFieldValue(string $name, int $index, $value) - { - if (Session::has('preFilled')) { - $preFilled = session('preFilled'); - $value = isset($preFilled[$name][$index]) && is_null($value) ? $preFilled[$name][$index] : $value; - } - try { - if (!is_null(Input::old($name)[$index])) { - $value = Input::old($name)[$index]; - } - } catch (RuntimeException $e) { - // don't care about session errors. - } - if ($value instanceof Carbon) { - $value = $value->format('Y-m-d'); - } - - - return $value; - } - - /** - * @param string $name - * @param int $index - * - * @return string - */ - protected function getHolderClasses(string $name, int $index): string - { - /* - * Get errors from session: - */ - /** @var MessageBag $errors */ - $errors = session('errors'); - $classes = 'form-group'; - $set = []; - - if (!is_null($errors)) { - $set = $errors->get($name . '.' . $index); - } - - if (!is_null($errors) && count($set) > 0) { - $classes = 'form-group has-error has-feedback'; - } - - return $classes; - } - - /** - * @param string $name - * @param array $options - * - * @return string - */ - protected function label(string $name, array $options): string - { - if (isset($options['label'])) { - return $options['label']; - } - - return strval(trans('form.' . $name)); - - } -} \ No newline at end of file diff --git a/app/Support/Facades/ExpandedMultiForm.php b/app/Support/Facades/ExpandedMultiForm.php deleted file mode 100644 index 064f3bb305..0000000000 --- a/app/Support/Facades/ExpandedMultiForm.php +++ /dev/null @@ -1,35 +0,0 @@ - 'FireflyIII\Support\Facades\Amount', 'Steam' => 'FireflyIII\Support\Facades\Steam', 'ExpandedForm' => 'FireflyIII\Support\Facades\ExpandedForm', - 'ExpandedMultiForm' => 'FireflyIII\Support\Facades\ExpandedMultiForm', 'Entrust' => 'Zizaco\Entrust\EntrustFacade', 'Input' => 'Illuminate\Support\Facades\Input', 'Google2FA' => 'PragmaRX\Google2FA\Vendor\Laravel\Facade', diff --git a/config/twigbridge.php b/config/twigbridge.php index 5a4822e22f..886f44d6e1 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -162,11 +162,6 @@ return [ 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', ], ], - 'ExpandedMultiForm' => [ - 'is_safe' => [ - 'text','select','amount' - ], - ], 'Form' => [ 'is_safe' => [ 'input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea', 'file', diff --git a/public/js/ff/firefly.js b/public/js/ff/firefly.js index b3e4b7bf93..d63eddbcc3 100644 --- a/public/js/ff/firefly.js +++ b/public/js/ff/firefly.js @@ -5,9 +5,6 @@ $(function () { // when you click on a currency, this happens: $('.currency-option').click(currencySelect); - // when you click on a multi currency, this happens: - $('.multi-currency-option').click(multiCurrencySelect); - var ranges = {}; ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])]; ranges[dateRangeConfig.previousPeriod] = [moment(dateRangeConfig.ranges.previous[0]), moment(dateRangeConfig.ranges.previous[1])]; @@ -60,47 +57,6 @@ $(function () { }); -function multiCurrencySelect(e) { - "use strict"; - // clicked on - var target = $(e.target); // target is the tag. - - // name of the field in question: - var name = target.data('name'); - - // index of the field in question: - var index = target.data('index'); - console.log('name is ' + name + ':' + index); - - // id of menu button (used later on): - var menuID = 'currency_dropdown_' + name + '_' + index; - - // the hidden input with the actual value of the selected currency: - var hiddenInputName = 'amount_currency_id_' + name + '_' + index; - console.log('Looking for hidden input: ' + hiddenInputName); - - // span with the current selection (next to the caret): - var spanId = 'currency_select_symbol_' + name + '_' + index; - - // the selected currency symbol: - var symbol = target.data('symbol'); - - // id of the selected currency. - var id = target.data('id'); - - // update the hidden input: - $('input[name="' + hiddenInputName + '"]').val(id); - - // update the symbol: - $('#' + spanId).text(symbol); - - // close the menu (hack hack) - $('#' + menuID).click(); - - - return false; -} - function currencySelect(e) { "use strict"; // clicked on diff --git a/resources/views/form/multi/amount.twig b/resources/views/form/multi/amount.twig deleted file mode 100644 index 4e6fc1590f..0000000000 --- a/resources/views/form/multi/amount.twig +++ /dev/null @@ -1,30 +0,0 @@ -

- -
-
- - {{ Form.input('number', name~'['~index~']', value, options) }} -
- {% include 'form.multi.feedback.twig' %} -
- - -
diff --git a/resources/views/form/multi/feedback.twig b/resources/views/form/multi/feedback.twig deleted file mode 100644 index 92c00d26d9..0000000000 --- a/resources/views/form/multi/feedback.twig +++ /dev/null @@ -1,4 +0,0 @@ -{% if errors.has(name~'.'~index) %} - -

{{ errors.first(name~'.'~index) }}

-{% endif %} diff --git a/resources/views/form/multi/select.twig b/resources/views/form/multi/select.twig deleted file mode 100644 index 1c6d81b31a..0000000000 --- a/resources/views/form/multi/select.twig +++ /dev/null @@ -1,10 +0,0 @@ -
- - -
- {{ Form.select(name~'['~index~']', list, selected , options ) }} - {% include 'form.help.twig' %} - {% include 'form.multi.feedback.twig' %} - -
-
diff --git a/resources/views/form/multi/text.twig b/resources/views/form/multi/text.twig deleted file mode 100644 index 5f0b154fbd..0000000000 --- a/resources/views/form/multi/text.twig +++ /dev/null @@ -1,9 +0,0 @@ -
- - -
- {{ Form.input('text', name~'['~index~']', value, options) }} - {% include 'form/help.twig' %} - {% include 'form.multi.feedback.twig' %} -
-
From 4d1c271da693c94441cd876542c99074a759ed7e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 21:54:57 +0200 Subject: [PATCH 32/45] Renamed a route [skip ci] --- app/Http/breadcrumbs.php | 4 ++-- resources/views/transactions/edit.twig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 0624262575..17c71e60cd 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -606,9 +606,9 @@ Breadcrumbs::register( * SPLIT */ Breadcrumbs::register( - 'split.journal.edit', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { + 'transactions.edit-split', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); - $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('split.journal.edit', [$journal->id])); + $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit-split', [$journal->id])); } ); diff --git a/resources/views/transactions/edit.twig b/resources/views/transactions/edit.twig index 3c3b948850..5046d67512 100644 --- a/resources/views/transactions/edit.twig +++ b/resources/views/transactions/edit.twig @@ -215,7 +215,7 @@
From a05bc0eed0e94cdd637aa390e0b8210bd477ec54 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 21:57:10 +0200 Subject: [PATCH 33/45] Fix route [skip ci] --- app/Http/Controllers/Transaction/SingleController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 169de3b707..23861d9ff4 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -274,7 +274,7 @@ class SingleController extends Controller if ($doSplit === true) { // redirect to edit screen: - return redirect(route('transactions.edit', [$journal->id])); + return redirect(route('transactions.edit-split', [$journal->id])); } @@ -311,6 +311,9 @@ class SingleController extends Controller Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => e($data['description'])]))); Preferences::mark(); + // if wishes to split: + + if (intval($request->get('return_to_edit')) === 1) { // set value so edit routine will not overwrite URL: Session::put('transactions.edit.fromUpdate', true); From 316980efbd657fedbdf52e96d1ecbee1f6d3b7c6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 22:00:45 +0200 Subject: [PATCH 34/45] Fix unset variable. [skip ci] --- app/Http/Controllers/Transaction/SplitController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 8bdda780f8..f89527ff58 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -249,7 +249,7 @@ class SplitController extends Controller 'destination_account_id' => $transaction['destination_account_id'], 'destination_account_name' => $transaction['destination_account_name'], 'amount' => round($transaction['destination_amount'], 2), - 'budget_id' => $transaction['budget_id'], + 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'], ]; } From a4c081c8a5e92d56ee0d136cc24dc793031991a2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 21 Oct 2016 22:01:42 +0200 Subject: [PATCH 35/45] Fix unset variable. [skip ci] --- app/Http/Controllers/Transaction/SplitController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index f89527ff58..61be6d8987 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -274,7 +274,7 @@ class SplitController extends Controller 'destination_account_id' => $transaction['destination_account_id'] ?? 0, 'destination_account_name' => $transaction['destination_account_name'] ?? '', 'amount' => round($transaction['amount'] ?? 0, 2), - 'budget_id' => intval($transaction['budget_id']), + 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'] ?? '', 'user' => auth()->user()->id, // needed for accounts. 'piggy_bank_id' => $transaction['piggy_bank_id'] ?? 0, From 2346d2ec059bcf94f20e9941160352c87437798d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 07:28:31 +0200 Subject: [PATCH 36/45] Fine tuning split edit screens. --- .../Transaction/SplitController.php | 6 +- .../Journal/JournalRepository.php | 15 ++- public/js/ff/split/journal/from-store.js | 110 ++++++++++++++++-- resources/views/transactions/edit-split.twig | 9 +- 4 files changed, 115 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 61be6d8987..6fe08cb008 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -126,7 +126,6 @@ class SplitController extends Controller */ public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { - $data = $this->arrayFromInput($request, $journal); $journal = $repository->updateSplitJournal($journal, $data); @@ -170,7 +169,7 @@ class SplitController extends Controller 'journal_source_account_id' => $request->get('journal_source_account_id'), 'journal_source_account_name' => $request->get('journal_source_account_name'), 'journal_destination_account_id' => $request->get('journal_destination_account_id'), - 'transaction_currency_id' => $request->get('transaction_currency_id'), + 'currency_id' => $request->get('currency_id'), 'what' => $request->get('what'), 'date' => $request->get('date'), // all custom fields: @@ -208,7 +207,7 @@ class SplitController extends Controller 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), - 'transaction_currency_id' => $request->old('transaction_currency_id', $journal->transaction_currency_id), + 'currency_id' => $request->old('currency_id', $journal->transaction_currency_id), 'destinationAccounts' => $destinationAccounts, 'what' => strtolower(TransactionJournal::transactionTypeStr($journal)), 'date' => $request->old('date', $journal->date), @@ -277,7 +276,6 @@ class SplitController extends Controller 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'] ?? '', 'user' => auth()->user()->id, // needed for accounts. - 'piggy_bank_id' => $transaction['piggy_bank_id'] ?? 0, ]; } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 01dfc3f100..855e253211 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -264,10 +264,11 @@ class JournalRepository implements JournalRepositoryInterface public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal { // update actual journal: - $journal->transaction_currency_id = $data['transaction_currency_id']; + $journal->transaction_currency_id = $data['currency_id']; $journal->description = $data['journal_description']; $journal->date = $data['date']; - + $journal->save(); + // unlink all categories: $journal->categories()->detach(); $journal->budgets()->detach(); @@ -330,6 +331,9 @@ class JournalRepository implements JournalRepositoryInterface case strtolower(TransactionType::WITHDRAWAL): $transaction['source_account_id'] = intval($data['journal_source_account_id']); break; + } + + switch ($data['what']) { case strtolower(TransactionType::TRANSFER): case strtolower(TransactionType::DEPOSIT): $transaction['destination_account_id'] = intval($data['journal_destination_account_id']); @@ -379,6 +383,7 @@ class JournalRepository implements JournalRepositoryInterface 'source' => null, 'destination' => null, ]; + Log::debug(sprintf('Going to store accounts for type %s', $type->type)); switch ($type->type) { case TransactionType::WITHDRAWAL: @@ -390,7 +395,6 @@ class JournalRepository implements JournalRepositoryInterface break; case TransactionType::TRANSFER: - $accounts['source'] = Account::where('user_id', $this->user->id)->where('id', $data['source_account_id'])->first(); $accounts['destination'] = Account::where('user_id', $this->user->id)->where('id', $data['destination_account_id'])->first(); break; @@ -536,11 +540,6 @@ class JournalRepository implements JournalRepositoryInterface $this->storeCategoryWithTransaction($two, $transaction['category']); $this->storeBudgetWithTransaction($two, $transaction['budget_id']); - if ($transaction['piggy_bank_id'] > 0) { - $transaction['date'] = $journal->date->format('Y-m-d'); - event(new TransactionStored($transaction)); - } - return new Collection([$one, $two]); } diff --git a/public/js/ff/split/journal/from-store.js b/public/js/ff/split/journal/from-store.js index e20b1c5aac..77856a0156 100644 --- a/public/js/ff/split/journal/from-store.js +++ b/public/js/ff/split/journal/from-store.js @@ -14,6 +14,7 @@ var categories = {}; $(function () { "use strict"; $('.btn-do-split').click(cloneRow); + $('.remove-current-split').click(removeRow); $.getJSON('json/expense-accounts').done(function (data) { destAccounts = data; @@ -38,24 +39,42 @@ $(function () { // add auto complete: - - }); +function removeRow(e) { + "use strict"; + var rows = $('table.split-table tbody tr'); + if (rows.length === 1) { + console.log('Will not remove last split'); + return false; + } + var row = $(e.target); + var index = row.data('split'); + console.log('Trying to remove row with split ' + index); + $('table.split-table tbody tr[data-split="' + index + '"]').remove(); + + + + resetSplits(); + + return false; + +} + function cloneRow() { "use strict"; - var source = $('.initial-row').clone(); + var source = $('.table.split-table tbody tr').last().clone(); var count = $('.split-table tbody tr').length + 1; var index = count - 1; source.removeClass('initial-row'); source.find('.count').text('#' + count); - // get each input, change the name? - $.each(source.find('input, select'), function (i, v) { - var obj = $(v); - var name = obj.attr('name').replace('[0]', '[' + index + ']'); - obj.attr('name', name); - }); + // // get each input, change the name? + // $.each(source.find('input, select'), function (i, v) { + // var obj = $(v); + // var name = obj.attr('name').replace('[0]', '[' + index + ']'); + // obj.attr('name', name); + // }); source.find('input[name$="][amount]"]').val("").on('input', calculateSum); if (destAccounts.length > 0) { @@ -74,11 +93,84 @@ function cloneRow() { $('.split-table tbody').append(source); + // remove original click things, add them again: + $('.remove-current-split').unbind('click').click(removeRow); + + calculateSum(); + resetSplits(); return false; } +function resetSplits() { + "use strict"; + // loop rows, reset numbers: + + // update the row split number: + $.each($('table.split-table tbody tr'), function (i, v) { + var row = $(v); + row.attr('data-split', i); + console.log('Row is now ' + row.data('split')); + }); + + // loop each remove button, update the index + $.each($('.remove-current-split'), function (i, v) { + var button = $(v); + button.attr('data-split', i); + button.find('i').attr('data-split', i); + console.log('Remove button index is now ' + button.data('split')); + + }); + + // loop each indicator (#) and update it: + $.each($('td.count'), function (i, v) { + var cell = $(v); + var index = i + 1; + cell.text('#' + index); + console.log('Cell is now ' + cell.text()); + }); + + // loop each possible field. + + // ends with ][description] + $.each($('input[name$="][description]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][description]'); + console.log('description is now ' + input.attr('name')); + }); + // ends with ][destination_account_name] + $.each($('input[name$="][destination_account_name]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][destination_account_name]'); + console.log('destination_account_name is now ' + input.attr('name')); + }); + // ends with ][source_account_name] + $.each($('input[name$="][source_account_name]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][source_account_name]'); + console.log('source_account_name is now ' + input.attr('name')); + }); + // ends with ][amount] + $.each($('input[name$="][amount]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][amount]'); + console.log('amount is now ' + input.attr('name')); + }); + // ends with ][budget_id] + $.each($('input[name$="][budget_id]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][budget_id]'); + console.log('budget_id is now ' + input.attr('name')); + }); + // ends with ][category] + $.each($('input[name$="][category]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transaction[' + i + '][category]'); + console.log('category is now ' + input.attr('name')); + }); +} + function calculateSum() { "use strict"; var sum = 0; diff --git a/resources/views/transactions/edit-split.twig b/resources/views/transactions/edit-split.twig index 91488e0936..5f71a30c78 100644 --- a/resources/views/transactions/edit-split.twig +++ b/resources/views/transactions/edit-split.twig @@ -41,7 +41,7 @@ {{ ExpandedForm.text('journal_description', journal.description) }} {# CURRENCY IS NEW FOR SPLIT JOURNALS #} - {{ ExpandedForm.select('journal_currency_id', currencies, preFilled.transaction_currency_id) }} + {{ ExpandedForm.select('currency_id', currencies, preFilled.currency_id) }} {# show source if withdrawal or transfer #} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} @@ -218,8 +218,9 @@
{% for index, transaction in preFilled.transactions %} - - + + {% endfor %} From 3d9b855849b9cb860e9a2f9a5b9071379720d9fa Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 07:52:17 +0200 Subject: [PATCH 37/45] Force larval 5.3.18 --- composer.json | 2 +- composer.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index ee3c0c430e..b443593700 100755 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require": { "php": ">=7.0.0", "ext-intl": "*", - "laravel/framework": "5.3.*", + "laravel/framework": "5.3.18", "davejamesmiller/laravel-breadcrumbs": "^3.0", "watson/validating": "^3.0", "doctrine/dbal": "^2.5", diff --git a/composer.lock b/composer.lock index e54440d03f..0b571ce03d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c370a68602f3922d07ad8b6a5722718a", - "content-hash": "ab1379ea8b6e5f438eba8f5771b49a59", + "hash": "cc3d23620e727ee1f4741b2e83f8685f", + "content-hash": "473d3c681e5c41989e9dced651a939df", "packages": [ { "name": "bacon/bacon-qr-code", @@ -1332,20 +1332,20 @@ }, { "name": "league/flysystem", - "version": "1.0.28", + "version": "1.0.32", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a9663643ff2d16d7f66ed1e0d3212c5491bc9044" + "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a9663643ff2d16d7f66ed1e0d3212c5491bc9044", - "reference": "a9663643ff2d16d7f66ed1e0d3212c5491bc9044", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b5c4a0031697f46e779a9d1b309c2e1b24daeab", + "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=5.5.9" }, "conflict": { "league/flysystem-sftp": "<1.0.6" @@ -1411,7 +1411,7 @@ "sftp", "storage" ], - "time": "2016-10-07 12:20:37" + "time": "2016-10-19 20:38:46" }, { "name": "maximebf/debugbar", @@ -1696,16 +1696,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf" + "reference": "c0125896dbb151380ab47e96c621741e79623beb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf", - "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c0125896dbb151380ab47e96c621741e79623beb", + "reference": "c0125896dbb151380ab47e96c621741e79623beb", "shasum": "" }, "require": { @@ -1740,7 +1740,7 @@ "pseudorandom", "random" ], - "time": "2016-04-03 06:00:07" + "time": "2016-10-17 15:23:22" }, { "name": "pragmarx/google2fa", From 7ce3b8d4efbe7c86e7253bf1b9e923921a5f5c9b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 09:31:27 +0200 Subject: [PATCH 38/45] Updated events, fixes #345 --- .../{UserIsDeleted.php => ConfirmedUser.php} | 6 +- ...UserIsConfirmed.php => RegisteredUser.php} | 6 +- app/Events/ResendConfirmation.php | 42 ---- ...egistration.php => ResentConfirmation.php} | 6 +- ...tLimitStored.php => StoredBudgetLimit.php} | 6 +- ...tored.php => StoredTransactionJournal.php} | 6 +- app/Events/TransactionStored.php | 41 ---- ...imitUpdated.php => UpdatedBudgetLimit.php} | 6 +- ...ated.php => UpdatedTransactionJournal.php} | 6 +- app/Handlers/Events/AttachUserRole.php | 52 ---- ...ventHandler.php => BudgetEventHandler.php} | 45 ++-- .../Events/ConnectJournalToPiggyBank.php | 72 ------ .../Events/ConnectTransactionToPiggyBank.php | 70 ------ app/Handlers/Events/FireRulesForStore.php | 68 ------ app/Handlers/Events/FireRulesForUpdate.php | 65 ----- .../Events/ScanForBillsAfterStore.php | 42 ---- .../Events/ScanForBillsAfterUpdate.php | 41 ---- app/Handlers/Events/SendRegistrationMail.php | 69 ------ .../Events/StoredJournalEventHandler.php | 124 ++++++++++ .../Events/UpdateJournalConnection.php | 74 ------ .../Events/UpdatedJournalEventHandler.php | 128 ++++++++++ app/Handlers/Events/UserConfirmation.php | 109 --------- app/Handlers/Events/UserEventHandler.php | 225 ++++++++++++++++++ app/Handlers/Events/UserEventListener.php | 38 --- app/Handlers/Events/UserSaveIpAddress.php | 63 ----- .../Auth/ConfirmationController.php | 8 +- .../Controllers/Auth/RegisterController.php | 4 +- app/Http/Controllers/ProfileController.php | 5 +- .../Transaction/SingleController.php | 8 +- .../Transaction/SplitController.php | 4 +- app/Providers/EventServiceProvider.php | 79 +++--- app/Repositories/Budget/BudgetRepository.php | 8 +- .../Journal/JournalRepository.php | 1 - 33 files changed, 580 insertions(+), 947 deletions(-) rename app/Events/{UserIsDeleted.php => ConfirmedUser.php} (90%) rename app/Events/{UserIsConfirmed.php => RegisteredUser.php} (89%) delete mode 100644 app/Events/ResendConfirmation.php rename app/Events/{UserRegistration.php => ResentConfirmation.php} (88%) rename app/Events/{BudgetLimitStored.php => StoredBudgetLimit.php} (91%) rename app/Events/{TransactionJournalStored.php => StoredTransactionJournal.php} (88%) delete mode 100644 app/Events/TransactionStored.php rename app/Events/{BudgetLimitUpdated.php => UpdatedBudgetLimit.php} (91%) rename app/Events/{TransactionJournalUpdated.php => UpdatedTransactionJournal.php} (86%) delete mode 100644 app/Handlers/Events/AttachUserRole.php rename app/Handlers/Events/{BudgetLimitEventHandler.php => BudgetEventHandler.php} (79%) delete mode 100644 app/Handlers/Events/ConnectJournalToPiggyBank.php delete mode 100644 app/Handlers/Events/ConnectTransactionToPiggyBank.php delete mode 100644 app/Handlers/Events/FireRulesForStore.php delete mode 100644 app/Handlers/Events/FireRulesForUpdate.php delete mode 100644 app/Handlers/Events/ScanForBillsAfterStore.php delete mode 100644 app/Handlers/Events/ScanForBillsAfterUpdate.php delete mode 100644 app/Handlers/Events/SendRegistrationMail.php create mode 100644 app/Handlers/Events/StoredJournalEventHandler.php delete mode 100644 app/Handlers/Events/UpdateJournalConnection.php create mode 100644 app/Handlers/Events/UpdatedJournalEventHandler.php delete mode 100644 app/Handlers/Events/UserConfirmation.php create mode 100644 app/Handlers/Events/UserEventHandler.php delete mode 100644 app/Handlers/Events/UserEventListener.php delete mode 100644 app/Handlers/Events/UserSaveIpAddress.php diff --git a/app/Events/UserIsDeleted.php b/app/Events/ConfirmedUser.php similarity index 90% rename from app/Events/UserIsDeleted.php rename to app/Events/ConfirmedUser.php index 4312cc0178..c932675ec5 100644 --- a/app/Events/UserIsDeleted.php +++ b/app/Events/ConfirmedUser.php @@ -1,6 +1,6 @@ user = $user; - $this->ipAddress = $ipAddress; - } -} diff --git a/app/Events/UserRegistration.php b/app/Events/ResentConfirmation.php similarity index 88% rename from app/Events/UserRegistration.php rename to app/Events/ResentConfirmation.php index 0af1bc833e..74a6f92c1b 100644 --- a/app/Events/UserRegistration.php +++ b/app/Events/ResentConfirmation.php @@ -1,6 +1,6 @@ transaction = $transaction; - } - -} diff --git a/app/Events/BudgetLimitUpdated.php b/app/Events/UpdatedBudgetLimit.php similarity index 91% rename from app/Events/BudgetLimitUpdated.php rename to app/Events/UpdatedBudgetLimit.php index 596c59c8b2..3642531e75 100644 --- a/app/Events/BudgetLimitUpdated.php +++ b/app/Events/UpdatedBudgetLimit.php @@ -1,6 +1,6 @@ count() == 1) { - $repository->attachRole($event->user, 'owner'); - } - } - -} diff --git a/app/Handlers/Events/BudgetLimitEventHandler.php b/app/Handlers/Events/BudgetEventHandler.php similarity index 79% rename from app/Handlers/Events/BudgetLimitEventHandler.php rename to app/Handlers/Events/BudgetEventHandler.php index a05a752cd2..cd02a373ef 100644 --- a/app/Handlers/Events/BudgetLimitEventHandler.php +++ b/app/Handlers/Events/BudgetEventHandler.php @@ -1,6 +1,6 @@ budgetLimit; $end = $event->end; @@ -71,12 +65,18 @@ class BudgetLimitEventHandler } + return true; } + /** - * @param BudgetLimitUpdated $event + * Updates, if present the budget limit repetition part of a budget limit. + * + * @param UpdatedBudgetLimit $event + * + * @return bool */ - public function update(BudgetLimitUpdated $event) + public function update(UpdatedBudgetLimit $event): bool { $budgetLimit = $event->budgetLimit; $end = $event->end; @@ -104,6 +104,7 @@ class BudgetLimitEventHandler $repetition->save(); } - } -} + return true; + } +} \ No newline at end of file diff --git a/app/Handlers/Events/ConnectJournalToPiggyBank.php b/app/Handlers/Events/ConnectJournalToPiggyBank.php deleted file mode 100644 index 1d7d75d28c..0000000000 --- a/app/Handlers/Events/ConnectJournalToPiggyBank.php +++ /dev/null @@ -1,72 +0,0 @@ -journal; - $piggyBankId = $event->piggyBankId; - - /** @var PiggyBank $piggyBank */ - $piggyBank = auth()->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); - - if (is_null($piggyBank)) { - return true; - } - // update piggy bank rep for date of transaction journal. - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - if (is_null($repetition)) { - return true; - } - - $amount = TransactionJournal::amountPositive($journal); - // if piggy account matches source account, the amount is positive - $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); - if (in_array($piggyBank->account_id, $sources)) { - $amount = bcmul($amount, '-1'); - } - - - $repetition->currentamount = bcadd($repetition->currentamount, $amount); - $repetition->save(); - - PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]); - - return true; - - } - - -} diff --git a/app/Handlers/Events/ConnectTransactionToPiggyBank.php b/app/Handlers/Events/ConnectTransactionToPiggyBank.php deleted file mode 100644 index 8e56987a0d..0000000000 --- a/app/Handlers/Events/ConnectTransactionToPiggyBank.php +++ /dev/null @@ -1,70 +0,0 @@ -transaction; - - $piggyBank = $repository->find($transaction['piggy_bank_id']); - - // valid piggy: - if (is_null($piggyBank->id)) { - return true; - } - $amount = strval($transaction['amount']); - // piggy bank account something with amount: - if ($transaction['source_account_id'] == $piggyBank->account_id) { - // if the source of this transaction is the same as the piggy bank, - // the money is being removed from the piggy bank. So the - // amount must be negative: - $amount = bcmul($amount, '-1'); - } - - $repetition = $piggyBank->currentRelevantRep(); - // add or remove the money from the piggy bank: - $newAmount = bcadd(strval($repetition->currentamount), $amount); - $repetition->currentamount = $newAmount; - $repetition->save(); - - // now generate a piggy bank event: - PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'date' => $transaction['date'], 'amount' => $newAmount]); - - return true; - } - - -} diff --git a/app/Handlers/Events/FireRulesForStore.php b/app/Handlers/Events/FireRulesForStore.php deleted file mode 100644 index 65cc16d67a..0000000000 --- a/app/Handlers/Events/FireRulesForStore.php +++ /dev/null @@ -1,68 +0,0 @@ -user(); - $groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); - // - /** @var RuleGroup $group */ - foreach ($groups as $group) { - $rules = $group->rules() - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'store-journal') - ->where('rules.active', 1) - ->get(['rules.*']); - /** @var Rule $rule */ - foreach ($rules as $rule) { - - $processor = Processor::make($rule); - $processor->handleTransactionJournal($event->journal); - - if ($rule->stop_processing) { - return true; - } - - } - } - - return true; - } -} diff --git a/app/Handlers/Events/FireRulesForUpdate.php b/app/Handlers/Events/FireRulesForUpdate.php deleted file mode 100644 index 889d1e3156..0000000000 --- a/app/Handlers/Events/FireRulesForUpdate.php +++ /dev/null @@ -1,65 +0,0 @@ -user(); - $groups = $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); - // - /** @var RuleGroup $group */ - foreach ($groups as $group) { - $rules = $group->rules() - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'update-journal') - ->where('rules.active', 1) - ->get(['rules.*']); - /** @var Rule $rule */ - foreach ($rules as $rule) { - $processor = Processor::make($rule); - $processor->handleTransactionJournal($event->journal); - - if ($rule->stop_processing) { - break; - } - - } - } - - return true; - } -} diff --git a/app/Handlers/Events/ScanForBillsAfterStore.php b/app/Handlers/Events/ScanForBillsAfterStore.php deleted file mode 100644 index 499370eab9..0000000000 --- a/app/Handlers/Events/ScanForBillsAfterStore.php +++ /dev/null @@ -1,42 +0,0 @@ -journal; - BillScanner::scan($journal); - - return true; - } - -} diff --git a/app/Handlers/Events/ScanForBillsAfterUpdate.php b/app/Handlers/Events/ScanForBillsAfterUpdate.php deleted file mode 100644 index 11ddfee6aa..0000000000 --- a/app/Handlers/Events/ScanForBillsAfterUpdate.php +++ /dev/null @@ -1,41 +0,0 @@ -journal; - BillScanner::scan($journal); - - return true; - } - -} diff --git a/app/Handlers/Events/SendRegistrationMail.php b/app/Handlers/Events/SendRegistrationMail.php deleted file mode 100644 index aabb3714af..0000000000 --- a/app/Handlers/Events/SendRegistrationMail.php +++ /dev/null @@ -1,69 +0,0 @@ -user->email; - $address = route('index'); - $ipAddress = $event->ipAddress; - // send email. - try { - Mail::send( - ['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) { - $message->to($email, $email)->subject('Welcome to Firefly III! '); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } - - return true; - } -} diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php new file mode 100644 index 0000000000..1410147625 --- /dev/null +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -0,0 +1,124 @@ +journal; + $piggyBankId = $event->piggyBankId; + + /** @var PiggyBank $piggyBank */ + $piggyBank = $journal->user()->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); + + if (is_null($piggyBank)) { + return true; + } + // update piggy bank rep for date of transaction journal. + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); + if (is_null($repetition)) { + return true; + } + + $amount = TransactionJournal::amountPositive($journal); + // if piggy account matches source account, the amount is positive + $sources = TransactionJournal::sourceAccountList($journal)->pluck('id')->toArray(); + if (in_array($piggyBank->account_id, $sources)) { + $amount = bcmul($amount, '-1'); + } + + + $repetition->currentamount = bcadd($repetition->currentamount, $amount); + $repetition->save(); + + PiggyBankEvent::create(['piggy_bank_id' => $piggyBank->id, 'transaction_journal_id' => $journal->id, 'date' => $journal->date, 'amount' => $amount]); + + return true; + } + + /** + * This method grabs all the users rules and processes them. + * + * @param StoredTransactionJournal $event + * + * @return bool + */ + public function processRules(StoredTransactionJournal $event): bool + { + // get all the user's rule groups, with the rules, order by 'order'. + $journal = $event->journal; + $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); + // + /** @var RuleGroup $group */ + foreach ($groups as $group) { + $rules = $group->rules() + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', 1) + ->get(['rules.*']); + /** @var Rule $rule */ + foreach ($rules as $rule) { + + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + + if ($rule->stop_processing) { + return true; + } + + } + } + + return true; + } + + /** + * This method calls a special bill scanner that will check if the stored journal is part of a bill. + * + * @param StoredTransactionJournal $event + * + * @return bool + */ + public function scanBills(StoredTransactionJournal $event): bool + { + $journal = $event->journal; + BillScanner::scan($journal); + + return true; + } +} \ No newline at end of file diff --git a/app/Handlers/Events/UpdateJournalConnection.php b/app/Handlers/Events/UpdateJournalConnection.php deleted file mode 100644 index a30f63923a..0000000000 --- a/app/Handlers/Events/UpdateJournalConnection.php +++ /dev/null @@ -1,74 +0,0 @@ -journal; - - if (!$journal->isTransfer()) { - return true; - } - - // get the event connected to this journal: - /** @var PiggyBankEvent $event */ - $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first(); - if (is_null($event)) { - return false; - } - $piggyBank = $event->piggyBank()->first(); - $repetition = null; - if (!is_null($piggyBank)) { - /** @var PiggyBankRepetition $repetition */ - $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); - } - - if (is_null($repetition)) { - return false; - } - - $amount = TransactionJournal::amount($journal); - $diff = bcsub($amount, $event->amount); // update current repetition - - $repetition->currentamount = bcadd($repetition->currentamount, $diff); - $repetition->save(); - - - $event->amount = $amount; - $event->save(); - - return true; - } - -} diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php new file mode 100644 index 0000000000..9626120ae1 --- /dev/null +++ b/app/Handlers/Events/UpdatedJournalEventHandler.php @@ -0,0 +1,128 @@ +journal; + + if (!$journal->isTransfer()) { + return true; + } + + // get the event connected to this journal: + /** @var PiggyBankEvent $event */ + $event = PiggyBankEvent::where('transaction_journal_id', $journal->id)->first(); + if (is_null($event)) { + return false; + } + $piggyBank = $event->piggyBank()->first(); + $repetition = null; + if (!is_null($piggyBank)) { + /** @var PiggyBankRepetition $repetition */ + $repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first(); + } + + if (is_null($repetition)) { + return false; + } + + $amount = TransactionJournal::amount($journal); + $diff = bcsub($amount, $event->amount); // update current repetition + + $repetition->currentamount = bcadd($repetition->currentamount, $diff); + $repetition->save(); + + + $event->amount = $amount; + $event->save(); + + return true; + } + + /** + * This method will check all the rules when a journal is updated. + * + * @param UpdatedTransactionJournal $event + * + * @return bool + */ + public function processRules(UpdatedTransactionJournal $event):bool + { + // get all the user's rule groups, with the rules, order by 'order'. + $journal = $event->journal; + $groups = $journal->user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(); + // + /** @var RuleGroup $group */ + foreach ($groups as $group) { + $rules = $group->rules() + ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') + ->where('rule_triggers.trigger_type', 'user_action') + ->where('rule_triggers.trigger_value', 'update-journal') + ->where('rules.active', 1) + ->get(['rules.*']); + /** @var Rule $rule */ + foreach ($rules as $rule) { + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + + if ($rule->stop_processing) { + break; + } + + } + } + + return true; + } + + /** + * This method calls a special bill scanner that will check if the updated journal is part of a bill. + * + * @param UpdatedTransactionJournal $event + * + * @return bool + */ + public function scanBills(UpdatedTransactionJournal $event): bool + { + $journal = $event->journal; + BillScanner::scan($journal); + + return true; + } +} \ No newline at end of file diff --git a/app/Handlers/Events/UserConfirmation.php b/app/Handlers/Events/UserConfirmation.php deleted file mode 100644 index 439db35e75..0000000000 --- a/app/Handlers/Events/UserConfirmation.php +++ /dev/null @@ -1,109 +0,0 @@ -user; - $ipAddress = $event->ipAddress; - $this->doConfirm($user, $ipAddress); - - return true; - } - - /** - * Handle the event. - * - * @param UserRegistration $event - * - * @return bool - */ - public function sendConfirmation(UserRegistration $event): bool - { - $user = $event->user; - $ipAddress = $event->ipAddress; - $this->doConfirm($user, $ipAddress); - - return true; - } - - /** - * @param User $user - * @param string $ipAddress - */ - private function doConfirm(User $user, string $ipAddress) - { - $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false); - if ($confirmAccount === false) { - Preferences::setForUser($user, 'user_confirmed', true); - Preferences::setForUser($user, 'user_confirmed_last_mail', 0); - Preferences::mark(); - - return; - } - $email = $user->email; - $code = str_random(16); - $route = route('do_confirm_account', [$code]); - Preferences::setForUser($user, 'user_confirmed', false); - Preferences::setForUser($user, 'user_confirmed_last_mail', time()); - Preferences::setForUser($user, 'user_confirmed_code', $code); - try { - Mail::send( - ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress], - function (Message $message) use ($email) { - $message->to($email, $email)->subject('Please confirm your Firefly III account'); - } - ); - } catch (Swift_TransportException $e) { - Log::error($e->getMessage()); - } catch (Exception $e) { - Log::error($e->getMessage()); - } - - return; - } - -} diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php new file mode 100644 index 0000000000..2d8523e3c9 --- /dev/null +++ b/app/Handlers/Events/UserEventHandler.php @@ -0,0 +1,225 @@ +count() === 1) { + $repository->attachRole($event->user, 'owner'); + } + + return true; + } + + /** + * Handle user logout events. + * + * @return bool + */ + public function onUserLogout(): bool + { + // dump stuff from the session: + Session::forget('twofactor-authenticated'); + Session::forget('twofactor-authenticated-date'); + + return true; + } + + /** + * This method will send a newly registered user a confirmation message, urging him or her to activate their account. + * + * @param RegisteredUser $event + * + * @return bool + */ + public function sendConfirmationMessage(RegisteredUser $event): bool + { + $user = $event->user; + $ipAddress = $event->ipAddress; + $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false); + if ($confirmAccount === false) { + Preferences::setForUser($user, 'user_confirmed', true); + Preferences::setForUser($user, 'user_confirmed_last_mail', 0); + Preferences::mark(); + + return true; + } + $email = $user->email; + $code = str_random(16); + $route = route('do_confirm_account', [$code]); + Preferences::setForUser($user, 'user_confirmed', false); + Preferences::setForUser($user, 'user_confirmed_last_mail', time()); + Preferences::setForUser($user, 'user_confirmed_code', $code); + try { + Mail::send( + ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress], + function (Message $message) use ($email) { + $message->to($email, $email)->subject('Please confirm your Firefly III account'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } catch (Exception $e) { + Log::error($e->getMessage()); + } + + return true; + } + + /** + * If the user has somehow lost his or her confirmation message, this event will send it to the user again. + * + * At the moment, this method is exactly the same as the ::sendConfirmationMessage method, but that will change. + * + * @param ResentConfirmation $event + * + * @return bool + */ + function sendConfirmationMessageAgain(ResentConfirmation $event): bool + { + $user = $event->user; + $ipAddress = $event->ipAddress; + $confirmAccount = env('MUST_CONFIRM_ACCOUNT', false); + if ($confirmAccount === false) { + Preferences::setForUser($user, 'user_confirmed', true); + Preferences::setForUser($user, 'user_confirmed_last_mail', 0); + Preferences::mark(); + + return true; + } + $email = $user->email; + $code = str_random(16); + $route = route('do_confirm_account', [$code]); + Preferences::setForUser($user, 'user_confirmed', false); + Preferences::setForUser($user, 'user_confirmed_last_mail', time()); + Preferences::setForUser($user, 'user_confirmed_code', $code); + try { + Mail::send( + ['emails.confirm-account-html', 'emails.confirm-account'], ['route' => $route, 'ip' => $ipAddress], + function (Message $message) use ($email) { + $message->to($email, $email)->subject('Please confirm your Firefly III account'); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } catch (Exception $e) { + Log::error($e->getMessage()); + } + + return true; + + } + + /** + * This method will send the user a registration mail, welcoming him or her to Firefly III. + * This message is only sent when the configuration of Firefly III says so. + * + * @param RegisteredUser $event + * + * @return bool + */ + public function sendRegistrationMail(RegisteredUser $event) + { + + $sendMail = env('SEND_REGISTRATION_MAIL', true); + if (!$sendMail) { + return true; + } + // get the email address + $email = $event->user->email; + $address = route('index'); + $ipAddress = $event->ipAddress; + // send email. + try { + Mail::send( + ['emails.registered-html', 'emails.registered'], ['address' => $address, 'ip' => $ipAddress], function (Message $message) use ($email) { + $message->to($email, $email)->subject('Welcome to Firefly III! '); + } + ); + } catch (Swift_TransportException $e) { + Log::error($e->getMessage()); + } + + return true; + } + + /** + * When the user is confirmed, this method stores the IP address of the user + * as a preference. Since this preference cannot be edited, it is effectively hidden + * from the user yet stored conveniently. + * + * @param ConfirmedUser $event + * + * @return bool + */ + public function storeConfirmationIpAddress(ConfirmedUser $event): bool + { + Preferences::setForUser($event->user, 'confirmation_ip_address', $event->ipAddress); + + return true; + } + + /** + * This message stores the users IP address on registration, in much the same + * fashion as the previous method. + * + * @param RegisteredUser $event + * + * @return bool + */ + public function storeRegistrationIpAddress(RegisteredUser $event): bool + { + Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress); + + return true; + + } + +} \ No newline at end of file diff --git a/app/Handlers/Events/UserEventListener.php b/app/Handlers/Events/UserEventListener.php deleted file mode 100644 index f81303d084..0000000000 --- a/app/Handlers/Events/UserEventListener.php +++ /dev/null @@ -1,38 +0,0 @@ -user, 'confirmation_ip_address', $event->ipAddress); - - return true; - } - - /** - * Handle the event. - * - * @param UserRegistration $event - * - * @return bool - */ - public function saveFromRegistration(UserRegistration $event): bool - { - Preferences::setForUser($event->user, 'registration_ip_address', $event->ipAddress); - - return true; - } -} diff --git a/app/Http/Controllers/Auth/ConfirmationController.php b/app/Http/Controllers/Auth/ConfirmationController.php index 4e34c7dcb8..d99662973f 100644 --- a/app/Http/Controllers/Auth/ConfirmationController.php +++ b/app/Http/Controllers/Auth/ConfirmationController.php @@ -13,8 +13,8 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Auth; -use FireflyIII\Events\ResendConfirmation; -use FireflyIII\Events\UserIsConfirmed; +use FireflyIII\Events\ResentConfirmation; +use FireflyIII\Events\ConfirmedUser; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use Illuminate\Http\Request; @@ -56,7 +56,7 @@ class ConfirmationController extends Controller if ($database === $code && ($now - $time <= $maxDiff)) { // trigger user registration event: - event(new UserIsConfirmed(auth()->user(), $request->ip())); + event(new ConfirmedUser(auth()->user(), $request->ip())); Preferences::setForUser(auth()->user(), 'user_confirmed', true); Preferences::setForUser(auth()->user(), 'user_confirmed_confirmed', time()); @@ -80,7 +80,7 @@ class ConfirmationController extends Controller $owner = env('SITE_OWNER', 'mail@example.com'); $view = 'auth.confirmation.no-resent'; if ($now - $time > $maxDiff) { - event(new ResendConfirmation(auth()->user(), $request->ip())); + event(new ResentConfirmation(auth()->user(), $request->ip())); $view = 'auth.confirmation.resent'; } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index bb87bd81e3..e7d6dc6692 100755 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -14,7 +14,7 @@ namespace FireflyIII\Http\Controllers\Auth; use Auth; use Config; -use FireflyIII\Events\UserRegistration; +use FireflyIII\Events\RegisteredUser; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Support\Facades\FireflyConfig; use FireflyIII\User; @@ -102,7 +102,7 @@ class RegisterController extends Controller $user = $this->create($request->all()); // trigger user registration event: - event(new UserRegistration($user, $request->ip())); + event(new RegisteredUser($user, $request->ip())); Auth::login($user); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index b03420b592..5d368c09f5 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -13,7 +13,7 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers; -use FireflyIII\Events\UserIsDeleted; +use FireflyIII\Events\DeletedUser; use FireflyIII\Http\Requests\DeleteAccountFormRequest; use FireflyIII\Http\Requests\ProfileFormRequest; use FireflyIII\User; @@ -116,9 +116,6 @@ class ProfileController extends Controller return redirect(route('profile.delete-account')); } - // respond to deletion: - event(new UserIsDeleted(auth()->user(), $request->ip())); - // store some stuff for the future: $registration = Preferences::get('registration_ip_address')->data; $confirmation = Preferences::get('confirmation_ip_address')->data; diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 23861d9ff4..e7efcd63e4 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -15,8 +15,8 @@ namespace FireflyIII\Http\Controllers\Transaction; use ExpandedForm; -use FireflyIII\Events\TransactionJournalStored; -use FireflyIII\Events\TransactionJournalUpdated; +use FireflyIII\Events\StoredTransactionJournal; +use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\JournalFormRequest; @@ -260,7 +260,7 @@ class SingleController extends Controller Session::flash('info', $this->attachments->getMessages()->get('attachments')); } - event(new TransactionJournalStored($journal, $data['piggy_bank_id'])); + event(new StoredTransactionJournal($journal, $data['piggy_bank_id'])); Session::flash('success', strval(trans('firefly.stored_journal', ['description' => e($journal->description)]))); Preferences::mark(); @@ -304,7 +304,7 @@ class SingleController extends Controller Session::flash('info', $this->attachments->getMessages()->get('attachments')); } - event(new TransactionJournalUpdated($journal)); + event(new UpdatedTransactionJournal($journal)); // update, get events by date and sort DESC $type = strtolower(TransactionJournal::transactionTypeStr($journal)); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 6fe08cb008..b849f810ef 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Transaction; use ExpandedForm; -use FireflyIII\Events\TransactionJournalUpdated; +use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\AccountType; @@ -132,7 +132,7 @@ class SplitController extends Controller // save attachments: $this->attachments->saveAttachmentsForModel($journal); - event(new TransactionJournalUpdated($journal)); + event(new UpdatedTransactionJournal($journal)); // update, get events by date and sort DESC // flash messages diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index d8373a2be6..6cfb886b4c 100755 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -35,42 +35,50 @@ class EventServiceProvider extends ServiceProvider */ protected $listen = [ - 'FireflyIII\Events\TransactionJournalUpdated' => [ - 'FireflyIII\Handlers\Events\ScanForBillsAfterUpdate', - 'FireflyIII\Handlers\Events\UpdateJournalConnection', - 'FireflyIII\Handlers\Events\FireRulesForUpdate', + // new event handlers: + 'FireflyIII\Events\ConfirmedUser' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@storeConfirmationIpAddress', + ], + 'FireflyIII\Events\RegisteredUser' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail', + 'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole', + 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessage', + 'FireflyIII\Handlers\Events\UserEventHandler@storeRegistrationIpAddress', + ], + 'FireflyIII\Events\ResentConfirmation' => // is a User related event. + [ + 'FireflyIII\Handlers\Events\UserEventHandler@sendConfirmationMessageAgain', + ], + 'FireflyIII\Events\StoredBudgetLimit' => // is a Budget related event. + [ + 'FireflyIII\Handlers\Events\BudgetEventHandler@storeRepetition', + ], - ], + 'FireflyIII\Events\UpdatedBudgetLimit' => // is a Budget related event. + [ + 'FireflyIII\Handlers\Events\BudgetEventHandler@updateRepetition', + ], - 'FireflyIII\Events\BudgetLimitStored' => [ - 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@store', - ], - 'FireflyIII\Events\BudgetLimitUpdated' => [ - 'FireflyIII\Handlers\Events\BudgetLimitEventHandler@update', - ], - 'FireflyIII\Events\TransactionStored' => [ - 'FireflyIII\Handlers\Events\ConnectTransactionToPiggyBank', - ], - 'FireflyIII\Events\TransactionJournalStored' => [ - 'FireflyIII\Handlers\Events\ScanForBillsAfterStore', - 'FireflyIII\Handlers\Events\ConnectJournalToPiggyBank', - 'FireflyIII\Handlers\Events\FireRulesForStore', - ], - 'Illuminate\Auth\Events\Logout' => [ - 'FireflyIII\Handlers\Events\UserEventListener@onUserLogout', - ], - 'FireflyIII\Events\UserRegistration' => [ - 'FireflyIII\Handlers\Events\SendRegistrationMail', - 'FireflyIII\Handlers\Events\AttachUserRole', - 'FireflyIII\Handlers\Events\UserConfirmation@sendConfirmation', - 'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromRegistration', - ], - 'FireflyIII\Events\UserIsConfirmed' => [ - 'FireflyIII\Handlers\Events\UserSaveIpAddress@saveFromConfirmation', - ], - 'FireflyIII\Events\ResendConfirmation' => [ - 'FireflyIII\Handlers\Events\UserConfirmation@resendConfirmation', - ], + 'FireflyIII\Events\StoredTransactionJournal' => // is a Transaction Journal related event. + [ + 'FireflyIII\Handlers\Events\StoredJournalEventHandler@scanBills', + 'FireflyIII\Handlers\Events\StoredJournalEventHandler@connectToPiggyBank', + 'FireflyIII\Handlers\Events\StoredJournalEventHandler@processRules', + ], + 'FireflyIII\Events\UpdatedTransactionJournal' => // is a Transaction Journal related event. + [ + 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@scanBills', + 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@connectToPiggyBank', + 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules', + ], + + // LARAVEL EVENTS: + 'Illuminate\Auth\Events\Logout' => + [ + 'FireflyIII\Handlers\Events\UserEventHandler@logoutUser', + ], ]; /** @@ -83,9 +91,6 @@ class EventServiceProvider extends ServiceProvider parent::boot(); $this->registerDeleteEvents(); $this->registerCreateEvents(); - - - // } /** diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 7d7e24dc52..b2a3440295 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -14,8 +14,8 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; -use FireflyIII\Events\BudgetLimitStored; -use FireflyIII\Events\BudgetLimitUpdated; +use FireflyIII\Events\StoredBudgetLimit; +use FireflyIII\Events\UpdatedBudgetLimit; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; @@ -555,7 +555,7 @@ class BudgetRepository implements BudgetRepositoryInterface $limit->save(); // fire event to create or update LimitRepetition. - event(new BudgetLimitUpdated($limit, $end)); + event(new UpdatedBudgetLimit($limit, $end)); return $limit; } @@ -568,7 +568,7 @@ class BudgetRepository implements BudgetRepositoryInterface $limit->repeat_freq = $repeatFreq; $limit->repeats = 0; $limit->save(); - event(new BudgetLimitStored($limit, $end)); + event(new StoredBudgetLimit($limit, $end)); // likewise, there should be a limit repetition to match the end date diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 855e253211..a43f55186a 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -14,7 +14,6 @@ declare(strict_types = 1); namespace FireflyIII\Repositories\Journal; use DB; -use FireflyIII\Events\TransactionStored; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; From 12a6a6110023f4aed811251f6174be2046ba202b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 09:33:03 +0200 Subject: [PATCH 39/45] Various code cleanup [skip ci] --- app/Console/Commands/CreateImport.php | 1 - app/Console/Commands/Import.php | 1 + app/Export/Processor.php | 3 +- app/Http/Controllers/HomeController.php | 1 + app/Models/Transaction.php | 2 +- .../Journal/JournalRepository.php | 2 +- .../User/UserRepositoryInterface.php | 32 +++++++++---------- app/Support/Twig/General.php | 1 - 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index 093ab03c3f..0bf65903fa 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -14,7 +14,6 @@ declare(strict_types = 1); namespace FireflyIII\Console\Commands; use Artisan; -use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Console\Command; diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index 8d7c23b0ac..0459e1c75b 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -62,6 +62,7 @@ class Import extends Command $job = ImportJob::whereKey($jobKey)->first(); if (!$this->isValid($job)) { Log::error('Job is not valid for some reason. Exit.'); + return; } diff --git a/app/Export/Processor.php b/app/Export/Processor.php index bfb7076006..7c33ae04b2 100644 --- a/app/Export/Processor.php +++ b/app/Export/Processor.php @@ -19,7 +19,6 @@ use FireflyIII\Export\Collector\UploadCollector; use FireflyIII\Export\Entry\Entry; use FireflyIII\Models\ExportJob; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Collection; @@ -97,7 +96,7 @@ class Processor public function collectJournals(): bool { /** @var JournalTaskerInterface $tasker */ - $tasker = app(JournalTaskerInterface::class); + $tasker = app(JournalTaskerInterface::class); $this->journals = $tasker->getJournalsInRange($this->accounts, $this->settings['startDate'], $this->settings['endDate']); return true; diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index fa7d003bb6..8afd02006f 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -205,6 +205,7 @@ class HomeController extends Controller Session::flash('info', 'This is an info message.'); Session::flash('warning', 'This is a warning.'); Session::flash('error', 'This is an error!'); + return redirect(route('home')); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 2c007aca1a..f35efcb180 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -55,7 +55,7 @@ class Transaction extends Model { protected $dates = ['created_at', 'updated_at', 'deleted_at']; - protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount','identifier']; + protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier']; protected $hidden = ['encrypted']; protected $rules = [ diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index a43f55186a..9c047824d8 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -267,7 +267,7 @@ class JournalRepository implements JournalRepositoryInterface $journal->description = $data['journal_description']; $journal->date = $data['date']; $journal->save(); - + // unlink all categories: $journal->categories()->detach(); $journal->budgets()->detach(); diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php index a5fce26bad..1855fc1da1 100644 --- a/app/Repositories/User/UserRepositoryInterface.php +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -31,22 +31,6 @@ interface UserRepositoryInterface */ public function all(): Collection; - /** - * @param int $userId - * - * @return User - */ - public function find(int $userId): User; - - /** - * Return basic user information. - * - * @param User $user - * - * @return array - */ - public function getUserData(User $user): array; - /** * Gives a user a role. * @@ -63,4 +47,20 @@ interface UserRepositoryInterface * @return int */ public function count(): int; + + /** + * @param int $userId + * + * @return User + */ + public function find(int $userId): User; + + /** + * Return basic user information. + * + * @param User $user + * + * @return array + */ + public function getUserData(User $user): array; } diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index b2dfda8831..a13a62faab 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -16,7 +16,6 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use Config; use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use Route; use Twig_Extension; From 5d9b68c3e713083a48ee276bd6dec46837e52c1d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 09:39:31 +0200 Subject: [PATCH 40/45] Various code cleanup [skip ci] --- .../AccountChartGeneratorInterface.php | 18 +++--- app/Http/Controllers/AccountController.php | 3 +- app/Http/Controllers/Admin/UserController.php | 8 +-- .../Auth/ConfirmationController.php | 2 +- .../Auth/ResetPasswordController.php | 2 + .../Transaction/SingleController.php | 7 ++- .../Transaction/SplitController.php | 7 +-- app/Http/Requests/JournalFormRequest.php | 59 ------------------- app/Repositories/Journal/JournalTasker.php | 3 +- 9 files changed, 26 insertions(+), 83 deletions(-) diff --git a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php index 9b90dc6668..0cb57197da 100644 --- a/app/Generator/Chart/Account/AccountChartGeneratorInterface.php +++ b/app/Generator/Chart/Account/AccountChartGeneratorInterface.php @@ -24,15 +24,6 @@ use Illuminate\Support\Collection; */ interface AccountChartGeneratorInterface { - /** - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array; - /** * @param Collection $accounts * @param Carbon $start @@ -51,6 +42,15 @@ interface AccountChartGeneratorInterface */ public function frontpage(Collection $accounts, Carbon $start, Carbon $end): array; + /** + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function revenueAccounts(Collection $accounts, Carbon $start, Carbon $end): array; + /** * @param Account $account * @param array $labels diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 23e71d6e4b..bad619e64a 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -114,12 +114,11 @@ class AccountController extends Controller } /** - * @param ARI $repository * @param Account $account * * @return View */ - public function edit(ARI $repository, Account $account) + public function edit(Account $account) { $what = config('firefly.shortNamesByFullName')[$account->accountType->type]; diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 65f7148c32..dc57328bbd 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -86,10 +86,10 @@ class UserController extends Controller */ public function show(UserRepositoryInterface $repository, User $user) { - $title = strval(trans('firefly.administration')); - $mainTitleIcon = 'fa-hand-spock-o'; - $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email])); - $subTitleIcon = 'fa-user'; + $title = strval(trans('firefly.administration')); + $mainTitleIcon = 'fa-hand-spock-o'; + $subTitle = strval(trans('firefly.single_user_administration', ['email' => $user->email])); + $subTitleIcon = 'fa-user'; // get IP info: $defaultIp = '0.0.0.0'; diff --git a/app/Http/Controllers/Auth/ConfirmationController.php b/app/Http/Controllers/Auth/ConfirmationController.php index d99662973f..3221971227 100644 --- a/app/Http/Controllers/Auth/ConfirmationController.php +++ b/app/Http/Controllers/Auth/ConfirmationController.php @@ -13,8 +13,8 @@ declare(strict_types = 1); namespace FireflyIII\Http\Controllers\Auth; -use FireflyIII\Events\ResentConfirmation; use FireflyIII\Events\ConfirmedUser; +use FireflyIII\Events\ResentConfirmation; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use Illuminate\Http\Request; diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index e9a3e7b3fc..3e8c162572 100755 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -41,6 +41,8 @@ class ResetPasswordController extends Controller */ public function __construct() { + parent::__construct(); + $this->middleware('guest'); } } diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index e7efcd63e4..c46d50167b 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -284,10 +284,11 @@ class SingleController extends Controller } /** - * @param JournalFormRequest $request - * @param TransactionJournal $journal + * @param JournalFormRequest $request + * @param JournalRepositoryInterface $repository + * @param TransactionJournal $journal * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index b849f810ef..e71fe2e60b 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -126,7 +126,7 @@ class SplitController extends Controller */ public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { - $data = $this->arrayFromInput($request, $journal); + $data = $this->arrayFromInput($request); $journal = $repository->updateSplitJournal($journal, $data); // save attachments: @@ -158,18 +158,17 @@ class SplitController extends Controller /** * @param Request $request - * @param TransactionJournal $journal * * @return array */ - private function arrayFromInput(Request $request, TransactionJournal $journal): array + private function arrayFromInput(Request $request): array { $array = [ 'journal_description' => $request->get('journal_description'), 'journal_source_account_id' => $request->get('journal_source_account_id'), 'journal_source_account_name' => $request->get('journal_source_account_name'), 'journal_destination_account_id' => $request->get('journal_destination_account_id'), - 'currency_id' => $request->get('currency_id'), + 'currency_id' => $request->get('currency_id'), 'what' => $request->get('what'), 'date' => $request->get('date'), // all custom fields: diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 0076f5e341..289c0b77fa 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -164,63 +164,4 @@ class JournalFormRequest extends Request { return $this->get($field) ?? ''; } - // - // /** - // * @param int $index - // * @param string $field - // * - // * @return int - // */ - // private function getIntFromArray(int $index, string $field): int - // { - // $array = $this->get($field); - // if (isset($array[$index])) { - // return intval($array[$index]); - // } - // - // return 0; - // } - // - // /** - // * @param int $index - // * @param string $field - // * - // * @return string - // */ - // private function getStringFromArray(int $index, string $field): string - // { - // $array = $this->get($field); - // if (isset($array[$index])) { - // return trim($array[$index]); - // } - // - // return ''; - // } - // - // /** - // * @return array - // */ - // private function getTransactionData(): array - // { - // $transactions = []; - // $array = $this->get('amount'); - // if (is_array($array) && count($array) > 0) { - // foreach ($array as $index => $amount) { - // $transaction = [ - // 'description' => $this->getStringFromArray($index, 'description'), - // 'amount' => round($amount, 2), - // 'budget_id' => $this->getIntFromArray($index, 'budget_id'), - // 'category' => $this->getStringFromArray($index, 'category'), - // 'source_account_id' => $this->getIntFromArray($index, 'source_account_id'), - // 'source_account_name' => $this->getStringFromArray($index, 'source_account_name'), - // 'destination_account_id' => $this->getIntFromArray($index, 'destination_account_id'), - // 'destination_account_name' => $this->getStringFromArray($index, 'destination_account_name'), - // 'piggy_bank_id' => $this->getIntFromArray($index, 'piggy_bank_id'), - // ]; - // $transactions[] = $transaction; - // } - // } - // - // return $transactions; - // } } diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index cb8fa0a21f..7d9add0dc8 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -159,7 +159,8 @@ class JournalTasker implements JournalTaskerInterface * * from transactions as source * - * left join transactions as destination ON source.transaction_journal_id = destination.transaction_journal_id AND source.amount = destination.amount * -1 AND source.identifier = destination.identifier + * left join transactions as destination ON source.transaction_journal_id = + * destination.transaction_journal_id AND source.amount = destination.amount * -1 AND source.identifier = destination.identifier * -- left join source account name: * left join accounts as source_accounts ON source.account_id = source_accounts.id * left join accounts as destination_accounts ON destination.account_id = destination_accounts.id From 091f6e918b24f7e3a24257d216a4ddd6b3821cca Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 09:44:47 +0200 Subject: [PATCH 41/45] Fix some reported issues. --- app/Http/Controllers/Admin/UserController.php | 18 +++++++++--------- app/Http/Controllers/ReportController.php | 2 -- .../Transaction/SplitController.php | 15 +++++++-------- app/Http/breadcrumbs.php | 2 +- app/Repositories/Bill/BillRepository.php | 6 +++--- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index dc57328bbd..5557539f35 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -92,16 +92,16 @@ class UserController extends Controller $subTitleIcon = 'fa-user'; // get IP info: - $defaultIp = '0.0.0.0'; - $registrationPreference = Preferences::getForUser($user, 'registration_ip_address'); - $registration = $defaultIp; - $confirmationPreference = Preferences::getForUser($user, 'confirmation_ip_address'); - $confirmation = $defaultIp; - if (!is_null($registrationPreference)) { - $registration = $registrationPreference->data; + $defaultIp = '0.0.0.0'; + $regPref = Preferences::getForUser($user, 'registration_ip_address'); + $registration = $defaultIp; + $conPref = Preferences::getForUser($user, 'confirmation_ip_address'); + $confirmation = $defaultIp; + if (!is_null($regPref)) { + $registration = $regPref->data; } - if (!is_null($confirmationPreference)) { - $confirmation = $confirmationPreference->data; + if (!is_null($conPref)) { + $confirmation = $conPref->data; } $registrationHost = ''; diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 9834dab7c0..ee09d7e9cd 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -153,8 +153,6 @@ class ReportController extends Controller */ private function auditReport(Carbon $start, Carbon $end, Collection $accounts) { - /** @var ARI $repos */ - $repos = app(ARI::class); /** @var AccountTaskerInterface $tasker */ $tasker = app(AccountTaskerInterface::class); $auditData = []; diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index e71fe2e60b..a50e31ccbf 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -43,18 +43,18 @@ class SplitController extends Controller /** @var AccountRepositoryInterface */ private $accounts; + /** @var AttachmentHelperInterface */ private $attachments; + /** @var BudgetRepositoryInterface */ private $budgets; + /** @var CurrencyRepositoryInterface */ private $currencies; /** @var JournalTaskerInterface */ private $tasker; - // - // /** @var PiggyBankRepositoryInterface */ - // private $piggyBanks; /** * @@ -68,10 +68,9 @@ class SplitController extends Controller // some useful repositories: $this->middleware( function ($request, $next) { - $this->accounts = app(AccountRepositoryInterface::class); - $this->budgets = app(BudgetRepositoryInterface::class); - $this->tasker = app(JournalTaskerInterface::class); - // $this->piggyBanks = app(PiggyBankRepositoryInterface::class); + $this->accounts = app(AccountRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + $this->tasker = app(JournalTaskerInterface::class); $this->attachments = app(AttachmentHelperInterface::class); $this->currencies = app(CurrencyRepositoryInterface::class); @@ -157,7 +156,7 @@ class SplitController extends Controller } /** - * @param Request $request + * @param Request $request * * @return array */ diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 17c71e60cd..44c4fc707e 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -119,7 +119,7 @@ Breadcrumbs::register( Breadcrumbs::register( 'admin.users.show', function (BreadCrumbGenerator $breadcrumbs, User $user) { $breadcrumbs->parent('admin.users'); - $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', $user->id)); + $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id])); } ); diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index f303fd1365..0412c3080c 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -372,7 +372,7 @@ class BillRepository implements BillRepositoryInterface $nextExpectedMatch = $this->nextDateMatch($bill, $currentStart); Log::debug(sprintf('Next Date match after %s is %s', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); /* - * If $nextExpectedMatch is after $end, we continue: + * If nextExpectedMatch is after end, we continue: */ if ($nextExpectedMatch > $end) { Log::debug( @@ -482,7 +482,7 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextDateMatch'); $cache->addProperty($date); if ($cache->has()) { - //return $cache->get(); + return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; @@ -519,7 +519,7 @@ class BillRepository implements BillRepositoryInterface $cache->addProperty('nextExpectedMatch'); $cache->addProperty($date); if ($cache->has()) { - //return $cache->get(); + return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; From e4d249e73cd93104636a75f12dfbc4840a687066 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 10:13:49 +0200 Subject: [PATCH 42/45] Piggy bank supports notes (#350) --- app/Http/Controllers/PiggyBankController.php | 6 ++- app/Models/Note.php | 54 +++++++++++++++++++ app/Models/PiggyBank.php | 9 ++++ .../PiggyBank/PiggyBankRepository.php | 32 +++++++++++ .../2016_10_22_075804_changes_for_v410.php | 41 ++++++++++++++ resources/views/piggy-banks/create.twig | 1 + resources/views/piggy-banks/edit.twig | 1 + resources/views/piggy-banks/show.twig | 16 +++++- 8 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 app/Models/Note.php create mode 100644 database/migrations/2016_10_22_075804_changes_for_v410.php diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 59e79a74c7..ae9f639afb 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -168,6 +168,7 @@ class PiggyBankController extends Controller 'account_id' => $piggyBank->account_id, 'targetamount' => $piggyBank->targetamount, 'targetdate' => $targetDate, + 'note' => $piggyBank->notes()->first()->text, ]; Session::flash('preFilled', $preFilled); Session::flash('gaEventCategory', 'piggy-banks'); @@ -346,10 +347,11 @@ class PiggyBankController extends Controller */ public function show(PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { + $note = $piggyBank->notes()->first(); $events = $repository->getEvents($piggyBank); $subTitle = e($piggyBank->name); - return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle')); + return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note')); } @@ -369,6 +371,7 @@ class PiggyBankController extends Controller 'targetamount' => round($request->get('targetamount'), 2), 'order' => $repository->getMaxOrder() + 1, 'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null, + 'note' => $request->get('note'), ]; $piggyBank = $repository->store($piggyBankData); @@ -402,6 +405,7 @@ class PiggyBankController extends Controller 'account_id' => intval($request->get('account_id')), 'targetamount' => round($request->get('targetamount'), 2), 'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null, + 'note' => $request->get('note'), ]; $piggyBank = $repository->update($piggyBank, $piggyBankData); diff --git a/app/Models/Note.php b/app/Models/Note.php new file mode 100644 index 0000000000..db1d2b4603 --- /dev/null +++ b/app/Models/Note.php @@ -0,0 +1,54 @@ +morphTo(); + } + +} \ No newline at end of file diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index f2d2ae4e50..3d5e5da150 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -56,6 +56,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @mixin \Eloquent * @property boolean $active * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank whereActive($value) + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes */ class PiggyBank extends Model { @@ -146,6 +147,14 @@ class PiggyBank extends Model } + /** + * Get all of the piggy bank's notes. + */ + public function notes() + { + return $this->morphMany('FireflyIII\Models\Note', 'noteable'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 9eca01a95a..2560c440de 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -15,6 +15,7 @@ namespace FireflyIII\Repositories\PiggyBank; use Amount; use Carbon\Carbon; +use FireflyIII\Models\Note; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankEvent; use FireflyIII\User; @@ -176,6 +177,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface { $piggyBank = PiggyBank::create($data); + $this->updateNote($piggyBank, $data['note']); + return $piggyBank; } @@ -196,6 +199,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface $piggyBank->save(); + $this->updateNote($piggyBank, $data['note']); + // if the piggy bank is now smaller than the current relevant rep, // remove money from the rep. $repetition = $piggyBank->currentRelevantRep(); @@ -210,4 +215,31 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $piggyBank; } + + /** + * @param PiggyBank $piggyBank + * @param string $note + * + * @return bool + */ + private function updateNote(PiggyBank $piggyBank, string $note): bool + { + if (strlen($note) === 0) { + $dbNote = $piggyBank->notes()->first(); + if (!is_null($dbNote)) { + $dbNote->delete(); + } + + return true; + } + $dbNote= $piggyBank->notes()->first(); + if (is_null($dbNote)) { + $dbNote= new Note(); + $dbNote->noteable()->associate($piggyBank); + } + $dbNote->text = trim($note); + $dbNote->save(); + + return true; + } } diff --git a/database/migrations/2016_10_22_075804_changes_for_v410.php b/database/migrations/2016_10_22_075804_changes_for_v410.php new file mode 100644 index 0000000000..bfd8c71085 --- /dev/null +++ b/database/migrations/2016_10_22_075804_changes_for_v410.php @@ -0,0 +1,41 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('noteable_id', false, true); + $table->string('noteable_type'); + $table->string('title')->nullable(); + $table->text('text')->nullable(); + } + ); + } +} diff --git a/resources/views/piggy-banks/create.twig b/resources/views/piggy-banks/create.twig index 492ff18ae8..f6d055b46e 100644 --- a/resources/views/piggy-banks/create.twig +++ b/resources/views/piggy-banks/create.twig @@ -31,6 +31,7 @@
{{ ExpandedForm.date('targetdate') }} + {{ ExpandedForm.textarea('note') }}
diff --git a/resources/views/piggy-banks/edit.twig b/resources/views/piggy-banks/edit.twig index 82bd5aeda7..c12e629504 100644 --- a/resources/views/piggy-banks/edit.twig +++ b/resources/views/piggy-banks/edit.twig @@ -33,6 +33,7 @@
{{ ExpandedForm.date('targetdate') }} + {{ ExpandedForm.textarea('note') }}
diff --git a/resources/views/piggy-banks/show.twig b/resources/views/piggy-banks/show.twig index a46f0920c2..0b625d7680 100644 --- a/resources/views/piggy-banks/show.twig +++ b/resources/views/piggy-banks/show.twig @@ -35,7 +35,7 @@
  {{ trans('list.split_number') }} {{ trans('list.description') }}{{ trans('list.destination') }}{{ trans('list.source') }}{{ trans('list.amount') }}{{ trans('list.budget') }}{{ trans('list.category') }}
#{{ loop.index }} - + - - - - {% for key, budget in budgets %} @@ -179,7 +264,8 @@ - +
#{{ loop.index }} + class="form-control"/>
- + @@ -73,6 +73,20 @@
{{ 'account'|_ }}{{ 'account'|_ }} {{ piggyBank.account.name }}
+ + {% if note %} +
+
+

{{ trans('form.notes') }}

+
+
+

+ {{ note.text|nl2br }} +

+
+
+ {% endif %} +

{{ 'table'|_ }}

From 5500e5b0aaa2971f82cadc96c8d35bb1ab085429 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 10:13:56 +0200 Subject: [PATCH 43/45] Remove debug classes --- config/app.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/app.php b/config/app.php index 51daf78b6d..0611c68b7f 100755 --- a/config/app.php +++ b/config/app.php @@ -188,8 +188,8 @@ return [ // own stuff: - Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, - Barryvdh\Debugbar\ServiceProvider::class, +// Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, +// Barryvdh\Debugbar\ServiceProvider::class, DaveJamesMiller\Breadcrumbs\ServiceProvider::class, TwigBridge\ServiceProvider::class, 'PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider', From 7b6c63e6a870f1a6770ce4406661ca18cd743ad7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 10:15:50 +0200 Subject: [PATCH 44/45] New version number [skip ci] Signed-off-by: James Cole --- config/firefly.php | 2 +- config/upgrade.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/firefly.php b/config/firefly.php index 6ffc2ed269..4d3f6a54cb 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -22,7 +22,7 @@ return [ 'single_user_mode' => true, ], 'chart' => 'chartjs', - 'version' => '4.0.2', + 'version' => '4.1', 'csv_import_enabled' => true, 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], diff --git a/config/upgrade.php b/config/upgrade.php index 8aec28d716..82a97f50a0 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -28,5 +28,6 @@ return [ '3.8' => 'This version of Firefly III requires PHP 7.0.', '3.10' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-3.10', '4.0' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0', + '4.1' => 'Please find the full upgrade instructions here: https://github.com/JC5/firefly-iii/wiki/Upgrade-to-4.0', ], ]; From 3bfcb1f3ab46d47f0bbc94589ef6e5b2bb295a94 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 22 Oct 2016 10:20:25 +0200 Subject: [PATCH 45/45] New change log. [skip ci] Signed-off-by: James Cole --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c245627b66..90ab0ae211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,29 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.1] - 2016-10-22 +### Added +- Option to show deposit accounts on the front page. +- Script to upgrade split transactions +- Can now save notes on piggy banks. +- Extend user admin options. +- Run import jobs from the command line +### Changed +- New preferences screen layout. + +### Deprecated +- ``firefly:import`` is now ``firefly:start-import`` + +### Removed +- Lots of old code + +### Fixed +- #357, where non utf-8 files would break Firefly. +- Tab delimiter is not properly loaded from import configuration (@roberthorlings) +- System response to yearly bills + ## [4.0.2] - 2016-10-14 ### Added - Added ``intl`` dependency to composer file to ease installation (thanks @telyn)