Fixed pending problmes

This commit is contained in:
2025-07-21 21:07:28 -04:00
parent 6896e42f6a
commit 8f16a3aa13
6 changed files with 199 additions and 10 deletions

View File

@ -10,6 +10,14 @@ const db = postgres({
export { db }; 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() { export async function getTotal() {
const result = await db` const result = await db`
select sum(balance) as total select sum(balance) as total
@ -94,7 +102,7 @@ export async function getBudgetTransactions(id) {
budget_transaction.amount as budget_amount, budget_transaction.amount as budget_amount,
budget_transaction.id as budget_transaction_id budget_transaction.id as budget_transaction_id
from budget_transaction 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} where budget_transaction.budget_id = ${id}
order by transaction.date desc order by transaction.date desc
`; `;
@ -262,7 +270,7 @@ export async function getHiddenAccounts(age) {
return accounts; return accounts;
} }
export async function getTransactions(accountId) { export async function getTransactionsForAccount(accountId) {
let transactions = await db` let transactions = await db`
select select
transaction.id as id, transaction.id as id,
@ -283,6 +291,28 @@ export async function getTransactions(accountId) {
return transactions; 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) { export async function setTransactionNote(transactionId, note) {
const result = await db` const result = await db`
update transaction update transaction

View File

@ -207,10 +207,36 @@ export async function pullData(amount = 100) {
state.push(`Found ${transactions.length} transactions for account ID: ${account.id}`); state.push(`Found ${transactions.length} transactions for account ID: ${account.id}`);
const cardRegEx = /\d{4}$/; 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) { for (const transaction of transactions) {
const amount = Number(transaction.amount); const amount = Number(transaction.amount);
const date = new Date(transaction.postedDate); const date = new Date(transaction.postedDate);
const id = transaction.hostTranNumber;
const payee = transaction.description || ''; const payee = transaction.description || '';
const statementDescription = transaction.statementDescription; const statementDescription = transaction.statementDescription;
const card = cardRegEx.test(statementDescription) const card = cardRegEx.test(statementDescription)
@ -218,6 +244,11 @@ export async function pullData(amount = 100) {
: null; : null;
const pending = transaction.extended?.allTransactionType == 1 ? true : false; const pending = transaction.extended?.allTransactionType == 1 ? true : false;
const accountId = transaction.accountId; const accountId = transaction.accountId;
const id = pending
? `${transaction.postedDate}:${transaction.amount}`
: transaction.hostTranNumber;
await db`INSERT INTO transaction ( await db`INSERT INTO transaction (
id, id,
account_id, account_id,
@ -248,6 +279,17 @@ export async function pullData(amount = 100) {
pending = EXCLUDED.pending`; 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) { } catch (error) {
console.error('Error in pullData:', error); console.error('Error in pullData:', error);
state.push(`Error: ${error.message}`); state.push(`Error: ${error.message}`);

View File

@ -1,10 +1,15 @@
import { error } from '@sveltejs/kit'; 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} */ /** @type {import('./$types').PageServerLoad} */
export async function load({ params, depends }) { export async function load({ params, depends }) {
const slug = params.slug; const slug = params.slug;
const transactions = await getTransactions(slug); const transactions = await getTransactionsForAccount(slug);
const account = await getAccount(slug); const account = await getAccount(slug);
const budgets = await getBudgets(); const budgets = await getBudgets();
const budgetTransactions = await getBudgetTransactionsForAccount(slug); const budgetTransactions = await getBudgetTransactionsForAccount(slug);

View File

@ -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 });
}
}

View File

@ -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));
}

View File

@ -18,6 +18,52 @@
let toDelete = $state(''); let toDelete = $state('');
let toDeleteName = $state(''); let toDeleteName = $state('');
let loading = $state(false); 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() { async function saveTransaction() {
loading = true; loading = true;
@ -97,15 +143,19 @@
{#each transactions as tras} {#each transactions as tras}
<li class="list-row"> <li class="list-row">
<div> <div>
<div>{tras.description}</div> {#if tras.id == null}
<div class="text-xs uppercase font-semibold opacity-60"> <span class="badge badge-warning">Orphan</span>
{tras.date.toDateString()} {:else}
</div> <div>{tras?.description}</div>
<div class="text-xs uppercase font-semibold opacity-60">
{tras?.date?.toDateString()}
</div>
{/if}
</div> </div>
<div class="text-center">{tras.notes}</div> <div class="text-center">{tras.notes}</div>
<div class="text-lg uppercase font-semibold text-right w-32">{tras.budget_amount}</div> <div class="text-lg uppercase font-semibold text-right w-32">{tras?.budget_amount}</div>
<div> <div>
<button class="btn btn-square btn-ghost" onclick={() => edit(tras)} <button class="btn btn-square btn-ghost" onclick={() => edit(tras)}
>{@render EditSymbol()}</button >{@render EditSymbol()}</button
@ -123,6 +173,42 @@
<dialog id="EditBudgetTransactionModal" class="modal"> <dialog id="EditBudgetTransactionModal" class="modal">
<div class="modal-box"> <div class="modal-box">
<h1>{newData.name}</h1> <h1>{newData.name}</h1>
<fieldset class="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4">
<legend class="fieldset-legend">Reassign</legend>
<div class="join">
<label class="input">
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="2.5"
fill="none"
stroke="currentColor"
>
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</g>
</svg>
<input type="search" required placeholder="Search" bind:value={searchString} />
</label>
<button class="btn btn-neutral join-item" onclick={() => search()} disabled={searching}
>Search</button
>
</div>
<div class="join">
<select class="select" bind:value={newTransaction.id} disabled={searching}>
<option value="" disabled selected>Select a Transaction</option>
{#each searchResults as res}
<option value={res?.id}
>{res?.description} - {new Date(res?.date).toDateString()} - {res?.amount}}</option
>
{/each}
</select>
<button class="btn btn-neutral join-item" onclick={() => updateTransactionID()}
>Update</button
>
</div>
</fieldset>
<fieldset class="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4"> <fieldset class="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4">
<legend class="fieldset-legend">Edit</legend> <legend class="fieldset-legend">Edit</legend>