-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Added import id for YNAB #626
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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[]>(); | ||||||||||||||||||||||||||||||||||
|
@@ -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", | ||||||||||||||||||||||||||||||||||
|
@@ -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, | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Comment on lines
+129
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve uniqueness of Concatenating date, amount, and description can cause collisions when different transactions produce the same truncated string. To enhance uniqueness within the character limit, it's advisable to follow YNAB's suggested pattern. Here's a revised implementation: +import crypto from 'crypto';
function buildImportId(transaction: EnrichedTransaction): string {
- return `${transaction.date.substring(0, 10)}${transaction.chargedAmount}${transaction.description}`.substring(
- 0,
- MAX_YNAB_IMPORT_ID_LENGTH,
- );
+ const amount = Math.round(transaction.chargedAmount * 1000);
+ const date = transaction.date.substring(0, 10);
+ const occurrence = '1'; // Or calculate occurrence if necessary
+ return `YNAB:${amount}:${date}:${occurrence}`.substring(0, MAX_YNAB_IMPORT_ID_LENGTH);
} If you need to handle multiple occurrences, you can increment the 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Comment on lines
+129
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you not using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because YNAB import_id is 36 characters max and should be unique |
||||||||||||||||||||||||||||||||||
function getYnabAccountIdByAccountNumberFromTransaction(transactionAccountNumber: string): string { | ||||||||||||||||||||||||||||||||||
const ynabAccountId = ynabConfig!.options.accountNumbersToYnabAccountIds[transactionAccountNumber]; | ||||||||||||||||||||||||||||||||||
if (!ynabAccountId) { | ||||||||||||||||||||||||||||||||||
|
@@ -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 | ||||||||||||||||||||||||||||||||||
brafdlog marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
(areStringsEqualIgnoreCaseAndWhitespace(transactionToCreate.payee_name, transactionFromYnab.payee_name) || | ||||||||||||||||||||||||||||||||||
Comment on lines
205
to
207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential Using Consider modifying the code to handle these cases explicitly: -// @ts-expect-error error TS18049: 'transactionToCreate.amount' is possibly 'null' or 'undefined'
-Math.abs(transactionToCreate.amount - transactionFromYnab.amount) < 1000 &&
+const amountDifferenceIsSmall =
+ transactionToCreate.amount != null &&
+ transactionFromYnab.amount != null &&
+ Math.abs(transactionToCreate.amount - transactionFromYnab.amount) < 1000;
+
+return (
+ transactionToCreate.account_id === transactionFromYnab.account_id &&
+ transactionToCreate.date === transactionFromYnab.date &&
+ amountDifferenceIsSmall &&
+ (areStringsEqualIgnoreCaseAndWhitespace(transactionToCreate.payee_name, transactionFromYnab.payee_name) ||
+ isATransferTransaction ||
+ isTransactionsImportIdEqual)
+); This approach ensures that you handle
|
||||||||||||||||||||||||||||||||||
isATransferTransaction) | ||||||||||||||||||||||||||||||||||
isATransferTransaction || | ||||||||||||||||||||||||||||||||||
isTransactionsImportIdEqual) | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
|
@@ -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; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Comment on lines
+335
to
+340
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure safe comparison of In Update the function to handle potential function isSameImportId(
transactionToCreate: ynab.SaveTransaction,
transactionFromYnab: ynab.TransactionDetail,
): boolean {
- return !!transactionToCreate.import_id && transactionToCreate.import_id === transactionFromYnab.import_id;
+ return (
+ transactionToCreate.import_id != null &&
+ transactionFromYnab.import_id != null &&
+ transactionToCreate.import_id === transactionFromYnab.import_id
+ );
} This change ensures that the comparison only occurs when both 📝 Committable suggestion
Suggested change
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure
import_id
follows YNAB's recommended format for uniqueness.The current
import_id
generation may lead to collisions due to truncation and similarity in transaction data. YNAB recommends using the formatYNAB:[milliunit_amount]:[iso_date]:[occurrence]
to ensure uniqueness.Consider updating the
buildImportId
function to match this format. Here's how you might adjust it: