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']);
}
);