From 28f9e84be4f958465b06d7b6b5240239361ea493 Mon Sep 17 00:00:00 2001 From: Abir Stolov Date: Sat, 23 Nov 2024 22:41:52 +0200 Subject: [PATCH 1/5] Added import id for YNAB In order to avoid duplicate inserted when a payee is changed, an import_id is added --- .../export/outputVendors/ynab/ynab.test.ts | 36 +++++++++++++++++++ .../backend/export/outputVendors/ynab/ynab.ts | 16 +++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts index 7baefbac..08cc5026 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts @@ -6,6 +6,42 @@ import * as ynab from './ynab'; import ClearedEnum = SaveTransaction.ClearedEnum; describe('ynab', () => { + describe('isSameTransaction(Payee changed)', () => { + test('Two transactions with different payee names should be considered the same if they have the same import id', () => { + const transferTransactionFromYnab: TransactionDetail = { + id: '579ae642-d161-4bbe-9d54-ae3322c93cf7', + date: '2019-06-27', + amount: -1000000, + memo: null, + cleared: SaveTransaction.ClearedEnum.Cleared, + approved: true, + flag_color: null, + account_id: 'SOME_ACCOUNT_ID', + account_name: 'My great account', + payee_id: 'fd7f187c-0633-434f-aaxe-1fevd68492cb', + payee_name: 'שיק', + category_id: null, + category_name: null, + transfer_account_id: null, + transfer_transaction_id: null, + matched_transaction_id: null, + import_id: '2019-06-27-1000000', + deleted: false, + subtransactions: [], + }; + const transactionFromFinancialAccount: SaveTransaction = { + account_id: 'SOME_ACCOUNT_ID', + date: '2019-06-27', + amount: -1000000, + payee_name: 'גן', + category_id: '4e0ttc69-b4f6-420b-8d07-986c8225a3d4', + cleared: ClearedEnum.Cleared, + import_id: '2019-06-27-1000000', + }; + + expect(ynab.isSameTransaction(transactionFromFinancialAccount, transferTransactionFromYnab)).toBeTruthy(); + }); + }); describe('isSameTransaction', () => { test('Two transactions with different payee names should be considered the same if one of them is a transfer transaction', () => { const transferTransactionFromYnab: TransactionDetail = { diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts index 3394d7d0..83dd0ba1 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts @@ -109,7 +109,6 @@ export function getPayeeName(transaction: EnrichedTransaction, payeeNameMaxLengt function convertTransactionToYnabFormat(originalTransaction: EnrichedTransaction): ynab.SaveTransaction { const amount = Math.round(originalTransaction.chargedAmount * 1000); const date = convertTimestampToYnabDateFormat(originalTransaction); - return { account_id: getYnabAccountIdByAccountNumberFromTransaction(originalTransaction.accountNumber), date, // "2019-01-17", @@ -119,12 +118,17 @@ function convertTransactionToYnabFormat(originalTransaction: EnrichedTransaction category_id: getYnabCategoryIdFromCategoryName(originalTransaction.category), memo: originalTransaction.memo, cleared: ynab.SaveTransaction.ClearedEnum.Cleared, + import_id: buildImportId(originalTransaction), // "approved": true, // "flag_color": "red", // "import_id": buildImportId(originalTransaction.description, amount, date) // 'YNAB:[milliunit_amount]:[iso_date]:[occurrence]' }; } +function buildImportId(transaction: EnrichedTransaction): string { + return `${transaction.date.substring(0, 10)}${transaction.chargedAmount}${transaction.description}`.substring(0, 36); +} + function getYnabAccountIdByAccountNumberFromTransaction(transactionAccountNumber: string): string { const ynabAccountId = ynabConfig!.options.accountNumbersToYnabAccountIds[transactionAccountNumber]; if (!ynabAccountId) { @@ -196,7 +200,8 @@ export function isSameTransaction( Math.abs(transactionToCreate.amount - transactionFromYnab.amount) < 1000 && // In a transfer transaction the payee name changes, but we still consider this the same transaction (areStringsEqualIgnoreCaseAndWhitespace(transactionToCreate.payee_name, transactionFromYnab.payee_name) || - isATransferTransaction) + isATransferTransaction || + isSameImportId(transactionToCreate, transactionFromYnab)) ); } @@ -321,3 +326,10 @@ export const ynabOutputVendor: OutputVendor = { init, exportTransactions: createTransactions, }; + +function isSameImportId( + transactionToCreate: ynab.SaveTransaction, + transactionFromYnab: ynab.TransactionDetail, +): boolean { + return !!transactionToCreate.import_id && transactionToCreate.import_id === transactionFromYnab.import_id; +} From 460aef8896a0e9979b3de84654f792e6af7a7b9a Mon Sep 17 00:00:00 2001 From: Abir Stolov Date: Sat, 23 Nov 2024 22:49:53 +0200 Subject: [PATCH 2/5] added YNAB max import id const --- .../main/src/backend/export/outputVendors/ynab/ynab.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts index 83dd0ba1..59b60c93 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts @@ -16,6 +16,7 @@ import * as ynab from 'ynab'; const YNAB_DATE_FORMAT = 'YYYY-MM-DD'; const NOW = moment(); const MIN_YNAB_ACCESS_TOKEN_LENGTH = 43; +const MAX_YNAB_IMPORT_ID_LENGTH = 36; const categoriesMap = new Map>(); const transactionsFromYnab = new Map(); @@ -118,7 +119,7 @@ function convertTransactionToYnabFormat(originalTransaction: EnrichedTransaction category_id: getYnabCategoryIdFromCategoryName(originalTransaction.category), memo: originalTransaction.memo, cleared: ynab.SaveTransaction.ClearedEnum.Cleared, - import_id: buildImportId(originalTransaction), + import_id: buildImportId(originalTransaction), // [date][amount][description] // "approved": true, // "flag_color": "red", // "import_id": buildImportId(originalTransaction.description, amount, date) // 'YNAB:[milliunit_amount]:[iso_date]:[occurrence]' @@ -126,7 +127,10 @@ function convertTransactionToYnabFormat(originalTransaction: EnrichedTransaction } function buildImportId(transaction: EnrichedTransaction): string { - return `${transaction.date.substring(0, 10)}${transaction.chargedAmount}${transaction.description}`.substring(0, 36); + return `${transaction.date.substring(0, 10)}${transaction.chargedAmount}${transaction.description}`.substring( + 0, + MAX_YNAB_IMPORT_ID_LENGTH, + ); } function getYnabAccountIdByAccountNumberFromTransaction(transactionAccountNumber: string): string { From bdc593fbf1e2107d2f2339fa10870ac3e361a52f Mon Sep 17 00:00:00 2001 From: Abir Stolov Date: Tue, 26 Nov 2024 21:50:39 +0200 Subject: [PATCH 3/5] break condition into a variable --- packages/main/src/backend/export/outputVendors/ynab/ynab.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts index 59b60c93..b555d5ce8d 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.ts @@ -197,6 +197,7 @@ export function isSameTransaction( transactionFromYnab: ynab.TransactionDetail, ) { const isATransferTransaction = !!transactionFromYnab.transfer_account_id; + const isTransactionsImportIdEqual = isSameImportId(transactionToCreate, transactionFromYnab); return ( transactionToCreate.account_id === transactionFromYnab.account_id && transactionToCreate.date === transactionFromYnab.date && @@ -205,7 +206,7 @@ export function isSameTransaction( // In a transfer transaction the payee name changes, but we still consider this the same transaction (areStringsEqualIgnoreCaseAndWhitespace(transactionToCreate.payee_name, transactionFromYnab.payee_name) || isATransferTransaction || - isSameImportId(transactionToCreate, transactionFromYnab)) + isTransactionsImportIdEqual) ); } From 008131cd3c26b30f66cd2acfb20046819c5bd0ab Mon Sep 17 00:00:00 2001 From: Abir Stolov Date: Fri, 29 Nov 2024 21:18:24 +0200 Subject: [PATCH 4/5] added description to ynab import_id test --- .../main/src/backend/export/outputVendors/ynab/ynab.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts index 08cc5026..4232648b 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts @@ -25,7 +25,7 @@ describe('ynab', () => { transfer_account_id: null, transfer_transaction_id: null, matched_transaction_id: null, - import_id: '2019-06-27-1000000', + import_id: '2019-06-27-1000000שיק', deleted: false, subtransactions: [], }; @@ -36,7 +36,7 @@ describe('ynab', () => { payee_name: 'גן', category_id: '4e0ttc69-b4f6-420b-8d07-986c8225a3d4', cleared: ClearedEnum.Cleared, - import_id: '2019-06-27-1000000', + import_id: '2019-06-27-1000000שיק', }; expect(ynab.isSameTransaction(transactionFromFinancialAccount, transferTransactionFromYnab)).toBeTruthy(); From df479a86a1a9e7125efe24f5d840c8a2371b8ccb Mon Sep 17 00:00:00 2001 From: Abir Stolov Date: Sat, 30 Nov 2024 13:21:41 +0200 Subject: [PATCH 5/5] refactor: YNAB isSameTransaction tests --- .../src/backend/export/outputVendors/ynab/ynab.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts index 4232648b..62cc9174 100644 --- a/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts +++ b/packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts @@ -6,9 +6,9 @@ import * as ynab from './ynab'; import ClearedEnum = SaveTransaction.ClearedEnum; describe('ynab', () => { - describe('isSameTransaction(Payee changed)', () => { + describe('isSameTransaction', () => { test('Two transactions with different payee names should be considered the same if they have the same import id', () => { - const transferTransactionFromYnab: TransactionDetail = { + const differentPayeeTransactionFromYnab: TransactionDetail = { id: '579ae642-d161-4bbe-9d54-ae3322c93cf7', date: '2019-06-27', amount: -1000000, @@ -39,10 +39,8 @@ describe('ynab', () => { import_id: '2019-06-27-1000000שיק', }; - expect(ynab.isSameTransaction(transactionFromFinancialAccount, transferTransactionFromYnab)).toBeTruthy(); + expect(ynab.isSameTransaction(transactionFromFinancialAccount, differentPayeeTransactionFromYnab)).toBeTruthy(); }); - }); - describe('isSameTransaction', () => { test('Two transactions with different payee names should be considered the same if one of them is a transfer transaction', () => { const transferTransactionFromYnab: TransactionDetail = { id: '579ae642-d161-4bbe-9d54-ae3322c93cf7',