From 8f16a3aa1311517a34f9a49e0f3aabcdb6eda7c4 Mon Sep 17 00:00:00 2001 From: Casey Timm Date: Mon, 21 Jul 2025 21:07:28 -0400 Subject: [PATCH] Fixed pending problmes --- src/lib/db.js | 34 ++++++- src/lib/united.js | 44 ++++++++- src/routes/account/[slug]/+page.server.js | 9 +- .../api/budget/transaction/[slug]/+server.js | 18 ++++ src/routes/api/transactions/+server.js | 8 ++ src/routes/budget/[slug]/+page.svelte | 96 ++++++++++++++++++- 6 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 src/routes/api/budget/transaction/[slug]/+server.js create mode 100644 src/routes/api/transactions/+server.js diff --git a/src/lib/db.js b/src/lib/db.js index 6693f1c..d3c9e8b 100644 --- a/src/lib/db.js +++ b/src/lib/db.js @@ -10,6 +10,14 @@ const db = postgres({ export { db }; +export async function setBudgetTransactionTransactionId(transactionId, budgetTransactionId) { + return await db` + update budget_transaction + set transaction_id = ${transactionId} + where id = ${budgetTransactionId} + `; +} + export async function getTotal() { const result = await db` select sum(balance) as total @@ -94,7 +102,7 @@ export async function getBudgetTransactions(id) { budget_transaction.amount as budget_amount, budget_transaction.id as budget_transaction_id from budget_transaction - join transaction on budget_transaction.transaction_id = transaction.id + left join transaction on budget_transaction.transaction_id = transaction.id where budget_transaction.budget_id = ${id} order by transaction.date desc `; @@ -262,7 +270,7 @@ export async function getHiddenAccounts(age) { return accounts; } -export async function getTransactions(accountId) { +export async function getTransactionsForAccount(accountId) { let transactions = await db` select transaction.id as id, @@ -283,6 +291,28 @@ export async function getTransactions(accountId) { return transactions; } +export async function getTransactions(pattern = '', limit = 100) { + let transactions = await db` + select + transaction.id as id, + transaction.account_id as account_id, + transaction.amount as amount, + transaction.description as description, + transaction.pending as pending, + transaction.notes as notes, + transaction.payee as payee, + transaction.date as date, + transaction.statement_description as statement_description, + transaction.out_of_budget as out_of_budget + from transaction + where transaction.description ILIKE ${`%${pattern}%`} + order by date desc + LIMIT ${limit} + `; + + return transactions; +} + export async function setTransactionNote(transactionId, note) { const result = await db` update transaction diff --git a/src/lib/united.js b/src/lib/united.js index bd106ac..882b4b6 100644 --- a/src/lib/united.js +++ b/src/lib/united.js @@ -207,10 +207,36 @@ export async function pullData(amount = 100) { state.push(`Found ${transactions.length} transactions for account ID: ${account.id}`); const cardRegEx = /\d{4}$/; + + // Check if any pending need to be updated + + state.push(`Checking for pending transactions for account ID: ${account.id}`); + let currentPend = + await db`SELECT id, amount, description, date from transaction where account_id = ${account.id} AND pending=true`; + for (const pend of currentPend) { + const found = transactions.find((t) => t.transactionId === pend.id); + if (!found) { + const updated = transactions.find( + (t) => t.amount == pend.amount && new Date(t.postedDate) == pend.date + ); + if (updated) { + state.push( + `I think I found an updated transaction: ${updated.statementDescription} ${updated.amount} for ${pend.description} ${pend.amount}` + ); + + await db`UPDATE budget_transaction SET transaction_id = ${updated.transactionId} WHERE transaction_id = ${pend.id}`; + } else { + state.push(`Orphaning no longer pending budget transaction with no new parent`); + await db`UPDATE budget_transaction SET transaction_id = null WHERE transaction_id = ${pend.id}`; + } + state.push(`Removing pending transaction: ${pend.id}`); + await db`DELETE FROM transaction WHERE id = ${pend.id}`; + } + } + for (const transaction of transactions) { const amount = Number(transaction.amount); const date = new Date(transaction.postedDate); - const id = transaction.hostTranNumber; const payee = transaction.description || ''; const statementDescription = transaction.statementDescription; const card = cardRegEx.test(statementDescription) @@ -218,6 +244,11 @@ export async function pullData(amount = 100) { : null; const pending = transaction.extended?.allTransactionType == 1 ? true : false; const accountId = transaction.accountId; + + const id = pending + ? `${transaction.postedDate}:${transaction.amount}` + : transaction.hostTranNumber; + await db`INSERT INTO transaction ( id, account_id, @@ -248,6 +279,17 @@ export async function pullData(amount = 100) { pending = EXCLUDED.pending`; } } + + state.push('Orphaning transactions'); + + const orphaned = await db`SELECT bt.id as id + FROM budget_transaction bt + LEFT OUTER JOIN transaction t ON bt.transaction_id = t.id + WHERE t.id IS NULL;`; + for (const orphan of orphaned) { + state.push(`Orphaning transaction: ${orphan.id}`); + await db`UPDATE budget_transaction set transaction_id = null where id = ${orphan.id}`; + } } catch (error) { console.error('Error in pullData:', error); state.push(`Error: ${error.message}`); diff --git a/src/routes/account/[slug]/+page.server.js b/src/routes/account/[slug]/+page.server.js index cb8ee24..f54555c 100644 --- a/src/routes/account/[slug]/+page.server.js +++ b/src/routes/account/[slug]/+page.server.js @@ -1,10 +1,15 @@ import { error } from '@sveltejs/kit'; -import { getAccount, getTransactions, getBudgets, getBudgetTransactionsForAccount } from '$lib/db'; +import { + getAccount, + getTransactionsForAccount, + getBudgets, + getBudgetTransactionsForAccount +} from '$lib/db'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params, depends }) { const slug = params.slug; - const transactions = await getTransactions(slug); + const transactions = await getTransactionsForAccount(slug); const account = await getAccount(slug); const budgets = await getBudgets(); const budgetTransactions = await getBudgetTransactionsForAccount(slug); diff --git a/src/routes/api/budget/transaction/[slug]/+server.js b/src/routes/api/budget/transaction/[slug]/+server.js new file mode 100644 index 0000000..748571b --- /dev/null +++ b/src/routes/api/budget/transaction/[slug]/+server.js @@ -0,0 +1,18 @@ +import { setBudgetTransactionTransactionId } from '$lib/db.js'; + +export async function PATCH({ request, params }) { + const body = await request.json(); + const { slug } = params; + const { transactionId } = body; + + if (!slug || !transactionId) { + return new Response('Missing transactionId or budgetTransactionId', { status: 400 }); + } + + try { + await setBudgetTransactionTransactionId(transactionId, slug); + return new Response('Budget transaction updated successfully', { status: 200 }); + } catch (error) { + return new Response(`Error updating budget transaction: ${error.message}`, { status: 500 }); + } +} diff --git a/src/routes/api/transactions/+server.js b/src/routes/api/transactions/+server.js new file mode 100644 index 0000000..46a02dd --- /dev/null +++ b/src/routes/api/transactions/+server.js @@ -0,0 +1,8 @@ +import { getTransactions } from '$lib/db.js'; + +export async function GET({ url }) { + const count = url.searchParams.get('c') || '100'; + const pattern = url.searchParams.get('p') || ''; + const transacations = await getTransactions(pattern, count); + return new Response(JSON.stringify(transacations)); +} diff --git a/src/routes/budget/[slug]/+page.svelte b/src/routes/budget/[slug]/+page.svelte index d139927..d00afd2 100644 --- a/src/routes/budget/[slug]/+page.svelte +++ b/src/routes/budget/[slug]/+page.svelte @@ -18,6 +18,52 @@ let toDelete = $state(''); let toDeleteName = $state(''); let loading = $state(false); + let newTransaction = $state({ + name: '', + id: null + }); + let searchString = $state(''); + let debounceString = $state(''); + let searchResults = $state([]); + let searching = $state(false); + + async function search() { + if (searching) return; // Prevent multiple searches at the same time + searching = true; + const res = await fetch(`/api/transactions?c=100&p=${searchString}`); + const data = await res.json(); + searchResults = data; + searching = false; + } + + async function updateTransactionID() { + loading = true; + EditBudgetTransactionModal.close(); + if (!newTransaction.id) { + addToast('Please select a transaction to update', 'warning'); + return; + } + if (newTransaction.id == newData.id) { + addToast('No changes made to the transaction', 'info'); + return; + } + const response = await fetch(`/api/budget/transaction/${newData.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + transactionId: newTransaction.id + }) + }); + if (response.ok) { + addToast('Transaction updated successfully', 'success'); + invalidateAll(); + } else { + addToast('Failed to update transaction', 'error'); + } + loading = false; + } async function saveTransaction() { loading = true; @@ -97,15 +143,19 @@ {#each transactions as tras}
  • -
    {tras.description}
    -
    - {tras.date.toDateString()} -
    + {#if tras.id == null} + Orphan + {:else} +
    {tras?.description}
    +
    + {tras?.date?.toDateString()} +
    + {/if}
    {tras.notes}
    -
    {tras.budget_amount}
    +
    {tras?.budget_amount}