diff --git a/packages/desktop-client/e2e/data/ynab5-demo-budget.json b/packages/desktop-client/e2e/data/ynab5-demo-budget.json
index 2f31164d64c..6dbe3ff5bfe 100644
--- a/packages/desktop-client/e2e/data/ynab5-demo-budget.json
+++ b/packages/desktop-client/e2e/data/ynab5-demo-budget.json
@@ -1671,9 +1671,70 @@
"import_payee_name_original": null,
"debt_transaction_type": null,
"deleted": false
+ },
+ {
+ "id": "213526fc-ba49-4790-8a96-cc2a50182728",
+ "date": "2023-09-04",
+ "amount": -100000,
+ "memo": "Test transaction",
+ "cleared": "cleared",
+ "approved": true,
+ "flag_color": null,
+ "account_id": "bc1d862f-bab0-41c3-bd1e-6cee8c688e32",
+ "payee_id": "2a20470a-634f-4efa-a7f6-f1c0b0bdda41",
+ "category_id": "36120d44-6c61-4402-985a-891a8d267858",
+ "transfer_account_id": null,
+ "transfer_transaction_id": null,
+ "matched_transaction_id": null,
+ "import_id": null,
+ "import_payee_name": null,
+ "import_payee_name_original": null,
+ "debt_transaction_type": null,
+ "deleted": false
+ },
+ {
+ "id": "024494a1-f1e0-4667-9fc0-91e4a4262193",
+ "date": "2023-09-04",
+ "amount": 50000,
+ "memo": "split part b",
+ "cleared": "cleared",
+ "approved": true,
+ "flag_color": null,
+ "account_id": "125f339b-2a63-481e-84c0-f04d898905d2",
+ "payee_id": "",
+ "category_id": null,
+ "transfer_account_id": "bc1d862f-bab0-41c3-bd1e-6cee8c688e32",
+ "transfer_transaction_id": "213526fc-ba49-4790-8a96-cc2a50182728",
+ "matched_transaction_id": "",
+ "import_id": null,
+ "import_payee_name": null,
+ "import_payee_name_original": null,
+ "debt_transaction_type": null,
+ "deleted": false
+ }
+ ],
+ "subtransactions": [
+ {
+ "id": "d8ec8c84-5033-4f7e-8485-66bfe19a70d6",
+ "transaction_id": "213526fc-ba49-4790-8a96-cc2a50182728",
+ "amount": -50000,
+ "memo": "split part a",
+ "payee_id": "2a20470a-634f-4efa-a7f6-f1c0b0bdda41",
+ "category_id": "36120d44-6c61-4402-985a-891a8d267858",
+ "transfer_account_id": null,
+ "deleted": false
+ },
+ {
+ "id": "870d8780-79cf-4197-a341-47d24b2b5a59",
+ "transaction_id": "213526fc-ba49-4790-8a96-cc2a50182728",
+ "amount": -50000,
+ "memo": "split part b",
+ "payee_id": "2a20470a-634f-4efa-a7f6-f1c0b0bdda41",
+ "category_id": null,
+ "transfer_account_id": "125f339b-2a63-481e-84c0-f04d898905d2",
+ "deleted": false
}
],
- "subtransactions": [],
"scheduled_transactions": [],
"scheduled_subtransactions": []
},
diff --git a/packages/desktop-client/e2e/onboarding.test.js b/packages/desktop-client/e2e/onboarding.test.js
index c748ad93523..98c5b28c083 100644
--- a/packages/desktop-client/e2e/onboarding.test.js
+++ b/packages/desktop-client/e2e/onboarding.test.js
@@ -60,10 +60,10 @@ test.describe('Onboarding', () => {
await expect(budgetPage.budgetTable).toBeVisible({ timeout: 30000 });
const accountPage = await navigation.goToAccountPage('Checking');
- await expect(accountPage.accountBalance).toHaveText('700.00');
+ await expect(accountPage.accountBalance).toHaveText('600.00');
await navigation.goToAccountPage('Saving');
- await expect(accountPage.accountBalance).toHaveText('200.00');
+ await expect(accountPage.accountBalance).toHaveText('250.00');
});
test('creates a new budget file by importing Actual budget', async () => {
diff --git a/packages/desktop-client/src/components/modals/ImportTransactions.js b/packages/desktop-client/src/components/modals/ImportTransactions.js
index e9f27f5c80a..2417553515b 100644
--- a/packages/desktop-client/src/components/modals/ImportTransactions.js
+++ b/packages/desktop-client/src/components/modals/ImportTransactions.js
@@ -193,11 +193,22 @@ function getInitialMappings(transactions) {
),
);
+ let inOutField = key(
+ fields.find(
+ ([name, value]) =>
+ name !== dateField &&
+ name !== amountField &&
+ name !== payeeField &&
+ name !== notesField,
+ ),
+ );
+
return {
date: dateField,
amount: amountField,
payee: payeeField,
notes: notesField,
+ inOut: inOutField,
};
}
@@ -222,7 +233,14 @@ function parseAmount(amount, mapper) {
return value;
}
-function parseAmountFields(trans, splitMode, flipAmount, multiplierAmount) {
+function parseAmountFields(
+ trans,
+ splitMode,
+ inOutMode,
+ outValue,
+ flipAmount,
+ multiplierAmount,
+) {
const multiplier = parseFloat(multiplierAmount) || 1.0;
if (splitMode) {
@@ -240,6 +258,16 @@ function parseAmountFields(trans, splitMode, flipAmount, multiplierAmount) {
inflow,
};
}
+ if (inOutMode) {
+ return {
+ amount:
+ parseAmount(trans.amount, n =>
+ trans.inOut === outValue ? Math.abs(n) * -1 : Math.abs(n),
+ ) * multiplier,
+ outflow: null,
+ inflow: null,
+ };
+ }
return {
amount:
parseAmount(trans.amount, n => (flipAmount ? n * -1 : n)) * multiplier,
@@ -255,6 +283,8 @@ function Transaction({
parseDateFormat,
dateFormat,
splitMode,
+ inOutMode,
+ outValue,
flipAmount,
multiplierAmount,
}) {
@@ -269,6 +299,8 @@ function Transaction({
let { amount, outflow, inflow } = parseAmountFields(
transaction,
splitMode,
+ inOutMode,
+ outValue,
flipAmount,
multiplierAmount,
);
@@ -320,13 +352,24 @@ function Transaction({
>
) : (
-
- {amount}
-
+ <>
+ {inOutMode && (
+
+ {transaction.inOut}
+
+ )}
+
+ {amount}
+
+ >
)}
);
@@ -457,11 +500,43 @@ function MultiplierOption({
);
}
+function InOutOption({
+ inOutMode,
+ outValue,
+ disabled,
+ onToggle,
+ onChangeText,
+}) {
+ return (
+
+
+ {inOutMode
+ ? 'in/out identifier'
+ : 'Select column to specify if amount goes in/out'}
+
+ {inOutMode && (
+
+ )}
+
+ );
+}
+
function FieldMappings({
transactions,
mappings,
onChange,
splitMode,
+ inOutMode,
hasHeaderRow,
}) {
if (transactions.length === 0) {
@@ -537,16 +612,30 @@ function FieldMappings({
>
) : (
-
-
- onChange('amount', name)}
- hasHeaderRow={hasHeaderRow}
- firstTransaction={transactions[0]}
- />
-
+ <>
+ {inOutMode && (
+
+
+ onChange('inOut', name)}
+ hasHeaderRow={hasHeaderRow}
+ firstTransaction={transactions[0]}
+ />
+
+ )}
+
+
+ onChange('amount', name)}
+ hasHeaderRow={hasHeaderRow}
+ firstTransaction={transactions[0]}
+ />
+
+ >
)}
@@ -569,6 +658,8 @@ export default function ImportTransactions({ modalProps, options }) {
let [filetype, setFileType] = useState(null);
let [fieldMappings, setFieldMappings] = useState(null);
let [splitMode, setSplitMode] = useState(false);
+ let [inOutMode, setInOutMode] = useState(false);
+ let [outValue, setOutValue] = useState('');
let [flipAmount, setFlipAmount] = useState(false);
let [multiplierEnabled, setMultiplierEnabled] = useState(false);
let { accountId, onImported } = options;
@@ -684,6 +775,8 @@ export default function ImportTransactions({ modalProps, options }) {
let isSplit = !splitMode;
setSplitMode(isSplit);
+ setInOutMode(false);
+ setFlipAmount(false);
// Run auto-detection on the fields to try to detect the fields
// automatically
@@ -749,6 +842,8 @@ export default function ImportTransactions({ modalProps, options }) {
let { amount } = parseAmountFields(
trans,
splitMode,
+ inOutMode,
+ outValue,
flipAmount,
multiplierAmount,
);
@@ -757,7 +852,7 @@ export default function ImportTransactions({ modalProps, options }) {
break;
}
- let { inflow, outflow, ...finalTransaction } = trans;
+ let { inflow, outflow, inOut, ...finalTransaction } = trans;
finalTransactions.push({
...finalTransaction,
date,
@@ -812,6 +907,9 @@ export default function ImportTransactions({ modalProps, options }) {
{ name: 'Notes', width: 'flex' },
];
+ if (inOutMode) {
+ headers.push({ name: 'In/Out', width: 90, style: { textAlign: 'left' } });
+ }
if (splitMode) {
headers.push({ name: 'Outflow', width: 90, style: { textAlign: 'right' } });
headers.push({ name: 'Inflow', width: 90, style: { textAlign: 'right' } });
@@ -873,6 +971,8 @@ export default function ImportTransactions({ modalProps, options }) {
dateFormat={dateFormat}
fieldMappings={fieldMappings}
splitMode={splitMode}
+ inOutMode={inOutMode}
+ outValue={outValue}
flipAmount={flipAmount}
multiplierAmount={multiplierAmount}
/>
@@ -905,6 +1005,7 @@ export default function ImportTransactions({ modalProps, options }) {
onChange={onUpdateFields}
mappings={fieldMappings}
splitMode={splitMode}
+ inOutMode={inOutMode}
hasHeaderRow={hasHeaderRow}
/>
@@ -1018,19 +1119,29 @@ export default function ImportTransactions({ modalProps, options }) {
setFlipAmount(!flipAmount)}
>
Flip amount
{filetype === 'csv' && (
-
- Split amount into separate inflow/outflow columns
-
+ <>
+
+ Split amount into separate inflow/outflow columns
+
+ setInOutMode(!inOutMode)}
+ onChangeText={setOutValue}
+ />
+ >
)}
payee?.transfer_acct)
+ .map((payee: YNAB5.Payee) => [payee.transfer_acct, payee]);
+ const payeeTransferAcctHashMap = new Map(
+ payeesByTransferAcct,
+ );
+
// Go ahead and generate ids for all of the transactions so we can
// reliably resolve transfers
for (let transaction of data.transactions) {
@@ -178,11 +185,22 @@ async function importTransactions(
entityIdMap.get(transaction.transfer_transaction_id) || null,
subtransactions: subtransactions
? subtransactions.map(subtrans => {
+ let payee = null;
+ if (subtrans.transfer_account_id) {
+ const mappedTransferAccountId = entityIdMap.get(
+ subtrans.transfer_account_id,
+ );
+ payee = payeeTransferAcctHashMap.get(
+ mappedTransferAccountId,
+ )?.id;
+ }
+
return {
id: entityIdMap.get(subtrans.id),
amount: amountFromYnab(subtrans.amount),
category: entityIdMap.get(subtrans.category_id) || null,
notes: subtrans.memo,
+ payee,
};
})
: null,
diff --git a/upcoming-release-notes/1788.md b/upcoming-release-notes/1788.md
new file mode 100644
index 00000000000..040114759db
--- /dev/null
+++ b/upcoming-release-notes/1788.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [Jessseee]
+---
+
+Added option to select in/out field during import.
diff --git a/upcoming-release-notes/1836.md b/upcoming-release-notes/1836.md
new file mode 100644
index 00000000000..e6c6ff8735f
--- /dev/null
+++ b/upcoming-release-notes/1836.md
@@ -0,0 +1,6 @@
+---
+category: Bugfix
+authors: [Marethyu1]
+---
+
+UPDATES NYNAB import to support importing transactions that contain sub transactions that are account transfers