From d9c2a3ba466d44815caf7830437beb5b637c5ddf Mon Sep 17 00:00:00 2001 From: stefanhall Date: Mon, 8 Jan 2024 11:27:50 +1300 Subject: [PATCH 1/6] Allow case insensitive ynab5 import for special 'starting balance' payee --- packages/loot-core/src/server/importers/ynab5.ts | 6 +++++- upcoming-release-notes/1968.md | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 upcoming-release-notes/1968.md diff --git a/packages/loot-core/src/server/importers/ynab5.ts b/packages/loot-core/src/server/importers/ynab5.ts index 0653a1f95a1..57e3ed3f2e3 100644 --- a/packages/loot-core/src/server/importers/ynab5.ts +++ b/packages/loot-core/src/server/importers/ynab5.ts @@ -136,8 +136,12 @@ async function importTransactions( const startingBalanceCatId = categories.find( cat => cat.name === 'Starting Balances', ).id; //better way to do it? + const startingPayeeYNAB = data.payees.find( - payee => payee.name === 'Starting Balance', + payee => + payee.name.localeCompare('Starting Balance', undefined, { + sensitivity: 'base', + }) === 0, ).id; const transactionsGrouped = groupBy(data.transactions, 'account_id'); diff --git a/upcoming-release-notes/1968.md b/upcoming-release-notes/1968.md new file mode 100644 index 00000000000..810ec372769 --- /dev/null +++ b/upcoming-release-notes/1968.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [Marethyu1] +--- + +Allow case insensitive ynab5 import for special 'starting balance' payee From 119246a2de726171f01500b28f1f3451bdc2a7cb Mon Sep 17 00:00:00 2001 From: stefanhall Date: Mon, 8 Jan 2024 11:32:32 +1300 Subject: [PATCH 2/6] set upcoming release number to related github issue --- upcoming-release-notes/{1968.md => 2191.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename upcoming-release-notes/{1968.md => 2191.md} (100%) diff --git a/upcoming-release-notes/1968.md b/upcoming-release-notes/2191.md similarity index 100% rename from upcoming-release-notes/1968.md rename to upcoming-release-notes/2191.md From a7bb9af1028d35f84d066f16cb35a40109d4a899 Mon Sep 17 00:00:00 2001 From: stefanhall Date: Tue, 9 Jan 2024 09:03:45 +1300 Subject: [PATCH 3/6] extract string comparison into separate function and reuse when checking starting balance/s on ynab4 import --- .../loot-core/src/server/importers/ynab5.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/loot-core/src/server/importers/ynab5.ts b/packages/loot-core/src/server/importers/ynab5.ts index 57e3ed3f2e3..421c761ac13 100644 --- a/packages/loot-core/src/server/importers/ynab5.ts +++ b/packages/loot-core/src/server/importers/ynab5.ts @@ -133,15 +133,12 @@ async function importTransactions( const payees = await actual.getPayees(); const categories = await actual.getCategories(); const incomeCatId = categories.find(cat => cat.name === 'Income').id; - const startingBalanceCatId = categories.find( - cat => cat.name === 'Starting Balances', + const startingBalanceCatId = categories.find(cat => + equalsIgnoreCase(cat.name, 'Starting Balances'), ).id; //better way to do it? - const startingPayeeYNAB = data.payees.find( - payee => - payee.name.localeCompare('Starting Balance', undefined, { - sensitivity: 'base', - }) === 0, + const startingPayeeYNAB = data.payees.find(payee => + equalsIgnoreCase(payee.name, 'Starting Balance'), ).id; const transactionsGrouped = groupBy(data.transactions, 'account_id'); @@ -331,3 +328,11 @@ export function parseFile(buffer: Buffer): YNAB5.Budget { export function getBudgetName(_filepath: string, data: YNAB5.Budget) { return data.budget_name || data.name; } + +function equalsIgnoreCase(stringa: string, stringb: string): boolean { + return ( + stringa.localeCompare(stringb, undefined, { + sensitivity: 'base', + }) === 0 + ); +} From 3007f2dfaf020bc99e1b8c3ccd0479846c2691bd Mon Sep 17 00:00:00 2001 From: stefanhall Date: Tue, 9 Jan 2024 09:24:11 +1300 Subject: [PATCH 4/6] make all category group checks case insensitive when importing from ynab5 to make the check strategy consistent when importing from ynab5 --- .../loot-core/src/server/importers/ynab5.ts | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/loot-core/src/server/importers/ynab5.ts b/packages/loot-core/src/server/importers/ynab5.ts index 421c761ac13..f1ee2279173 100644 --- a/packages/loot-core/src/server/importers/ynab5.ts +++ b/packages/loot-core/src/server/importers/ynab5.ts @@ -39,25 +39,31 @@ async function importCategories( // so it's already handled. const categories = await actual.getCategories(); - const incomeCatId = categories.find(cat => cat.name === 'Income').id; + const incomeCatId = categories.find(cat => + equalsIgnoreCase(cat.name, 'Income'), + ).id; const ynabIncomeCategories = ['To be Budgeted', 'Inflow: Ready to Assign']; function checkSpecialCat(cat) { if ( - cat.category_group_id === - data.category_groups.find( - group => group.name === 'Internal Master Category', + data.category_groups.find(group => + equalsIgnoreCase(group.name, 'Internal Master Category'), ).id ) { - if (ynabIncomeCategories.includes(cat.name)) { + if ( + ynabIncomeCategories.some(ynabIncomeCategory => + equalsIgnoreCase(cat.name, ynabIncomeCategory), + ) + ) { return 'income'; } else { return 'internal'; } } else if ( cat.category_group_id === - data.category_groups.find(group => group.name === 'Credit Card Payments') - .id + data.category_groups.find(group => + equalsIgnoreCase(group.name, 'Credit Card Payments'), + ).id ) { return 'creditCard'; } @@ -70,8 +76,8 @@ async function importCategories( let groupId; // Ignores internal category and credit cards if ( - group.name !== 'Internal Master Category' && - group.name !== 'Credit Card Payments' + !equalsIgnoreCase(group.name, 'Internal Master Category') && + !equalsIgnoreCase(group.name, 'Credit Card Payments') ) { groupId = await actual.createCategoryGroup({ name: group.name, @@ -132,7 +138,9 @@ async function importTransactions( ) { const payees = await actual.getPayees(); const categories = await actual.getCategories(); - const incomeCatId = categories.find(cat => cat.name === 'Income').id; + const incomeCatId = categories.find(cat => + equalsIgnoreCase(cat.name, 'Income'), + ).id; const startingBalanceCatId = categories.find(cat => equalsIgnoreCase(cat.name, 'Starting Balances'), ).id; //better way to do it? @@ -259,11 +267,11 @@ async function importBudgets( const budgets = sortByKey(data.months, 'month'); - const internalCatIdYnab = data.category_groups.find( - group => group.name === 'Internal Master Category', + const internalCatIdYnab = data.category_groups.find(group => + equalsIgnoreCase(group.name, 'Internal Master Category'), ).id; - const creditcardCatIdYnab = data.category_groups.find( - group => group.name === 'Credit Card Payments', + const creditcardCatIdYnab = data.category_groups.find(group => + equalsIgnoreCase(group.name, 'Credit Card Payments'), ).id; await actual.batchBudgetUpdates(async () => { From 79c1563cf684d304e54de2033a74d51ed783f655 Mon Sep 17 00:00:00 2001 From: stefanhall Date: Tue, 9 Jan 2024 09:31:13 +1300 Subject: [PATCH 5/6] extract findById into sreusable function to 'simplify' usage --- .../loot-core/src/server/importers/ynab5.ts | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/loot-core/src/server/importers/ynab5.ts b/packages/loot-core/src/server/importers/ynab5.ts index f1ee2279173..3e76b70dfff 100644 --- a/packages/loot-core/src/server/importers/ynab5.ts +++ b/packages/loot-core/src/server/importers/ynab5.ts @@ -39,16 +39,13 @@ async function importCategories( // so it's already handled. const categories = await actual.getCategories(); - const incomeCatId = categories.find(cat => - equalsIgnoreCase(cat.name, 'Income'), - ).id; + const incomeCatId = findIdByName(categories, 'Income'); const ynabIncomeCategories = ['To be Budgeted', 'Inflow: Ready to Assign']; function checkSpecialCat(cat) { if ( - data.category_groups.find(group => - equalsIgnoreCase(group.name, 'Internal Master Category'), - ).id + cat.category_group_id === + findIdByName(data.category_groups, 'Internal Master Category') ) { if ( ynabIncomeCategories.some(ynabIncomeCategory => @@ -61,9 +58,7 @@ async function importCategories( } } else if ( cat.category_group_id === - data.category_groups.find(group => - equalsIgnoreCase(group.name, 'Credit Card Payments'), - ).id + findIdByName(data.category_groups, 'Credit Card Payments') ) { return 'creditCard'; } @@ -138,16 +133,10 @@ async function importTransactions( ) { const payees = await actual.getPayees(); const categories = await actual.getCategories(); - const incomeCatId = categories.find(cat => - equalsIgnoreCase(cat.name, 'Income'), - ).id; - const startingBalanceCatId = categories.find(cat => - equalsIgnoreCase(cat.name, 'Starting Balances'), - ).id; //better way to do it? + const incomeCatId = findIdByName(categories, 'Income'); + const startingBalanceCatId = findIdByName(categories, 'Starting Balances'); //better way to do it? - const startingPayeeYNAB = data.payees.find(payee => - equalsIgnoreCase(payee.name, 'Starting Balance'), - ).id; + const startingPayeeYNAB = findIdByName(data.payees, 'Starting Balance'); const transactionsGrouped = groupBy(data.transactions, 'account_id'); const subtransactionsGrouped = groupBy( @@ -267,12 +256,14 @@ async function importBudgets( const budgets = sortByKey(data.months, 'month'); - const internalCatIdYnab = data.category_groups.find(group => - equalsIgnoreCase(group.name, 'Internal Master Category'), - ).id; - const creditcardCatIdYnab = data.category_groups.find(group => - equalsIgnoreCase(group.name, 'Credit Card Payments'), - ).id; + const internalCatIdYnab = findIdByName( + data.category_groups, + 'Internal Master Category', + ); + const creditcardCatIdYnab = findIdByName( + data.category_groups, + 'Credit Card Payments', + ); await actual.batchBudgetUpdates(async () => { for (const budget of budgets) { @@ -344,3 +335,11 @@ function equalsIgnoreCase(stringa: string, stringb: string): boolean { }) === 0 ); } + +function findByNameIgnoreCase(categories: YNAB5.CategoryGroup[], name: string) { + return categories.find(cat => equalsIgnoreCase(cat.name, name)); +} + +function findIdByName(categories: YNAB5.CategoryGroup[], name: string) { + return findByNameIgnoreCase(categories, name).id; +} From 9740a1a4ed817c088c99d92a14b2a9c8f7026ac5 Mon Sep 17 00:00:00 2001 From: Stefan Hall Date: Sat, 13 Jan 2024 09:37:38 +1300 Subject: [PATCH 6/6] Add null check Co-authored-by: Joel Jeremy Marquez --- packages/loot-core/src/server/importers/ynab5.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loot-core/src/server/importers/ynab5.ts b/packages/loot-core/src/server/importers/ynab5.ts index 3e76b70dfff..8ea7ac63089 100644 --- a/packages/loot-core/src/server/importers/ynab5.ts +++ b/packages/loot-core/src/server/importers/ynab5.ts @@ -341,5 +341,5 @@ function findByNameIgnoreCase(categories: YNAB5.CategoryGroup[], name: string) { } function findIdByName(categories: YNAB5.CategoryGroup[], name: string) { - return findByNameIgnoreCase(categories, name).id; + return findByNameIgnoreCase(categories, name)?.id; }