From 28c68940214cf80d0a638ffb39794879b05fce21 Mon Sep 17 00:00:00 2001
From: Aidan Harbison <aharbis@users.noreply.github.com>
Date: Fri, 24 Mar 2023 13:57:50 -0400
Subject: [PATCH] transaction-import: treat (amount) as -amount (#808)

- When parsing an amount string, consider surrounding parentheses to
mean the amount is negative.
- Ensures all input to `parseFloat()` is sanitized.

Closes: #807
---
 packages/loot-core/src/shared/util.js      | 16 ++++++++++++----
 packages/loot-core/src/shared/util.test.js |  5 +++++
 upcoming-release-notes/808.md              |  6 ++++++
 3 files changed, 23 insertions(+), 4 deletions(-)
 create mode 100644 upcoming-release-notes/808.md

diff --git a/packages/loot-core/src/shared/util.js b/packages/loot-core/src/shared/util.js
index 1101c4212..484ccb321 100644
--- a/packages/loot-core/src/shared/util.js
+++ b/packages/loot-core/src/shared/util.js
@@ -377,15 +377,23 @@ export function looselyParseAmount(amount) {
     return isNaN(v) ? null : v;
   }
 
+  function extractNumbers(v) {
+    return v.replace(/[^0-9-]/g, '');
+  }
+
+  if (amount.startsWith('(') && amount.endsWith(')')) {
+    amount = amount.replace('(', '-').replace(')', '');
+  }
+
   let m = amount.match(/[.,][^.,]*$/);
   if (!m || m.index === 0) {
-    return safeNumber(parseFloat(amount));
+    return safeNumber(parseFloat(extractNumbers(amount)));
   }
 
-  let left = amount.slice(0, m.index);
-  let right = amount.slice(m.index + 1);
+  let left = extractNumbers(amount.slice(0, m.index));
+  let right = extractNumbers(amount.slice(m.index + 1));
 
-  return safeNumber(parseFloat(left.replace(/[^0-9-]/g, '') + '.' + right));
+  return safeNumber(parseFloat(left + '.' + right));
 }
 
 export function semverToNumber(str) {
diff --git a/packages/loot-core/src/shared/util.test.js b/packages/loot-core/src/shared/util.test.js
index ebd52e0c0..afe3b3d7c 100644
--- a/packages/loot-core/src/shared/util.test.js
+++ b/packages/loot-core/src/shared/util.test.js
@@ -22,6 +22,11 @@ describe('utility functions', () => {
     expect(looselyParseAmount('-3,45')).toBe(-3.45);
   });
 
+  test('looseParseAmount works with parentheses (negative)', () => {
+    expect(looselyParseAmount('(3.45)')).toBe(-3.45);
+    expect(looselyParseAmount('(3)')).toBe(-3);
+  });
+
   test('looseParseAmount ignores non-numeric characters', () => {
     // This is strange behavior because it does not work for just
     // `3_45_23` (it needs a decimal amount). This function should be
diff --git a/upcoming-release-notes/808.md b/upcoming-release-notes/808.md
new file mode 100644
index 000000000..9009e101d
--- /dev/null
+++ b/upcoming-release-notes/808.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [aharbis]
+---
+
+Import transactions with negative amounts represented as `(amount)`
-- 
GitLab