From a9039e825d6b2eafce0469f51a01f41c0c5a3c14 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Sun, 31 Mar 2024 13:55:51 +0100 Subject: [PATCH 1/9] :recycle: (bank-sync) unify the sync/reconciliation logic for interal & external sync --- .../loot-core/src/server/accounts/link.ts | 8 +- .../loot-core/src/server/accounts/sync.ts | 423 +++++------------- packages/loot-core/src/server/main.ts | 6 +- 3 files changed, 114 insertions(+), 323 deletions(-) diff --git a/packages/loot-core/src/server/accounts/link.ts b/packages/loot-core/src/server/accounts/link.ts index ff2618af87d..26879ed3c5e 100644 --- a/packages/loot-core/src/server/accounts/link.ts +++ b/packages/loot-core/src/server/accounts/link.ts @@ -144,13 +144,7 @@ export async function addGoCardlessAccounts( }); // Do an initial sync - await bankSync.syncExternalAccount( - userId, - userKey, - id, - acct.account_id, - bankId, - ); + await bankSync.syncAccount(userId, userKey, id, acct.account_id, bankId); return id; }), diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index 494759b7278..8a3251272c3 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -19,6 +19,7 @@ import { getStartingBalancePayee } from './payees'; import { title } from './title'; import { runRules } from './transaction-rules'; import { batchUpdateTransactions } from './transactions'; +import { TransactionEntity } from 'loot-core/types/models'; // Plaid article about API options: // https://support.plaid.com/customer/en/portal/articles/2612155-transactions-returned-per-request @@ -437,14 +438,18 @@ async function createNewPayees(payeesToCreate, addsAndUpdates) { }); } -export async function reconcileExternalTransactions(acctId, transactions) { +export async function reconcileTransactions( + acctId, + transactions, + transactionNormalization = normalizeTransactions, +) { console.log('Performing transaction reconciliation'); const hasMatched = new Set(); const updated = []; const added = []; - const { normalized, payeesToCreate } = await normalizeExternalTransactions( + const { normalized, payeesToCreate } = await transactionNormalization( transactions, acctId, ); @@ -575,6 +580,7 @@ export async function reconcileExternalTransactions(acctId, transactions) { // Update the transaction const updates = { + date: trans.date, imported_id: trans.imported_id || null, payee: existing.payee || trans.payee || null, category: existing.category || trans.category || null, @@ -630,190 +636,6 @@ export async function reconcileExternalTransactions(acctId, transactions) { }; } -export async function reconcileTransactions(acctId, transactions) { - const hasMatched = new Set(); - const updated = []; - const added = []; - - const { normalized, payeesToCreate } = await normalizeTransactions( - transactions, - acctId, - ); - - // The first pass runs the rules, and preps data for fuzzy matching - const transactionsStep1 = []; - for (const { - payee_name, - trans: originalTrans, - subtransactions, - } of normalized) { - // Run the rules - const trans = runRules(originalTrans); - - let match = null; - let fuzzyDataset = null; - - // First, match with an existing transaction's imported_id. This - // is the highest fidelity match and should always be attempted - // first. - if (trans.imported_id) { - match = await db.first( - 'SELECT * FROM v_transactions WHERE imported_id = ? AND account = ?', - [trans.imported_id, acctId], - ); - - if (match) { - hasMatched.add(match.id); - } - } - - // If it didn't match, query data needed for fuzzy matching - if (!match) { - // Look 7 days ahead and 7 days back when fuzzy matching. This - // needs to select all fields that need to be read from the - // matched transaction. See the final pass below for the needed - // fields. - fuzzyDataset = await db.all( - `SELECT id, is_parent, date, imported_id, payee, category, notes, reconciled FROM v_transactions - WHERE date >= ? AND date <= ? AND amount = ? AND account = ?`, - [ - db.toDateRepr(monthUtils.subDays(trans.date, 7)), - db.toDateRepr(monthUtils.addDays(trans.date, 7)), - trans.amount || 0, - acctId, - ], - ); - - // Sort the matched transactions according to the distance from the original - // transactions date. i.e. if the original transaction is in 21-02-2024 and - // the matched transactions are: 20-02-2024, 21-02-2024, 29-02-2024 then - // the resulting data-set should be: 21-02-2024, 20-02-2024, 29-02-2024. - fuzzyDataset = fuzzyDataset.sort((a, b) => { - const aDistance = Math.abs( - dateFns.differenceInMilliseconds( - dateFns.parseISO(trans.date), - dateFns.parseISO(db.fromDateRepr(a.date)), - ), - ); - const bDistance = Math.abs( - dateFns.differenceInMilliseconds( - dateFns.parseISO(trans.date), - dateFns.parseISO(db.fromDateRepr(b.date)), - ), - ); - return aDistance > bDistance ? 1 : -1; - }); - } - - transactionsStep1.push({ - payee_name, - trans, - subtransactions: trans.subtransactions || subtransactions, - match, - fuzzyDataset, - }); - } - - // Next, do the fuzzy matching. This first pass matches based on the - // payee id. We do this in multiple passes so that higher fidelity - // matching always happens first, i.e. a transaction should match - // match with low fidelity if a later transaction is going to match - // the same one with high fidelity. - const transactionsStep2 = transactionsStep1.map(data => { - if (!data.match && data.fuzzyDataset) { - // Try to find one where the payees match. - const match = data.fuzzyDataset.find( - row => !hasMatched.has(row.id) && data.trans.payee === row.payee, - ); - - if (match) { - hasMatched.add(match.id); - return { ...data, match }; - } - } - return data; - }); - - // The final fuzzy matching pass. This is the lowest fidelity - // matching: it just find the first transaction that hasn't been - // matched yet. Remember the dataset only contains transactions - // around the same date with the same amount. - const transactionsStep3 = transactionsStep2.map(data => { - if (!data.match && data.fuzzyDataset) { - const match = data.fuzzyDataset.find(row => !hasMatched.has(row.id)); - if (match) { - hasMatched.add(match.id); - return { ...data, match }; - } - } - return data; - }); - - // Finally, generate & commit the changes - for (const { trans, subtransactions, match } of transactionsStep3) { - if (match) { - // Skip updating already reconciled (locked) transactions - if (match.reconciled) { - continue; - } - - // TODO: change the above sql query to use aql - const existing = { - ...match, - cleared: match.cleared === 1, - date: db.fromDateRepr(match.date), - }; - - // Update the transaction - const updates = { - date: trans.date, - imported_id: trans.imported_id || null, - payee: existing.payee || trans.payee || null, - category: existing.category || trans.category || null, - imported_payee: trans.imported_payee || null, - notes: existing.notes || trans.notes || null, - cleared: trans.cleared != null ? trans.cleared : true, - }; - - if (hasFieldsChanged(existing, updates, Object.keys(updates))) { - updated.push({ id: existing.id, ...updates }); - } - - if (existing.is_parent && existing.cleared !== updates.cleared) { - const children = await db.all( - 'SELECT id FROM v_transactions WHERE parent_id = ?', - [existing.id], - ); - for (const child of children) { - updated.push({ id: child.id, cleared: updates.cleared }); - } - } - } else { - // Insert a new transaction - const finalTransaction = { - ...trans, - id: uuidv4(), - category: trans.category || null, - cleared: trans.cleared != null ? trans.cleared : true, - }; - - if (subtransactions && subtransactions.length > 0) { - added.push(...makeSplitTransaction(finalTransaction, subtransactions)); - } else { - added.push(finalTransaction); - } - } - } - - await createNewPayees(payeesToCreate, [...added, ...updated]); - await batchUpdateTransactions({ added, updated }); - - return { - added: added.map(trans => trans.id), - updated: updated.map(trans => trans.id), - }; -} - // This is similar to `reconcileTransactions` except much simpler: it // does not try to match any transactions. It just adds them export async function addTransactions( @@ -872,7 +694,13 @@ export async function addTransactions( return newTransactions; } -export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { +export async function syncAccount( + userId: string, + userKey: string, + id: string, + acctId: string, + bankId: string, +) { // TODO: Handle the case where transactions exist in the future // (that will make start date after end date) const latestTransaction = await db.first( @@ -881,15 +709,17 @@ export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { ); const acctRow = await db.select('accounts', id); + const isExternalAccount = + acctRow.account_sync_source === 'simpleFin' || + acctRow.account_sync_source === 'goCardless'; if (latestTransaction) { const startingTransaction = await db.first( 'SELECT date FROM v_transactions WHERE account = ? ORDER BY date ASC LIMIT 1', [id], ); - const startingDate = monthUtils.parseDate( - db.fromDateRepr(startingTransaction.date), - ); + const startingDate = db.fromDateRepr(startingTransaction.date); + // assert(startingTransaction) const startDate = monthUtils.dayFromDate( dateFns.max([ @@ -898,7 +728,7 @@ export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { monthUtils.parseDate(monthUtils.subDays(monthUtils.currentDay(), 90)), // Never download transactions before the starting date. - startingDate, + monthUtils.parseDate(startingDate), ]), ); @@ -914,6 +744,31 @@ export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { bankId, startDate, ); + } else { + // Get all transactions since the latest transaction, plus any 5 + // days before the latest transaction. This gives us a chance to + // resolve any transactions that were entered manually. + // + // TODO: What this really should do is query the last imported_id + // and since then + let date = monthUtils.subDays( + db.fromDateRepr(latestTransaction.date), + 31, + ); + + // Never download transactions before the starting date. This was + // when the account was added to the system. + if (date < startingDate) { + date = startingDate; + } + + download = await downloadTransactions( + userId, + userKey, + acctId, + bankId, + date, + ); } const { transactions: originalTransactions, accountBalance } = download; @@ -928,40 +783,78 @@ export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { })); return runMutator(async () => { - const result = await reconcileExternalTransactions(id, transactions); + const result = await reconcileTransactions( + id, + transactions, + isExternalAccount + ? normalizeExternalTransactions + : normalizeTransactions, + ); await updateAccountBalance(id, accountBalance); return result; }); } else { - // Otherwise, download transaction for the past 90 days - const startingDay = monthUtils.subDays(monthUtils.currentDay(), 90); + let balanceToUse: number; + let transactions; - let download; + if (isExternalAccount) { + let download; - if (acctRow.account_sync_source === 'simpleFin') { - download = await downloadSimpleFinTransactions(acctId, startingDay); - } else if (acctRow.account_sync_source === 'goCardless') { - download = await downloadGoCardlessTransactions( + // Otherwise, download transaction for the past 90 days + const startingDay = monthUtils.subDays(monthUtils.currentDay(), 90); + + if (acctRow.account_sync_source === 'simpleFin') { + download = await downloadSimpleFinTransactions(acctId, startingDay); + } else if (acctRow.account_sync_source === 'goCardless') { + download = await downloadGoCardlessTransactions( + userId, + userKey, + acctId, + bankId, + startingDay, + ); + } + + transactions = download.transactions; + balanceToUse = download.startingBalance; + + if (acctRow.account_sync_source === 'simpleFin') { + const currentBalance = download.startingBalance; + const previousBalance = transactions.reduce((total, trans) => { + return ( + total - parseInt(trans.transactionAmount.amount.replace('.', '')) + ); + }, currentBalance); + balanceToUse = previousBalance; + } + } else { + // Otherwise, download transaction for the last few days if it's an + // on-budget account, or for the past 30 days if off-budget + const startingDay = monthUtils.subDays( + monthUtils.currentDay(), + acctRow.offbudget === 0 ? 1 : 30, + ); + + const download = await downloadTransactions( userId, userKey, acctId, bankId, - startingDay, + dateFns.format(dateFns.parseISO(startingDay), 'yyyy-MM-dd'), ); - } - const { transactions, startingBalance } = download; + transactions = download.transactions; - let balanceToUse = startingBalance; + // We need to add a transaction that represents the starting + // balance for everything to balance out. In order to get balance + // before the first imported transaction, we need to get the + // current balance from the accounts table and subtract all the + // imported transactions. + const currentBalance = acctRow.balance_current; - if (acctRow.account_sync_source === 'simpleFin') { - const currentBalance = startingBalance; - const previousBalance = transactions.reduce((total, trans) => { - return ( - total - parseInt(trans.transactionAmount.amount.replace('.', '')) - ); + balanceToUse = transactions.reduce((total, trans) => { + return total - trans.amount; }, currentBalance); - balanceToUse = previousBalance; } const oldestTransaction = transactions[transactions.length - 1]; @@ -984,109 +877,13 @@ export async function syncExternalAccount(userId, userKey, id, acctId, bankId) { starting_balance_flag: true, }); - const result = await reconcileExternalTransactions(id, transactions); - return { - ...result, - added: [initialId, ...result.added], - }; - }); - } -} - -export async function syncAccount(userId, userKey, id, acctId, bankId) { - // TODO: Handle the case where transactions exist in the future - // (that will make start date after end date) - const latestTransaction = await db.first( - 'SELECT * FROM v_transactions WHERE account = ? ORDER BY date DESC LIMIT 1', - [id], - ); - - if (latestTransaction) { - const startingTransaction = await db.first( - 'SELECT date FROM v_transactions WHERE account = ? ORDER BY date ASC LIMIT 1', - [id], - ); - const startingDate = db.fromDateRepr(startingTransaction.date); - // assert(startingTransaction) - - // Get all transactions since the latest transaction, plus any 5 - // days before the latest transaction. This gives us a chance to - // resolve any transactions that were entered manually. - // - // TODO: What this really should do is query the last imported_id - // and since then - let date = monthUtils.subDays(db.fromDateRepr(latestTransaction.date), 31); - - // Never download transactions before the starting date. This was - // when the account was added to the system. - if (date < startingDate) { - date = startingDate; - } - - const { transactions: originalTransactions, accountBalance } = - await downloadTransactions(userId, userKey, acctId, bankId, date); - if (originalTransactions.length === 0) { - return { added: [], updated: [] }; - } - - const transactions = originalTransactions.map(trans => ({ - ...trans, - account: id, - })); - - return runMutator(async () => { - const result = await reconcileTransactions(id, transactions); - await updateAccountBalance(id, accountBalance); - return result; - }); - } else { - const acctRow = await db.select('accounts', id); - - // Otherwise, download transaction for the last few days if it's an - // on-budget account, or for the past 30 days if off-budget - const startingDay = monthUtils.subDays( - monthUtils.currentDay(), - acctRow.offbudget === 0 ? 1 : 30, - ); - - const { transactions } = await downloadTransactions( - userId, - userKey, - acctId, - bankId, - dateFns.format(dateFns.parseISO(startingDay), 'yyyy-MM-dd'), - ); - - // We need to add a transaction that represents the starting - // balance for everything to balance out. In order to get balance - // before the first imported transaction, we need to get the - // current balance from the accounts table and subtract all the - // imported transactions. - const currentBalance = acctRow.balance_current; - - const previousBalance = transactions.reduce((total, trans) => { - return total - trans.amount; - }, currentBalance); - - const oldestDate = - transactions.length > 0 - ? transactions[transactions.length - 1].date - : monthUtils.currentDay(); - - const payee = await getStartingBalancePayee(); - - return runMutator(async () => { - const initialId = await db.insertTransaction({ - account: id, - amount: previousBalance, - category: acctRow.offbudget === 0 ? payee.category : null, - payee: payee.id, - date: oldestDate, - cleared: true, - starting_balance_flag: true, - }); - - const result = await reconcileTransactions(id, transactions); + const result = await reconcileTransactions( + id, + transactions, + isExternalAccount + ? normalizeExternalTransactions + : normalizeTransactions, + ); return { ...result, added: [initialId, ...result.added], diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 0cc70892db3..3342871763e 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -694,7 +694,7 @@ handlers['gocardless-accounts-link'] = async function ({ }); } - await bankSync.syncExternalAccount( + await bankSync.syncAccount( undefined, undefined, id, @@ -752,7 +752,7 @@ handlers['simplefin-accounts-link'] = async function ({ }); } - await bankSync.syncExternalAccount( + await bankSync.syncAccount( undefined, undefined, id, @@ -1288,7 +1288,7 @@ handlers['gocardless-accounts-sync'] = async function ({ id }) { if (acct.bankId) { try { console.group('Bank Sync operation'); - const res = await bankSync.syncExternalAccount( + const res = await bankSync.syncAccount( userId, userKey, acct.id, From 54d2ee84cf284fdf255a93cb9ee65db8591d8094 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Sun, 31 Mar 2024 14:00:01 +0100 Subject: [PATCH 2/9] Release notes --- upcoming-release-notes/2534.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/2534.md diff --git a/upcoming-release-notes/2534.md b/upcoming-release-notes/2534.md new file mode 100644 index 00000000000..6101f9b69b8 --- /dev/null +++ b/upcoming-release-notes/2534.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Removing code duplication in bank-sync logic From 967c837007b0d36fad77cc739bf9ba6f57022e0d Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Sun, 31 Mar 2024 14:01:26 +0100 Subject: [PATCH 3/9] Remove unused TransactionEntity import --- packages/loot-core/src/server/accounts/sync.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index 8a3251272c3..e87f809e605 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -19,7 +19,6 @@ import { getStartingBalancePayee } from './payees'; import { title } from './title'; import { runRules } from './transaction-rules'; import { batchUpdateTransactions } from './transactions'; -import { TransactionEntity } from 'loot-core/types/models'; // Plaid article about API options: // https://support.plaid.com/customer/en/portal/articles/2612155-transactions-returned-per-request From 0edcd096d18c436e29d95ad2bb19131f317d743f Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 2 Apr 2024 19:27:58 +0100 Subject: [PATCH 4/9] Feedback: update date only for internal accounts --- packages/loot-core/src/server/accounts/sync.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index e87f809e605..b7899f8449f 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -440,7 +440,7 @@ async function createNewPayees(payeesToCreate, addsAndUpdates) { export async function reconcileTransactions( acctId, transactions, - transactionNormalization = normalizeTransactions, + isExternalAccount: boolean = false, ) { console.log('Performing transaction reconciliation'); @@ -448,6 +448,10 @@ export async function reconcileTransactions( const updated = []; const added = []; + const transactionNormalization = isExternalAccount + ? normalizeExternalTransactions + : normalizeTransactions; + const { normalized, payeesToCreate } = await transactionNormalization( transactions, acctId, @@ -579,7 +583,7 @@ export async function reconcileTransactions( // Update the transaction const updates = { - date: trans.date, + ...(isExternalAccount ? {} : { date: trans.date }), imported_id: trans.imported_id || null, payee: existing.payee || trans.payee || null, category: existing.category || trans.category || null, @@ -785,9 +789,7 @@ export async function syncAccount( const result = await reconcileTransactions( id, transactions, - isExternalAccount - ? normalizeExternalTransactions - : normalizeTransactions, + isExternalAccount, ); await updateAccountBalance(id, accountBalance); return result; @@ -879,9 +881,7 @@ export async function syncAccount( const result = await reconcileTransactions( id, transactions, - isExternalAccount - ? normalizeExternalTransactions - : normalizeTransactions, + isExternalAccount, ); return { ...result, From df35270b438f7126561ce53a69cb18141e942d07 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 2 Apr 2024 19:29:16 +0100 Subject: [PATCH 5/9] Patch linter --- packages/loot-core/src/server/accounts/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index b7899f8449f..d1ff9fa3385 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -440,7 +440,7 @@ async function createNewPayees(payeesToCreate, addsAndUpdates) { export async function reconcileTransactions( acctId, transactions, - isExternalAccount: boolean = false, + isExternalAccount = false, ) { console.log('Performing transaction reconciliation'); From c8a741a9543898e36eaa17b498cc0a03a714dd75 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 3 Apr 2024 17:39:11 +0100 Subject: [PATCH 6/9] Rename external -> bankSync and a bit of cleanup --- .../loot-core/src/server/accounts/sync.ts | 94 +++++-------------- packages/loot-core/src/server/main.ts | 48 ---------- .../loot-core/src/types/server-handlers.d.ts | 7 -- 3 files changed, 25 insertions(+), 124 deletions(-) diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index d1ff9fa3385..3c8c44639fb 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -328,7 +328,7 @@ async function normalizeTransactions( return { normalized, payeesToCreate }; } -async function normalizeExternalTransactions(transactions, acctId) { +async function normalizeBankSyncTransactions(transactions, acctId) { const payeesToCreate = new Map(); const normalized = []; @@ -440,7 +440,7 @@ async function createNewPayees(payeesToCreate, addsAndUpdates) { export async function reconcileTransactions( acctId, transactions, - isExternalAccount = false, + isBankSyncAccount = false, ) { console.log('Performing transaction reconciliation'); @@ -448,8 +448,8 @@ export async function reconcileTransactions( const updated = []; const added = []; - const transactionNormalization = isExternalAccount - ? normalizeExternalTransactions + const transactionNormalization = isBankSyncAccount + ? normalizeBankSyncTransactions : normalizeTransactions; const { normalized, payeesToCreate } = await transactionNormalization( @@ -583,7 +583,7 @@ export async function reconcileTransactions( // Update the transaction const updates = { - ...(isExternalAccount ? {} : { date: trans.date }), + ...(isBankSyncAccount ? {} : { date: trans.date }), imported_id: trans.imported_id || null, payee: existing.payee || trans.payee || null, category: existing.category || trans.category || null, @@ -712,9 +712,6 @@ export async function syncAccount( ); const acctRow = await db.select('accounts', id); - const isExternalAccount = - acctRow.account_sync_source === 'simpleFin' || - acctRow.account_sync_source === 'goCardless'; if (latestTransaction) { const startingTransaction = await db.first( @@ -786,76 +783,39 @@ export async function syncAccount( })); return runMutator(async () => { - const result = await reconcileTransactions( - id, - transactions, - isExternalAccount, - ); + const result = await reconcileTransactions(id, transactions, true); await updateAccountBalance(id, accountBalance); return result; }); } else { - let balanceToUse: number; - let transactions; - - if (isExternalAccount) { - let download; - - // Otherwise, download transaction for the past 90 days - const startingDay = monthUtils.subDays(monthUtils.currentDay(), 90); + let download; - if (acctRow.account_sync_source === 'simpleFin') { - download = await downloadSimpleFinTransactions(acctId, startingDay); - } else if (acctRow.account_sync_source === 'goCardless') { - download = await downloadGoCardlessTransactions( - userId, - userKey, - acctId, - bankId, - startingDay, - ); - } + // Otherwise, download transaction for the past 90 days + const startingDay = monthUtils.subDays(monthUtils.currentDay(), 90); - transactions = download.transactions; - balanceToUse = download.startingBalance; - - if (acctRow.account_sync_source === 'simpleFin') { - const currentBalance = download.startingBalance; - const previousBalance = transactions.reduce((total, trans) => { - return ( - total - parseInt(trans.transactionAmount.amount.replace('.', '')) - ); - }, currentBalance); - balanceToUse = previousBalance; - } - } else { - // Otherwise, download transaction for the last few days if it's an - // on-budget account, or for the past 30 days if off-budget - const startingDay = monthUtils.subDays( - monthUtils.currentDay(), - acctRow.offbudget === 0 ? 1 : 30, - ); - - const download = await downloadTransactions( + if (acctRow.account_sync_source === 'simpleFin') { + download = await downloadSimpleFinTransactions(acctId, startingDay); + } else if (acctRow.account_sync_source === 'goCardless') { + download = await downloadGoCardlessTransactions( userId, userKey, acctId, bankId, - dateFns.format(dateFns.parseISO(startingDay), 'yyyy-MM-dd'), + startingDay, ); + } - transactions = download.transactions; - - // We need to add a transaction that represents the starting - // balance for everything to balance out. In order to get balance - // before the first imported transaction, we need to get the - // current balance from the accounts table and subtract all the - // imported transactions. - const currentBalance = acctRow.balance_current; + const transactions = download.transactions; + let balanceToUse = download.startingBalance; - balanceToUse = transactions.reduce((total, trans) => { - return total - trans.amount; + if (acctRow.account_sync_source === 'simpleFin') { + const currentBalance = download.startingBalance; + const previousBalance = transactions.reduce((total, trans) => { + return ( + total - parseInt(trans.transactionAmount.amount.replace('.', '')) + ); }, currentBalance); + balanceToUse = previousBalance; } const oldestTransaction = transactions[transactions.length - 1]; @@ -878,11 +838,7 @@ export async function syncAccount( starting_balance_flag: true, }); - const result = await reconcileTransactions( - id, - transactions, - isExternalAccount, - ); + const result = await reconcileTransactions(id, transactions, true); return { ...result, added: [initialId, ...result.added], diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 3342871763e..f648a0b19a1 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -610,54 +610,6 @@ handlers['account-properties'] = async function ({ id }) { return { balance: balance || 0, numTransactions: count }; }; -handlers['accounts-link'] = async function ({ - institution, - publicToken, - accountId, - upgradingId, -}) { - const bankId = await link.handoffPublicToken(institution, publicToken); - - const [[, userId], [, userKey]] = await asyncStorage.multiGet([ - 'user-id', - 'user-key', - ]); - - // Get all the available accounts and find the selected one - const accounts = await bankSync.getGoCardlessAccounts( - userId, - userKey, - bankId, - ); - const account = accounts.find(acct => acct.account_id === accountId); - - await db.update('accounts', { - id: upgradingId, - account_id: account.account_id, - official_name: account.official_name, - balance_current: amountToInteger(account.balances.current), - balance_available: amountToInteger(account.balances.available), - balance_limit: amountToInteger(account.balances.limit), - mask: account.mask, - bank: bankId, - }); - - await bankSync.syncAccount( - userId, - userKey, - upgradingId, - account.account_id, - bankId, - ); - - connection.send('sync-event', { - type: 'success', - tables: ['transactions'], - }); - - return 'ok'; -}; - handlers['gocardless-accounts-link'] = async function ({ requisitionId, account, diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index eff46e2f601..7704b7582a8 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -149,13 +149,6 @@ export interface ServerHandlers { id; }) => Promise<{ balance: number; numTransactions: number }>; - 'accounts-link': (arg: { - institution; - publicToken; - accountId; - upgradingId; - }) => Promise<'ok'>; - 'gocardless-accounts-link': (arg: { requisitionId; account; From 25d278d5bd07f38cf61bf7972c2c833f42638f37 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 3 Apr 2024 18:02:02 +0100 Subject: [PATCH 7/9] Remove plaid sync tests --- .../accounts/__snapshots__/sync.test.ts.snap | 2503 ----------------- .../src/server/accounts/sync.test.ts | 247 +- 2 files changed, 1 insertion(+), 2749 deletions(-) diff --git a/packages/loot-core/src/server/accounts/__snapshots__/sync.test.ts.snap b/packages/loot-core/src/server/accounts/__snapshots__/sync.test.ts.snap index 53dec388648..0602bdc5574 100644 --- a/packages/loot-core/src/server/accounts/__snapshots__/sync.test.ts.snap +++ b/packages/loot-core/src/server/accounts/__snapshots__/sync.test.ts.snap @@ -1,2508 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Account sync import never matches existing with financial ids 1`] = ` -Array [ - Object { - "account": "one", - "amount": 8105, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id18", - "imported_id": "5629ec0c-e559-49f4-9105-91d7b8b8738a", - "imported_payee": "Transaction 90", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id5", - "payee_name": "Transaction 90", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -473, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id22", - "imported_id": "45a2ad98-acbc-4120-a856-dec0839fa73c", - "imported_payee": "Transaction 54", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id9", - "payee_name": "Transaction 54", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1462, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id20", - "imported_id": "8791ca7e-5c19-4f23-bdf9-f60ee2a90081", - "imported_payee": "Transaction 79", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id7", - "payee_name": "Transaction 79", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4207, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id19", - "imported_id": "254ad9b3-23aa-4f70-b617-e126e054cc0e", - "imported_payee": "Transaction 3", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id6", - "payee_name": "Transaction 3", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5093, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id21", - "imported_id": "8e0ddc45-d545-4504-bed7-23c417a90f90", - "imported_payee": "Transaction 51", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id8", - "payee_name": "Transaction 51", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5938, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id23", - "imported_id": "00ab7e01-73f7-4da0-98f3-233f1dcc5b3e", - "imported_payee": "Transaction 68", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id10", - "payee_name": "Transaction 68", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 35884, - "category": "id2", - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id4", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id3", - "payee_name": "Starting Balance", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 1, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1105, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id25", - "imported_id": "6a764abf-69f4-47a6-94e1-638c7df0e245", - "imported_payee": "Transaction 64", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id12", - "payee_name": "Transaction 64", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3200, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id24", - "imported_id": "1b25b9df-8aa4-47c8-9792-75f4c38e351f", - "imported_payee": "Transaction 11", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id11", - "payee_name": "Transaction 11", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3342, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id28", - "imported_id": "f4b34ee3-6d5b-4625-bffc-057a72270140", - "imported_payee": "Transaction 48", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id15", - "payee_name": "Transaction 48", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3984, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id26", - "imported_id": "768b6d36-0ad7-47fb-a631-145170adc0b9", - "imported_payee": "Transaction 107", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id13", - "payee_name": "Transaction 107", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4524, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id30", - "imported_id": "f4e3a3f2-c20f-4667-8df6-6530bf5f4cb0", - "imported_payee": "Transaction 56", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id17", - "payee_name": "Transaction 56", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4881, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id27", - "imported_id": "ceb53daf-7318-4aee-a562-19a45654eaa7", - "imported_payee": "Transaction 28", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id14", - "payee_name": "Transaction 28", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5541, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id29", - "imported_id": "af2d6eba-f75e-4ee0-a9ad-5d9f8264797b", - "imported_payee": "Transaction 114", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id16", - "payee_name": "Transaction 114", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, -] -`; - -exports[`Account sync import never matches existing with financial ids 2`] = ` -"Snapshot Diff: -- First value -+ Second value - -@@ -68,10 +68,56 @@ - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { - \\"account\\": \\"one\\", -+ \\"amount\\": -2947, -+ \\"category\\": null, -+ \\"cleared\\": 1, -+ \\"date\\": 20171015, -+ \\"error\\": null, -+ \\"id\\": \\"one\\", -+ \\"imported_id\\": \\"trans1\\", -+ \\"imported_payee\\": null, -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"id31\\", -+ \\"payee_name\\": \\"foo\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": null, -+ }, -+ Object { -+ \\"account\\": \\"one\\", -+ \\"amount\\": -2947, -+ \\"category\\": null, -+ \\"cleared\\": 1, -+ \\"date\\": 20171015, -+ \\"error\\": null, -+ \\"id\\": \\"two\\", -+ \\"imported_id\\": \\"trans2\\", -+ \\"imported_payee\\": null, -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"id32\\", -+ \\"payee_name\\": \\"bar\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": null, -+ }, -+ Object { -+ \\"account\\": \\"one\\", - \\"amount\\": -4207, - \\"category\\": null, - \\"cleared\\": 1, - \\"date\\": 20171015, - \\"error\\": null," -`; - -exports[`Account sync import never matches existing with financial ids 3`] = ` -"Snapshot Diff: -- First value -+ Second value - -@@ -1,114 +1,160 @@ - Array [ - Object { - \\"account\\": \\"one\\", -- \\"amount\\": 8105, -+ \\"amount\\": -1865, -+ \\"category\\": null, -+ \\"cleared\\": 1, -+ \\"date\\": 20171017, -+ \\"error\\": null, -+ \\"id\\": \\"id36\\", -+ \\"imported_id\\": \\"622f7b61-a6be-4ce5-bd2f-50eb14c12f42\\", -+ \\"imported_payee\\": \\"Transaction 53\\", -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"id34\\", -+ \\"payee_name\\": \\"Transaction 53\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": null, -+ }, -+ Object { -+ \\"account\\": \\"one\\", -+ \\"amount\\": -2947, - \\"category\\": null, - \\"cleared\\": 1, -- \\"date\\": 20171015, -+ \\"date\\": 20171017, - \\"error\\": null, -- \\"id\\": \\"id18\\", -- \\"imported_id\\": \\"5629ec0c-e559-49f4-9105-91d7b8b8738a\\", -- \\"imported_payee\\": \\"Transaction 90\\", -+ \\"id\\": \\"one\\", -+ \\"imported_id\\": \\"3591ad03-b705-42e0-945d-402a70371c49\\", -+ \\"imported_payee\\": \\"foo\\", - \\"is_child\\": 0, - \\"is_parent\\": 0, - \\"notes\\": null, - \\"parent_id\\": null, -- \\"payee\\": \\"id5\\", -- \\"payee_name\\": \\"Transaction 90\\", -+ \\"payee\\": \\"id31\\", -+ \\"payee_name\\": \\"foo\\", - \\"reconciled\\": 0, - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { - \\"account\\": \\"one\\", -- \\"amount\\": -473, -+ \\"amount\\": -2947, - \\"category\\": null, - \\"cleared\\": 1, -- \\"date\\": 20171015, -+ \\"date\\": 20171017, - \\"error\\": null, -- \\"id\\": \\"id22\\", -- \\"imported_id\\": \\"45a2ad98-acbc-4120-a856-dec0839fa73c\\", -- \\"imported_payee\\": \\"Transaction 54\\", -+ \\"id\\": \\"two\\", -+ \\"imported_id\\": \\"01a3a594-a381-49d1-bcf8-331a3c410900\\", -+ \\"imported_payee\\": \\"bar\\", - \\"is_child\\": 0, - \\"is_parent\\": 0, - \\"notes\\": null, - \\"parent_id\\": null, -- \\"payee\\": \\"id9\\", -- \\"payee_name\\": \\"Transaction 54\\", -+ \\"payee\\": \\"id32\\", -+ \\"payee_name\\": \\"bar\\", - \\"reconciled\\": 0, - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { - \\"account\\": \\"one\\", -- \\"amount\\": -1462, -+ \\"amount\\": -2407, -+ \\"category\\": null, -+ \\"cleared\\": 1, -+ \\"date\\": 20171016, -+ \\"error\\": null, -+ \\"id\\": \\"id35\\", -+ \\"imported_id\\": \\"753911ce-7b09-4cb3-8447-ac6eb74e727e\\", -+ \\"imported_payee\\": \\"Transaction 32\\", -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"id33\\", -+ \\"payee_name\\": \\"Transaction 32\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": null, -+ }, -+ Object { -+ \\"account\\": \\"one\\", -+ \\"amount\\": 8105, - \\"category\\": null, - \\"cleared\\": 1, - \\"date\\": 20171015, - \\"error\\": null, -- \\"id\\": \\"id20\\", -- \\"imported_id\\": \\"8791ca7e-5c19-4f23-bdf9-f60ee2a90081\\", -- \\"imported_payee\\": \\"Transaction 79\\", -+ \\"id\\": \\"id18\\", -+ \\"imported_id\\": \\"5629ec0c-e559-49f4-9105-91d7b8b8738a\\", -+ \\"imported_payee\\": \\"Transaction 90\\", - \\"is_child\\": 0, - \\"is_parent\\": 0, - \\"notes\\": null, - \\"parent_id\\": null, -- \\"payee\\": \\"id7\\", -- \\"payee_name\\": \\"Transaction 79\\", -+ \\"payee\\": \\"id5\\", -+ \\"payee_name\\": \\"Transaction 90\\", - \\"reconciled\\": 0, - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { - \\"account\\": \\"one\\", -- \\"amount\\": -2947, -+ \\"amount\\": -473, - \\"category\\": null, - \\"cleared\\": 1, - \\"date\\": 20171015, - \\"error\\": null, -- \\"id\\": \\"one\\", -- \\"imported_id\\": \\"trans1\\", -- \\"imported_payee\\": null, -+ \\"id\\": \\"id22\\", -+ \\"imported_id\\": \\"45a2ad98-acbc-4120-a856-dec0839fa73c\\", -+ \\"imported_payee\\": \\"Transaction 54\\", - \\"is_child\\": 0, - \\"is_parent\\": 0, - \\"notes\\": null, - \\"parent_id\\": null, -- \\"payee\\": \\"id31\\", -- \\"payee_name\\": \\"foo\\", -+ \\"payee\\": \\"id9\\", -+ \\"payee_name\\": \\"Transaction 54\\", - \\"reconciled\\": 0, - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { - \\"account\\": \\"one\\", -- \\"amount\\": -2947, -+ \\"amount\\": -1462, - \\"category\\": null, - \\"cleared\\": 1, - \\"date\\": 20171015, - \\"error\\": null, -- \\"id\\": \\"two\\", -- \\"imported_id\\": \\"trans2\\", -- \\"imported_payee\\": null, -+ \\"id\\": \\"id20\\", -+ \\"imported_id\\": \\"8791ca7e-5c19-4f23-bdf9-f60ee2a90081\\", -+ \\"imported_payee\\": \\"Transaction 79\\", - \\"is_child\\": 0, - \\"is_parent\\": 0, - \\"notes\\": null, - \\"parent_id\\": null, -- \\"payee\\": \\"id32\\", -- \\"payee_name\\": \\"bar\\", -+ \\"payee\\": \\"id7\\", -+ \\"payee_name\\": \\"Transaction 79\\", - \\"reconciled\\": 0, - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0," -`; - -exports[`Account sync import updates transfers when matched 1`] = ` -Array [ - Object { - "account": "one", - "amount": 8105, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id18", - "imported_id": "5629ec0c-e559-49f4-9105-91d7b8b8738a", - "imported_payee": "Transaction 90", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id5", - "payee_name": "Transaction 90", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -473, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id22", - "imported_id": "45a2ad98-acbc-4120-a856-dec0839fa73c", - "imported_payee": "Transaction 54", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id9", - "payee_name": "Transaction 54", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1462, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id20", - "imported_id": "8791ca7e-5c19-4f23-bdf9-f60ee2a90081", - "imported_payee": "Transaction 79", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id7", - "payee_name": "Transaction 79", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4207, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id19", - "imported_id": "254ad9b3-23aa-4f70-b617-e126e054cc0e", - "imported_payee": "Transaction 3", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id6", - "payee_name": "Transaction 3", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5093, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id21", - "imported_id": "8e0ddc45-d545-4504-bed7-23c417a90f90", - "imported_payee": "Transaction 51", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id8", - "payee_name": "Transaction 51", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5938, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id23", - "imported_id": "00ab7e01-73f7-4da0-98f3-233f1dcc5b3e", - "imported_payee": "Transaction 68", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id10", - "payee_name": "Transaction 68", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 35884, - "category": "id2", - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id4", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id3", - "payee_name": "Starting Balance", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 1, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1105, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id25", - "imported_id": "6a764abf-69f4-47a6-94e1-638c7df0e245", - "imported_payee": "Transaction 64", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id12", - "payee_name": "Transaction 64", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3200, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id24", - "imported_id": "1b25b9df-8aa4-47c8-9792-75f4c38e351f", - "imported_payee": "Transaction 11", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id11", - "payee_name": "Transaction 11", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3342, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id28", - "imported_id": "f4b34ee3-6d5b-4625-bffc-057a72270140", - "imported_payee": "Transaction 48", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id15", - "payee_name": "Transaction 48", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3984, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id26", - "imported_id": "768b6d36-0ad7-47fb-a631-145170adc0b9", - "imported_payee": "Transaction 107", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id13", - "payee_name": "Transaction 107", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4524, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id30", - "imported_id": "f4e3a3f2-c20f-4667-8df6-6530bf5f4cb0", - "imported_payee": "Transaction 56", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id17", - "payee_name": "Transaction 56", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4881, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id27", - "imported_id": "ceb53daf-7318-4aee-a562-19a45654eaa7", - "imported_payee": "Transaction 28", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id14", - "payee_name": "Transaction 28", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5541, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id29", - "imported_id": "af2d6eba-f75e-4ee0-a9ad-5d9f8264797b", - "imported_payee": "Transaction 114", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id16", - "payee_name": "Transaction 114", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, -] -`; - -exports[`Account sync import updates transfers when matched 2`] = ` -"Snapshot Diff: -- First value -+ Second value - -@@ -21,10 +21,33 @@ - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, - }, - Object { -+ \\"account\\": \\"two\\", -+ \\"amount\\": 2948, -+ \\"category\\": null, -+ \\"cleared\\": 1, -+ \\"date\\": 20171015, -+ \\"error\\": null, -+ \\"id\\": \\"one\\", -+ \\"imported_id\\": null, -+ \\"imported_payee\\": null, -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"transfer-one\\", -+ \\"payee_name\\": \\"\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": \\"id31\\", -+ }, -+ Object { - \\"account\\": \\"one\\", - \\"amount\\": -473, - \\"category\\": null, - \\"cleared\\": 1, - \\"date\\": 20171015, -@@ -65,10 +88,33 @@ - \\"schedule\\": null, - \\"sort_order\\": 123456789, - \\"starting_balance_flag\\": 0, - \\"tombstone\\": 0, - \\"transfer_id\\": null, -+ }, -+ Object { -+ \\"account\\": \\"one\\", -+ \\"amount\\": -2948, -+ \\"category\\": null, -+ \\"cleared\\": 0, -+ \\"date\\": 20171015, -+ \\"error\\": null, -+ \\"id\\": \\"id31\\", -+ \\"imported_id\\": null, -+ \\"imported_payee\\": null, -+ \\"is_child\\": 0, -+ \\"is_parent\\": 0, -+ \\"notes\\": null, -+ \\"parent_id\\": null, -+ \\"payee\\": \\"transfer-two\\", -+ \\"payee_name\\": \\"\\", -+ \\"reconciled\\": 0, -+ \\"schedule\\": null, -+ \\"sort_order\\": 123456789, -+ \\"starting_balance_flag\\": 0, -+ \\"tombstone\\": 0, -+ \\"transfer_id\\": \\"one\\", - }, - Object { - \\"account\\": \\"one\\", - \\"amount\\": -4207, - \\"category\\": null," -`; - -exports[`Account sync import updates transfers when matched 3`] = ` -Array [ - Object { - "account": "two", - "amount": 2948, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "one", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "transfer-one", - "payee_name": "", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": "id31", - }, - Object { - "account": "one", - "amount": -1865, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id38", - "imported_id": "622f7b61-a6be-4ce5-bd2f-50eb14c12f42", - "imported_payee": "Transaction 53", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id35", - "payee_name": "Transaction 53", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2948, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id31", - "imported_id": "3591ad03-b705-42e0-945d-402a70371c49", - "imported_payee": "#001 fenn st Macy’s 33333 EMX", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "transfer-two", - "payee_name": "", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": "one", - }, - Object { - "account": "one", - "amount": -4911, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id37", - "imported_id": "01a3a594-a381-49d1-bcf8-331a3c410900", - "imported_payee": "Transaction 78", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id34", - "payee_name": "Transaction 78", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2407, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id36", - "imported_id": "753911ce-7b09-4cb3-8447-ac6eb74e727e", - "imported_payee": "Transaction 32", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id32", - "payee_name": "Transaction 32", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 8105, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id18", - "imported_id": "5629ec0c-e559-49f4-9105-91d7b8b8738a", - "imported_payee": "Transaction 90", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id5", - "payee_name": "Transaction 90", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -473, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id22", - "imported_id": "45a2ad98-acbc-4120-a856-dec0839fa73c", - "imported_payee": "Transaction 54", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id9", - "payee_name": "Transaction 54", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1462, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id20", - "imported_id": "8791ca7e-5c19-4f23-bdf9-f60ee2a90081", - "imported_payee": "Transaction 79", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id7", - "payee_name": "Transaction 79", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4207, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id19", - "imported_id": "254ad9b3-23aa-4f70-b617-e126e054cc0e", - "imported_payee": "Transaction 3", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id6", - "payee_name": "Transaction 3", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5093, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id21", - "imported_id": "8e0ddc45-d545-4504-bed7-23c417a90f90", - "imported_payee": "Transaction 51", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id8", - "payee_name": "Transaction 51", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5938, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id23", - "imported_id": "00ab7e01-73f7-4da0-98f3-233f1dcc5b3e", - "imported_payee": "Transaction 68", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id10", - "payee_name": "Transaction 68", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 35884, - "category": "id2", - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id4", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id3", - "payee_name": "Starting Balance", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 1, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1105, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id25", - "imported_id": "6a764abf-69f4-47a6-94e1-638c7df0e245", - "imported_payee": "Transaction 64", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id12", - "payee_name": "Transaction 64", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3200, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id24", - "imported_id": "1b25b9df-8aa4-47c8-9792-75f4c38e351f", - "imported_payee": "Transaction 11", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id11", - "payee_name": "Transaction 11", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3342, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id28", - "imported_id": "f4b34ee3-6d5b-4625-bffc-057a72270140", - "imported_payee": "Transaction 48", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id15", - "payee_name": "Transaction 48", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3984, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id26", - "imported_id": "768b6d36-0ad7-47fb-a631-145170adc0b9", - "imported_payee": "Transaction 107", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id13", - "payee_name": "Transaction 107", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4524, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id30", - "imported_id": "f4e3a3f2-c20f-4667-8df6-6530bf5f4cb0", - "imported_payee": "Transaction 56", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id17", - "payee_name": "Transaction 56", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4881, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id27", - "imported_id": "ceb53daf-7318-4aee-a562-19a45654eaa7", - "imported_payee": "Transaction 28", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id14", - "payee_name": "Transaction 28", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5541, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id29", - "imported_id": "af2d6eba-f75e-4ee0-a9ad-5d9f8264797b", - "imported_payee": "Transaction 114", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id16", - "payee_name": "Transaction 114", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, -] -`; - -exports[`Account sync imports transactions for current day and adds latest 1`] = ` -Array [ - Object { - "account": "one", - "amount": 8105, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id18", - "imported_id": "5629ec0c-e559-49f4-9105-91d7b8b8738a", - "imported_payee": "Transaction 90", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id5", - "payee_name": "Transaction 90", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -473, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id22", - "imported_id": "45a2ad98-acbc-4120-a856-dec0839fa73c", - "imported_payee": "Transaction 54", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id9", - "payee_name": "Transaction 54", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1462, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id20", - "imported_id": "8791ca7e-5c19-4f23-bdf9-f60ee2a90081", - "imported_payee": "Transaction 79", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id7", - "payee_name": "Transaction 79", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4207, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id19", - "imported_id": "254ad9b3-23aa-4f70-b617-e126e054cc0e", - "imported_payee": "Transaction 3", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id6", - "payee_name": "Transaction 3", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5093, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id21", - "imported_id": "8e0ddc45-d545-4504-bed7-23c417a90f90", - "imported_payee": "Transaction 51", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id8", - "payee_name": "Transaction 51", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5938, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id23", - "imported_id": "00ab7e01-73f7-4da0-98f3-233f1dcc5b3e", - "imported_payee": "Transaction 68", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id10", - "payee_name": "Transaction 68", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 35884, - "category": "id2", - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id4", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id3", - "payee_name": "Starting Balance", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 1, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1105, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id25", - "imported_id": "6a764abf-69f4-47a6-94e1-638c7df0e245", - "imported_payee": "Transaction 64", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id12", - "payee_name": "Transaction 64", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3200, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id24", - "imported_id": "1b25b9df-8aa4-47c8-9792-75f4c38e351f", - "imported_payee": "Transaction 11", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id11", - "payee_name": "Transaction 11", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3342, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id28", - "imported_id": "f4b34ee3-6d5b-4625-bffc-057a72270140", - "imported_payee": "Transaction 48", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id15", - "payee_name": "Transaction 48", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3984, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id26", - "imported_id": "768b6d36-0ad7-47fb-a631-145170adc0b9", - "imported_payee": "Transaction 107", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id13", - "payee_name": "Transaction 107", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4524, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id30", - "imported_id": "f4e3a3f2-c20f-4667-8df6-6530bf5f4cb0", - "imported_payee": "Transaction 56", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id17", - "payee_name": "Transaction 56", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4881, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id27", - "imported_id": "ceb53daf-7318-4aee-a562-19a45654eaa7", - "imported_payee": "Transaction 28", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id14", - "payee_name": "Transaction 28", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5541, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id29", - "imported_id": "af2d6eba-f75e-4ee0-a9ad-5d9f8264797b", - "imported_payee": "Transaction 114", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id16", - "payee_name": "Transaction 114", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, -] -`; - -exports[`Account sync imports transactions for current day and adds latest 2`] = ` -Array [ - Object { - "account": "one", - "amount": -434, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id58", - "imported_id": "b89c446a-c425-4ce2-b3b9-251b3fbe1568", - "imported_payee": "Transaction 118", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id40", - "payee_name": "Transaction 118", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1865, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id51", - "imported_id": "622f7b61-a6be-4ce5-bd2f-50eb14c12f42", - "imported_payee": "Transaction 53", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id33", - "payee_name": "Transaction 53", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2147, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id49", - "imported_id": "3591ad03-b705-42e0-945d-402a70371c49", - "imported_payee": "Transaction 13", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id31", - "payee_name": "Transaction 13", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2947, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id54", - "imported_id": "d5ec5f37-2ac1-4b8d-b020-8eda39d4f61a", - "imported_payee": "Transaction 96", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id36", - "payee_name": "Transaction 96", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2947, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id55", - "imported_id": "d5ec5f37-2ac1-4b8d-b020-8eda39d4f61a2", - "imported_payee": "Transaction Bazillion", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id37", - "payee_name": "Transaction Bazillion", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3170, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id59", - "imported_id": "aa69713d-f3ea-4914-a4fd-f04fcad242ba", - "imported_payee": "Transaction 89", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id41", - "payee_name": "Transaction 89", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3339, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id57", - "imported_id": "ade5fc3c-cce6-435e-9634-9e03c7876373", - "imported_payee": "Transaction 65", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id39", - "payee_name": "Transaction 65", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4911, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id50", - "imported_id": "01a3a594-a381-49d1-bcf8-331a3c410900", - "imported_payee": "Transaction 78", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id32", - "payee_name": "Transaction 78", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5391, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id56", - "imported_id": "8083e915-7eb0-4226-9514-2574e39ed333", - "imported_payee": "Transaction 49", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id38", - "payee_name": "Transaction 49", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5709, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id53", - "imported_id": "7f438a75-1ba3-40a0-a25b-689c8e720e31", - "imported_payee": "Transaction 100", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id35", - "payee_name": "Transaction 100", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5730, - "category": null, - "cleared": 1, - "date": 20171017, - "error": null, - "id": "id52", - "imported_id": "7b5a9df8-ca05-411b-82f9-4261b2df5b8c", - "imported_payee": "Transaction 115", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id34", - "payee_name": "Transaction 115", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 9675, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id63", - "imported_id": "4a907027-bed2-4f5d-ac67-b78fb14cead9", - "imported_payee": "Transaction 67", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id45", - "payee_name": "Transaction 67", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -2407, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id60", - "imported_id": "753911ce-7b09-4cb3-8447-ac6eb74e727e", - "imported_payee": "Transaction 32", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id42", - "payee_name": "Transaction 32", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3006, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id65", - "imported_id": "14ea70a2-511f-4797-92ae-81772acedf86", - "imported_payee": "Transaction 20", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id47", - "payee_name": "Transaction 20", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3935, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id64", - "imported_id": "5568650a-db53-43ce-9812-2755e8c7ca62", - "imported_payee": "Transaction 14", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id46", - "payee_name": "Transaction 14", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3987, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id62", - "imported_id": "831051b1-5d93-4df8-ab04-dd69615cd001", - "imported_payee": "Transaction 6", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id44", - "payee_name": "Transaction 6", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4837, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id61", - "imported_id": "e1f42f2d-ab97-468c-a859-df571e858896", - "imported_payee": "Transaction 119", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id43", - "payee_name": "Transaction 119", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4936, - "category": null, - "cleared": 1, - "date": 20171016, - "error": null, - "id": "id66", - "imported_id": "86c867c2-6d44-4556-84e7-015226144a7d", - "imported_payee": "Transaction 21", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id48", - "payee_name": "Transaction 21", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 8105, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id18", - "imported_id": "5629ec0c-e559-49f4-9105-91d7b8b8738a", - "imported_payee": "Transaction 90", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id5", - "payee_name": "Transaction 90", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -473, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id22", - "imported_id": "45a2ad98-acbc-4120-a856-dec0839fa73c", - "imported_payee": "Transaction 54", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id9", - "payee_name": "Transaction 54", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1462, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id20", - "imported_id": "8791ca7e-5c19-4f23-bdf9-f60ee2a90081", - "imported_payee": "Transaction 79", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id7", - "payee_name": "Transaction 79", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4207, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id19", - "imported_id": "254ad9b3-23aa-4f70-b617-e126e054cc0e", - "imported_payee": "Transaction 3", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id6", - "payee_name": "Transaction 3", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5093, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id21", - "imported_id": "8e0ddc45-d545-4504-bed7-23c417a90f90", - "imported_payee": "Transaction 51", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id8", - "payee_name": "Transaction 51", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5938, - "category": null, - "cleared": 1, - "date": 20171015, - "error": null, - "id": "id23", - "imported_id": "00ab7e01-73f7-4da0-98f3-233f1dcc5b3e", - "imported_payee": "Transaction 68", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id10", - "payee_name": "Transaction 68", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": 35884, - "category": "id2", - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id4", - "imported_id": null, - "imported_payee": null, - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id3", - "payee_name": "Starting Balance", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 1, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -1105, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id25", - "imported_id": "6a764abf-69f4-47a6-94e1-638c7df0e245", - "imported_payee": "Transaction 64", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id12", - "payee_name": "Transaction 64", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3200, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id24", - "imported_id": "1b25b9df-8aa4-47c8-9792-75f4c38e351f", - "imported_payee": "Transaction 11", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id11", - "payee_name": "Transaction 11", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3342, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id28", - "imported_id": "f4b34ee3-6d5b-4625-bffc-057a72270140", - "imported_payee": "Transaction 48", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id15", - "payee_name": "Transaction 48", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -3984, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id26", - "imported_id": "768b6d36-0ad7-47fb-a631-145170adc0b9", - "imported_payee": "Transaction 107", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id13", - "payee_name": "Transaction 107", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4524, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id30", - "imported_id": "f4e3a3f2-c20f-4667-8df6-6530bf5f4cb0", - "imported_payee": "Transaction 56", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id17", - "payee_name": "Transaction 56", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -4881, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id27", - "imported_id": "ceb53daf-7318-4aee-a562-19a45654eaa7", - "imported_payee": "Transaction 28", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id14", - "payee_name": "Transaction 28", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, - Object { - "account": "one", - "amount": -5541, - "category": null, - "cleared": 1, - "date": 20171014, - "error": null, - "id": "id29", - "imported_id": "af2d6eba-f75e-4ee0-a9ad-5d9f8264797b", - "imported_payee": "Transaction 114", - "is_child": 0, - "is_parent": 0, - "notes": null, - "parent_id": null, - "payee": "id16", - "payee_name": "Transaction 114", - "reconciled": 0, - "schedule": null, - "sort_order": 123456789, - "starting_balance_flag": 0, - "tombstone": 0, - "transfer_id": null, - }, -] -`; - exports[`Account sync reconcile handles transactions with undefined fields 1`] = ` Array [ Object { diff --git a/packages/loot-core/src/server/accounts/sync.test.ts b/packages/loot-core/src/server/accounts/sync.test.ts index e6aaae93613..d05badeceb9 100644 --- a/packages/loot-core/src/server/accounts/sync.test.ts +++ b/packages/loot-core/src/server/accounts/sync.test.ts @@ -8,17 +8,8 @@ import { post } from '../post'; import { getServer } from '../server-config'; import * as mockSyncServer from '../tests/mockSyncServer'; -import { - syncAccount, - reconcileTransactions, - addTransactions, - fromPlaid, -} from './sync'; +import { reconcileTransactions, addTransactions } from './sync'; import { loadRules, insertRule } from './transaction-rules'; -import * as transfer from './transfer'; - -const papaJohns = 'Papa Johns east side'; -const lowes = 'Lowe’s Store'; jest.mock('../../shared/months', () => ({ ...jest.requireActual('../../shared/months'), @@ -136,229 +127,6 @@ describe('Account sync', () => { ); }); - test('reconcile matches single transaction', async () => { - const mockTransactions = prepMockTransactions(); - const { id, account_id } = await prepareDatabase(); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - - // The payee can be anything, all that matters is the amount is the same - const mockTransaction = mockTransactions.find(t => t.date === '2017-10-17'); - mockTransaction.amount = 29.47; - - const payeeId = await db.insertPayee({ name: 'macy' }); - await db.insertTransaction({ - id: 'one', - account: id, - amount: -2947, - date: '2017-10-15', - payee: payeeId, - }); - - const { added, updated } = await reconcileTransactions( - id, - mockTransactions.filter(t => t.date >= '2017-10-15').map(fromPlaid), - ); - - expect(added.length).toBe(3); - expect(updated.length).toBe(1); - - const transactions = await getAllTransactions(); - const transaction = transactions.find(t => t.amount === -2947); - expect(transaction.id).toBe(updated[0]); - - // The payee has not been updated - it's still the payee that the original transaction had - const payees = await getAllPayees(); - expect(payees.length).toBe(18); - expect(transaction.payee).toBe(payeeId); - }); - - test('reconcile matches multiple transactions', async () => { - const mockTransactions = prepMockTransactions(); - const { id, account_id } = await prepareDatabase(); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - - // These should all match, but note that the one with the payee - // `macy` should match with the imported one with the same payee - // name. This should happen even though other transactions with - // the same amount are imported first, i.e. high fidelity matches - // always win - const mocked = mockTransactions.filter(t => t.date === '2017-10-17'); - mocked[0].name = papaJohns; - mocked[0].amount = 29.47; - mocked[1].name = 'Lowe’s Store'; - mocked[1].amount = 29.47; - mocked[2].name = 'macy'; - mocked[2].amount = 29.47; - - // Make sure that it macy is correctly matched from a different - // day first, and then the other two are matched based on amount. - // And it should never match the same transactions twice - await db.insertTransaction({ - id: 'one', - account: id, - amount: -2947, - date: '2017-10-15', - payee: await db.insertPayee({ name: 'papa johns' }), - }); - await db.insertTransaction({ - id: 'two', - account: id, - amount: -2947, - date: '2017-10-17', - payee: await db.insertPayee({ name: 'lowes' }), - }); - await db.insertTransaction({ - id: 'three', - account: id, - amount: -2947, - date: '2017-10-17', - payee: await db.insertPayee({ name: 'macy' }), - }); - - const { added, updated } = await reconcileTransactions( - id, - mockTransactions.filter(t => t.date >= '2017-10-15').map(fromPlaid), - ); - - const transactions = await getAllTransactions(); - expect(updated.length).toBe(3); - expect(added.length).toBe(1); - - expect(transactions.find(t => t.id === 'one').imported_id).toBe( - mocked[1].transaction_id, - ); - expect(transactions.find(t => t.id === 'two').imported_id).toBe( - mocked[0].transaction_id, - ); - expect(transactions.find(t => t.id === 'three').imported_id).toBe( - mocked[2].transaction_id, - ); - }); - - test('reconcile matches multiple transactions (imported_id wins)', async () => { - const mockTransactions = prepMockTransactions(); - const { id, account_id } = await prepareDatabase(); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - - const mocked = mockTransactions.filter(t => t.date === '2017-10-17'); - mocked[0].name = papaJohns; - mocked[0].amount = 29.47; - mocked[1].name = lowes; - mocked[1].amount = 29.47; - mocked[1].transaction_id = 'imported1'; - - // Technically, the amount doesn't even matter. The - // imported_id will always match no matter what - await db.insertTransaction({ - id: 'one', - account: id, - amount: -3000, - date: '2017-10-15', - imported_id: 'imported1', - payee: await db.insertPayee({ name: 'papa johns' }), - }); - await db.insertTransaction({ - id: 'two', - account: id, - amount: -2947, - date: '2017-10-17', - payee: await db.insertPayee({ name: 'lowes' }), - }); - - const { added, updated } = await reconcileTransactions( - id, - mockTransactions.filter(t => t.date >= '2017-10-15').map(fromPlaid), - ); - - const transactions = await getAllTransactions(); - expect(updated).toEqual(['two', 'one']); - expect(added.length).toBe(2); - - // Make sure lowes, which has the imported_id, is the one that - // got matched with the same imported_id - expect(transactions.find(t => t.id === 'one').imported_payee).toBe(lowes); - }); - - test('import never matches existing with financial ids', async () => { - let mockTransactions = prepMockTransactions(); - const { id, account_id } = await prepareDatabase(); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - const differ = expectSnapshotWithDiffer(await getAllTransactions()); - - mockTransactions = mockTransactions.filter(t => t.date === '2017-10-17'); - mockTransactions[0].name = 'foo'; - mockTransactions[0].amount = 29.47; - mockTransactions[1].name = 'bar'; - mockTransactions[1].amount = 29.47; - - // Make sure, no matter what, it never tries to match with an - // existing transaction that already has a financial id - await db.insertTransaction({ - id: 'one', - account: id, - amount: -2947, - date: '2017-10-15', - payee: await db.insertPayee({ name: 'foo' }), - imported_id: 'trans1', - }); - - await db.insertTransaction({ - id: 'two', - account: id, - amount: -2947, - date: '2017-10-15', - payee: await db.insertPayee({ name: 'bar' }), - imported_id: 'trans2', - }); - - differ.expectToMatchDiff(await getAllTransactions()); - - (monthUtils.currentDay as jest.Mock).mockReturnValue('2017-10-17'); - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - - differ.expectToMatchDiff(await getAllTransactions()); - }); - - test('import updates transfers when matched', async () => { - const mockTransactions = prepMockTransactions(); - const { id, account_id } = await prepareDatabase(); - await db.insertAccount({ id: 'two', name: 'two' }); - await db.insertPayee({ - id: 'transfer-two', - name: '', - transfer_acct: 'two', - }); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - const differ = expectSnapshotWithDiffer(await getAllTransactions()); - - const mockTransaction = mockTransactions.find(t => t.date === '2017-10-17'); - mockTransaction.name = '#001 fenn st Macy’s 33333 EMX'; - mockTransaction.amount = 29.48; - - const transactionId = await db.insertTransaction({ - id: 'one', - account: 'two', - amount: 2948, - date: '2017-10-15', - payee: 'transfer-' + id, - }); - await transfer.onInsert(await db.getTransaction(transactionId)); - - differ.expectToMatchDiff(await getAllTransactions()); - - (monthUtils.currentDay as jest.Mock).mockReturnValue('2017-10-17'); - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - - // Don't use `differ.expectToMatchDiff` because there's too many - // changes that look too confusing - expect(await getAllTransactions()).toMatchSnapshot(); - }); - test('reconcile handles transactions with undefined fields', async () => { const { id: acctId } = await prepareDatabase(); @@ -609,17 +377,4 @@ describe('Account sync', () => { 'bakkerij-renamed', ]); }); - - test('imports transactions for current day and adds latest', async () => { - const { id, account_id } = await prepareDatabase(); - - expect((await getAllTransactions()).length).toBe(0); - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - expect(await getAllTransactions()).toMatchSnapshot(); - - (monthUtils.currentDay as jest.Mock).mockReturnValue('2017-10-17'); - - await syncAccount('userId', 'userKey', id, account_id, 'bank'); - expect(await getAllTransactions()).toMatchSnapshot(); - }); }); From d0f0b9237aa7c14f453d204721b7737b9f21e50f Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 3 Apr 2024 18:03:38 +0100 Subject: [PATCH 8/9] Remove more dead code --- .../src/server/accounts/sync.test.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/packages/loot-core/src/server/accounts/sync.test.ts b/packages/loot-core/src/server/accounts/sync.test.ts index d05badeceb9..0c8c9e8e119 100644 --- a/packages/loot-core/src/server/accounts/sync.test.ts +++ b/packages/loot-core/src/server/accounts/sync.test.ts @@ -1,12 +1,9 @@ // @ts-strict-ignore -import snapshotDiff from 'snapshot-diff'; - import * as monthUtils from '../../shared/months'; import * as db from '../db'; import { loadMappings } from '../db/mappings'; import { post } from '../post'; import { getServer } from '../server-config'; -import * as mockSyncServer from '../tests/mockSyncServer'; import { reconcileTransactions, addTransactions } from './sync'; import { loadRules, insertRule } from './transaction-rules'; @@ -18,7 +15,6 @@ jest.mock('../../shared/months', () => ({ })); beforeEach(async () => { - mockSyncServer.reset(); jest.resetAllMocks(); (monthUtils.currentDay as jest.Mock).mockReturnValue('2017-10-15'); (monthUtils.currentMonth as jest.Mock).mockReturnValue('2017-10'); @@ -37,37 +33,6 @@ function getAllTransactions() { ); } -function expectSnapshotWithDiffer(initialValue) { - let currentValue = initialValue; - expect(initialValue).toMatchSnapshot(); - return { - expectToMatchDiff: value => { - expect(snapshotDiff(currentValue, value)).toMatchSnapshot(); - currentValue = value; - }, - }; -} - -function prepMockTransactions() { - let mockTransactions; - mockSyncServer.filterMockData(data => { - const account_id = data.accounts[0].account_id; - const transactions = data.transactions[account_id].filter(t => !t.pending); - - mockTransactions = [ - ...transactions.filter(t => t.date <= '2017-10-15'), - ...transactions.filter(t => t.date === '2017-10-16').slice(0, 1), - ...transactions.filter(t => t.date === '2017-10-17').slice(0, 3), - ]; - - return { - accounts: data.accounts, - transactions: { [account_id]: mockTransactions }, - }; - }); - return mockTransactions; -} - async function prepareDatabase() { await db.insertCategoryGroup({ id: 'group1', name: 'group1', is_income: 1 }); await db.insertCategory({ From 7095f1ce41f61e01a61291b1616e4e66de721f76 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Wed, 3 Apr 2024 18:10:15 +0100 Subject: [PATCH 9/9] Strip even more Plaid code --- .../loot-core/src/client/actions/account.ts | 20 ------- .../loot-core/src/server/accounts/link.ts | 42 -------------- .../loot-core/src/server/accounts/sync.ts | 2 +- packages/loot-core/src/server/main.test.ts | 58 ------------------- packages/loot-core/src/server/main.ts | 11 ---- .../loot-core/src/types/server-handlers.d.ts | 7 --- 6 files changed, 1 insertion(+), 139 deletions(-) diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts index f6448ce9e56..0f31d3b4ac3 100644 --- a/packages/loot-core/src/client/actions/account.ts +++ b/packages/loot-core/src/client/actions/account.ts @@ -78,26 +78,6 @@ export function linkAccountSimpleFin(externalAccount, upgradingId) { }; } -// TODO: type correctly or remove (unused) -export function connectAccounts( - institution, - publicToken, - accountIds, - offbudgetIds, -) { - return async (dispatch: Dispatch) => { - const ids = await send('accounts-connect', { - institution, - publicToken, - accountIds, - offbudgetIds, - }); - await dispatch(getPayees()); - await dispatch(getAccounts()); - return ids; - }; -} - export function syncAccounts(id?: string) { return async (dispatch: Dispatch, getState: GetState) => { // Disallow two parallel sync operations diff --git a/packages/loot-core/src/server/accounts/link.ts b/packages/loot-core/src/server/accounts/link.ts index 26879ed3c5e..2f7282c5c7a 100644 --- a/packages/loot-core/src/server/accounts/link.ts +++ b/packages/loot-core/src/server/accounts/link.ts @@ -63,48 +63,6 @@ export async function findOrCreateBank(institution, requisitionId) { return bankData; } -export async function addAccounts(bankId, accountIds, offbudgetIds = []) { - const [[, userId], [, userKey]] = await asyncStorage.multiGet([ - 'user-id', - 'user-key', - ]); - - // Get all the available accounts - let accounts = await bankSync.getAccounts(userId, userKey, bankId); - - // Only add the selected accounts - accounts = accounts.filter(acct => accountIds.includes(acct.account_id)); - - return Promise.all( - accounts.map(async acct => { - const id = await runMutator(async () => { - const id = await db.insertAccount({ - account_id: acct.account_id, - name: acct.name, - official_name: acct.official_name, - balance_current: amountToInteger(acct.balances.current), - mask: acct.mask, - bank: bankId, - offbudget: offbudgetIds.includes(acct.account_id) ? 1 : 0, - }); - - // Create a transfer payee - await db.insertPayee({ - name: '', - transfer_acct: id, - }); - - return id; - }); - - // Do an initial sync - await bankSync.syncAccount(userId, userKey, id, acct.account_id, bankId); - - return id; - }), - ); -} - export async function addGoCardlessAccounts( bankId, accountIds, diff --git a/packages/loot-core/src/server/accounts/sync.ts b/packages/loot-core/src/server/accounts/sync.ts index 3c8c44639fb..03bd3fb9dae 100644 --- a/packages/loot-core/src/server/accounts/sync.ts +++ b/packages/loot-core/src/server/accounts/sync.ts @@ -805,7 +805,7 @@ export async function syncAccount( ); } - const transactions = download.transactions; + const { transactions } = download; let balanceToUse = download.startingBalance; if (acctRow.account_sync_source === 'simpleFin') { diff --git a/packages/loot-core/src/server/main.test.ts b/packages/loot-core/src/server/main.test.ts index 4625011bc84..ea4d8713007 100644 --- a/packages/loot-core/src/server/main.test.ts +++ b/packages/loot-core/src/server/main.test.ts @@ -16,7 +16,6 @@ import { disableGlobalMutations, enableGlobalMutations, } from './mutators'; -import { post } from './post'; import * as prefs from './prefs'; import * as sheet from './sheet'; @@ -103,63 +102,6 @@ describe('Budgets', () => { }); describe('Accounts', () => { - test('create accounts with correct starting balance', async () => { - prefs.loadPrefs(); - prefs.savePrefs({ groupId: 'group' }); - - await runMutator(async () => { - // An income category is required because the starting balance is - // categorized to it. Create one now. - await db.insertCategoryGroup({ - id: 'group1', - name: 'income', - is_income: 1, - }); - await db.insertCategory({ - name: 'income', - cat_group: 'group1', - is_income: 1, - }); - }); - - // Get accounts from the server. This isn't the normal API call, - // we know that the mock server just returns hardcoded accounts - const { accounts } = await post('/plaid/accounts', {}); - - // Create the accounts for the bank (bank is generally ignored in tests) - await runHandler(handlers['accounts-connect'], { - institution: { institution_id: 1, name: 'Jamesy Bank' }, - publicToken: 'foo', - accountIds: accounts.map(acct => acct.account_id), - }); - - // Import transactions for all accounts - await runHandler(handlers['accounts-sync'], {}); - - // Go through each account and make sure the starting balance was - // created correctly - const res = await db.all('SELECT * FROM accounts'); - for (const account of res) { - const sum = await db.first( - 'SELECT sum(amount) as sum FROM transactions WHERE acct = ? AND starting_balance_flag = 0', - [account.id], - ); - const starting = await db.first( - 'SELECT * FROM transactions WHERE acct = ? AND starting_balance_flag = 1', - [account.id], - ); - expect(account.balance_current - sum.sum).toBe(starting.amount); - - // Also ensure that the starting balance has the earliest date - // possible - const earliestTrans = await db.first( - 'SELECT p.name as payee_name FROM transactions t LEFT JOIN payees p ON p.id = t.description WHERE acct = ? ORDER BY date LIMIT 1', - [account.id], - ); - expect(earliestTrans.payee_name).toBe('Starting Balance'); - } - }); - test('Transfers are properly updated', async () => { await runMutator(async () => { await db.insertAccount({ id: 'one', name: 'one' }); diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index f648a0b19a1..c0ca4ec8ea4 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -720,17 +720,6 @@ handlers['simplefin-accounts-link'] = async function ({ return 'ok'; }; -handlers['accounts-connect'] = async function ({ - institution, - publicToken, - accountIds, - offbudgetIds, -}) { - const bankId = await link.handoffPublicToken(institution, publicToken); - const ids = await link.addAccounts(bankId, accountIds, offbudgetIds); - return ids; -}; - handlers['gocardless-accounts-connect'] = async function ({ institution, publicToken, diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index 7704b7582a8..476bbeaa97b 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -160,13 +160,6 @@ export interface ServerHandlers { upgradingId; }) => Promise<'ok'>; - 'accounts-connect': (arg: { - institution; - publicToken; - accountIds; - offbudgetIds?; - }) => Promise; - 'gocardless-accounts-connect': (arg: { institution; publicToken;