From dd5e9155c625077557e48c361c201229525a4a01 Mon Sep 17 00:00:00 2001
From: George Sumpster <gsumpster@gmail.com>
Date: Sat, 28 Jan 2023 12:23:25 -0800
Subject: [PATCH] Add 'Duplicate Transaction' to bulk editor for #548 (#582)

---
 .../src/components/accounts/Account.js        | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/packages/desktop-client/src/components/accounts/Account.js b/packages/desktop-client/src/components/accounts/Account.js
index 4e463f71a..32c42f2ea 100644
--- a/packages/desktop-client/src/components/accounts/Account.js
+++ b/packages/desktop-client/src/components/accounts/Account.js
@@ -18,6 +18,7 @@ import {
   deleteTransaction,
   updateTransaction,
   realizeTempTransactions,
+  ungroupTransaction,
   ungroupTransactions
 } from 'loot-core/src/shared/transactions';
 import {
@@ -464,6 +465,7 @@ function SelectedTransactionsButton({
   style,
   getTransaction,
   onShow,
+  onDuplicate,
   onDelete,
   onEdit,
   onUnlink,
@@ -480,6 +482,12 @@ function SelectedTransactionsButton({
     };
   }, [selectedItems]);
 
+  let ambiguousDuplication = useMemo(() => {
+    let transactions = [...selectedItems].map(id => getTransaction(id));
+
+    return transactions.some(t => t.is_child);
+  }, [selectedItems]);
+
   let linked = useMemo(() => {
     return (
       !types.preview &&
@@ -513,6 +521,11 @@ function SelectedTransactionsButton({
             ]
           : [
               { name: 'show', text: 'Show', key: 'F' },
+              {
+                name: 'duplicate',
+                text: 'Duplicate',
+                disabled: ambiguousDuplication
+              },
               { name: 'delete', text: 'Delete', key: 'D' },
               ...(linked
                 ? [
@@ -545,6 +558,9 @@ function SelectedTransactionsButton({
           case 'show':
             onShow([...selectedItems]);
             break;
+          case 'duplicate':
+            onDuplicate([...selectedItems]);
+            break;
           case 'delete':
             onDelete([...selectedItems]);
             break;
@@ -620,6 +636,7 @@ const AccountHeader = React.memo(
     onMenuSelect,
     onReconcile,
     onBatchDelete,
+    onBatchDuplicate,
     onBatchEdit,
     onBatchUnlink,
     onApplyFilter,
@@ -824,6 +841,7 @@ const AccountHeader = React.memo(
               <SelectedTransactionsButton
                 getTransaction={id => transactions.find(t => t.id === id)}
                 onShow={onShowTransactions}
+                onDuplicate={onBatchDuplicate}
                 onDelete={onBatchDelete}
                 onEdit={onBatchEdit}
                 onUnlink={onBatchUnlink}
@@ -1511,6 +1529,31 @@ class AccountInternal extends React.PureComponent {
     }
   };
 
+  onBatchDuplicate = async ids => {
+    this.setState({ workingHard: true });
+
+    let { data } = await runQuery(
+      q('transactions')
+        .filter({ id: { $oneof: ids } })
+        .select('*')
+        .options({ splits: 'grouped' })
+    );
+
+    let changes = {
+      added: data
+        .reduce((newTransactions, trans) => {
+          return newTransactions.concat(
+            realizeTempTransactions(ungroupTransaction(trans))
+          );
+        }, [])
+        .map(({ sort_order, ...trans }) => ({ ...trans }))
+    };
+
+    await send('transactions-batch-update', changes);
+
+    await this.refetchTransactions();
+  };
+
   onBatchDelete = async ids => {
     this.setState({ workingHard: true });
 
@@ -1707,6 +1750,7 @@ class AccountInternal extends React.PureComponent {
                   onSync={this.onSync}
                   onImport={this.onImport}
                   onBatchDelete={this.onBatchDelete}
+                  onBatchDuplicate={this.onBatchDuplicate}
                   onBatchEdit={this.onBatchEdit}
                   onBatchUnlink={this.onBatchUnlink}
                   onDeleteFilter={this.onDeleteFilter}
-- 
GitLab