diff --git a/app/Http/Controllers/Transaction/CreateController.php b/app/Http/Controllers/Transaction/CreateController.php index 6153c72b88..490a1ba802 100644 --- a/app/Http/Controllers/Transaction/CreateController.php +++ b/app/Http/Controllers/Transaction/CreateController.php @@ -25,7 +25,9 @@ namespace FireflyIII\Http\Controllers\Transaction; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Services\Internal\Update\GroupCloneService; /** * Class CreateController @@ -34,6 +36,7 @@ class CreateController extends Controller { /** * CreateController constructor. + * * @codeCoverageIgnore */ public function __construct() @@ -55,6 +58,21 @@ class CreateController extends Controller ); } + /** + * @param TransactionGroup $group + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function cloneGroup(TransactionGroup $group) + { + + /** @var GroupCloneService $service */ + $service = app(GroupCloneService::class); + $newGroup = $service->cloneGroup($group); + + return redirect(route('transactions.show', [$newGroup->id])); + } + /** * Create a new transaction group. * diff --git a/app/Services/Internal/Update/GroupCloneService.php b/app/Services/Internal/Update/GroupCloneService.php new file mode 100644 index 0000000000..79a07f8c41 --- /dev/null +++ b/app/Services/Internal/Update/GroupCloneService.php @@ -0,0 +1,146 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Update; + +use Carbon\Carbon; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Note; +use FireflyIII\Models\Tag; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalMeta; + +/** + * Class GroupCloneService + * TODO test. + */ +class GroupCloneService +{ + /** + * @param TransactionGroup $group + * + * @return TransactionGroup + */ + public function cloneGroup(TransactionGroup $group): TransactionGroup + { + $newGroup = $group->replicate(); + $newGroup->save(); + foreach ($group->transactionJournals as $journal) { + $this->cloneJournal($journal, $newGroup, (int)$group->id); + } + + + return $newGroup; + } + + /** + * @param TransactionJournal $journal + * @param TransactionGroup $newGroup + * @param int $originalGroup + */ + private function cloneJournal(TransactionJournal $journal, TransactionGroup $newGroup, int $originalGroup): void + { + $newJournal = $journal->replicate(); + $newJournal->transaction_group_id = $newGroup->id; + $newJournal->date = Carbon::now(); + $newJournal->save(); + + foreach ($journal->transactions as $transaction) { + $this->cloneTransaction($transaction, $newJournal); + } + + // clone notes + /** @var Note $note */ + foreach ($journal->notes as $note) { + $this->cloneNote($note, $newJournal, $originalGroup); + } + // clone location (not yet available) + + // clone meta + /** @var TransactionJournalMeta $meta */ + foreach ($journal->transactionJournalMeta as $meta) { + $this->cloneMeta($meta, $newJournal); + } + // clone category + /** @var Category $category */ + foreach ($journal->categories as $category) { + $newJournal->categories()->save($category); + } + + // clone budget + /** @var Budget $budget */ + foreach ($journal->budgets as $budget) { + $newJournal->budgets()->save($budget); + } + // clone links (ongoing). + + // clone tags + /** @var Tag $tag */ + foreach ($journal->tags as $tag) { + $newJournal->tags()->save($tag); + } + // add note saying "cloned". + + // add relation. + } + + /** + * @param TransactionJournalMeta $meta + * @param TransactionJournal $newJournal + */ + private function cloneMeta(TransactionJournalMeta $meta, TransactionJournal $newJournal): void + { + $newMeta = $meta->replicate(); + $newMeta->transaction_journal_id = $newJournal->id; + if ('recurrence_id' !== $newMeta->name) { + $newMeta->save(); + } + } + + private function cloneNote(Note $note, TransactionJournal $newJournal, int $oldGroupId): void + { + $newNote = $note->replicate(); + $newNote->text .= sprintf( + "\n\n%s", trans('firefly.clones_journal_x', ['description' => $newJournal->description, 'id' => $oldGroupId]) + ); + $newNote->noteable_id = $newJournal->id; + $newNote->save(); + + } + + /** + * @param Transaction $transaction + * @param TransactionJournal $newJournal + */ + private function cloneTransaction(Transaction $transaction, TransactionJournal $newJournal): void + { + $newTransaction = $transaction->replicate(); + $newTransaction->transaction_journal_id = $newJournal->id; + $newTransaction->save(); + } + + +} diff --git a/public/v1/js/ff/transactions/show.js b/public/v1/js/ff/transactions/show.js index 6efb5ab0c0..b377f56ce5 100644 --- a/public/v1/js/ff/transactions/show.js +++ b/public/v1/js/ff/transactions/show.js @@ -27,8 +27,14 @@ $(function () { makeAutoComplete(); }) $('[data-toggle="tooltip"]').tooltip(); + + $('#clone_button').click(cloneNewFunction); }); +function cloneNewFunction() { + return confirm(newCloneInstructions); +} + function getLinkModal(e) { var button = $(e.currentTarget); var journalId = parseInt(button.data('journal')); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 1eb9238937..e77a1ec894 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -58,7 +58,8 @@ return [ 'no_rules_for_bill' => 'This bill has no rules associated to it.', 'go_to_asset_accounts' => 'View your asset accounts', 'go_to_budgets' => 'Go to your budgets', - 'clone_instructions' => 'To clone a transaction, search for the "store as new" checkbox in the edit screen', + 'new_clone_instructions' => 'This button will automatically clone the transaction and set the date to today. Are you sure?', + 'clones_journal_x' => 'This transaction is a clone of ":description" (#:id)', 'go_to_categories' => 'Go to your categories', 'go_to_bills' => 'Go to your bills', 'go_to_expense_accounts' => 'See your expense accounts', diff --git a/resources/views/v1/transactions/show.twig b/resources/views/v1/transactions/show.twig index b9ebd56c09..d2302d89bd 100644 --- a/resources/views/v1/transactions/show.twig +++ b/resources/views/v1/transactions/show.twig @@ -61,9 +61,8 @@ {% if groupArray.transactions[0].type != 'opening balance' and groupArray.transactions[0].type != 'reconciliation' %} - - Clone - {# {{ 'clone'|_ }}#} + + {{ 'clone'|_ }} {% endif %} {# {{ 'delete'|_ }} @@ -415,6 +414,7 @@ var modalDialogURI = '{{ route('transactions.link.modal', ['%JOURNAL%']) }}'; var acURI = '{{ route('json.autocomplete.all-journals-with-id') }}'; var groupURI = '{{ route('transactions.show',['%GROUP%']) }}'; + var newCloneInstructions = '{{ 'new_clone_instructions'|_|escape('js') }}'; diff --git a/routes/web.php b/routes/web.php index babf1c9752..d105dc77ab 100644 --- a/routes/web.php +++ b/routes/web.php @@ -963,6 +963,9 @@ Route::group( Route::get('create/{objectType}', ['uses' => 'Transaction\CreateController@create', 'as' => 'create']); Route::post('store', ['uses' => 'Transaction\CreateController@store', 'as' => 'store']); + // clone group + Route::get('clone/{transactionGroup}', ['uses' => 'Transaction\CreateController@cloneGroup', 'as' => 'clone']); + // edit group Route::get('edit/{transactionGroup}', ['uses' => 'Transaction\EditController@edit', 'as' => 'edit']); Route::post('update', ['uses' => 'Transaction\EditController@update', 'as' => 'update']); @@ -971,17 +974,6 @@ Route::group( Route::get('delete/{transactionGroup}', ['uses' => 'Transaction\DeleteController@delete', 'as' => 'delete']); Route::post('destroy/{transactionGroup}', ['uses' => 'Transaction\DeleteController@destroy', 'as' => 'destroy']); - // clone group: - //Route::get('clone/{transactionGroup}', ['uses' => 'Transaction\CloneController@clone', 'as' => 'clone']); - - //Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); - //Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); - - //Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']); - //Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']); - // TODO end of improvement. - - Route::get('show/{transactionGroup}', ['uses' => 'Transaction\ShowController@show', 'as' => 'show']); } );