Skip to content

Commit

Permalink
Fix: Added import id for YNAB (#626)
Browse files Browse the repository at this point in the history
  • Loading branch information
brafdlog authored Dec 5, 2024
2 parents 6ca612f + df479a8 commit 97f0909
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
34 changes: 34 additions & 0 deletions packages/main/src/backend/export/outputVendors/ynab/ynab.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ import ClearedEnum = SaveTransaction.ClearedEnum;

describe('ynab', () => {
describe('isSameTransaction', () => {
test('Two transactions with different payee names should be considered the same if they have the same import id', () => {
const differentPayeeTransactionFromYnab: 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, differentPayeeTransactionFromYnab)).toBeTruthy();
});
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',
Expand Down
21 changes: 19 additions & 2 deletions packages/main/src/backend/export/outputVendors/ynab/ynab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Pick<ynab.Category, 'id' | 'name' | 'category_group_id'>>();
const transactionsFromYnab = new Map<Date, ynab.TransactionDetail[]>();
Expand Down Expand Up @@ -109,7 +110,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",
Expand All @@ -119,12 +119,20 @@ function convertTransactionToYnabFormat(originalTransaction: EnrichedTransaction
category_id: getYnabCategoryIdFromCategoryName(originalTransaction.category),
memo: originalTransaction.memo,
cleared: ynab.SaveTransaction.ClearedEnum.Cleared,
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]'
};
}

function buildImportId(transaction: EnrichedTransaction): string {
return `${transaction.date.substring(0, 10)}${transaction.chargedAmount}${transaction.description}`.substring(
0,
MAX_YNAB_IMPORT_ID_LENGTH,
);
}

function getYnabAccountIdByAccountNumberFromTransaction(transactionAccountNumber: string): string {
const ynabAccountId = ynabConfig!.options.accountNumbersToYnabAccountIds[transactionAccountNumber];
if (!ynabAccountId) {
Expand Down Expand Up @@ -189,14 +197,16 @@ 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 &&
// @ts-expect-error error TS18049: 'transactionToCreate.amount' is possibly 'null' or 'undefined'
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 ||
isTransactionsImportIdEqual)
);
}

Expand Down Expand Up @@ -321,3 +331,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;
}

0 comments on commit 97f0909

Please sign in to comment.