Implement account and transaction upsert functionality; add updateAccounts API endpoint

This commit is contained in:
2025-07-07 18:35:00 -04:00
parent 824a23765e
commit 3bc6b9ab7c
5 changed files with 181 additions and 61 deletions

View File

@ -166,5 +166,97 @@ export async function setTransactionNote(transactionId, note) {
return result return result
} }
export async function updateAccounts(data) {
try {
console.log('Updating accounts with data:', data);
for (const account of data.accounts) {
// Upsert Org
console.log(`Upserting org for account: ${account.id}`, account.org);
await db`
insert into org (id, domain, name, sfin_url, url)
values (${account.org.id}, ${account.org.domain ?? null}, ${account.org.name ?? null}, ${account.org.sfin_url ?? null}, ${account.org.url ?? null})
on conflict (id) do update set
domain = excluded.domain,
name = excluded.name,
sfin_url = excluded.sfin_url,
url = excluded.url
`;
console.log(`Upserting account: ${account.id} (${account.name})`);
// Upsert Account
await db`
insert into account (id, org_id, name, currency, balance, available_balance, balance_date)
values (
${account.id},
${account.org.id},
${account.name ?? null},
${account.currency ?? null},
${account.balance ?? null},
${account.available_balance ?? null},
${account.balance_date ?? null}
)
on conflict (id) do update set
org_id = excluded.org_id,
name = excluded.name,
currency = excluded.currency,
balance = excluded.balance,
available_balance = excluded.available_balance,
balance_date = excluded.balance_date
`;
// Upsert Transactions
if (account.transactions && account.transactions.length > 0) {
for (const txn of account.transactions) {
let extraId = null;
console.log(`Upserting transaction: ${txn.id} for account: ${account.id}`);
if (txn.extra) {
// Upsert TransactionExtra (insert only, update not needed for category)
const extraResult = await db`
insert into transaction_extra (category)
values (${txn.extra.category ?? null})
on conflict (category) do nothing
returning id
`;
if (extraResult.length > 0) {
extraId = extraResult[0].id;
} else {
// If already exists, fetch id
const existing = await db`
select id from transaction_extra where category = ${txn.extra.category ?? null}
`;
if (existing.length > 0) {
extraId = existing[0].id;
}
}
}
console.log(`Preparing to upsert transaction: ${txn.id} with data:`, txn);
await db`
insert into transaction (id, account_id, posted, amount, description, pending, extra_id)
values (
${txn.id},
${account.id},
${txn.posted},
${txn.amount ?? null},
${txn.description ?? null},
${txn.pending},
${extraId}
)
on conflict (id) do update set
account_id = excluded.account_id,
posted = excluded.posted,
amount = excluded.amount,
description = excluded.description,
pending = excluded.pending,
extra_id = excluded.extra_id
`;
}
}
}
return true;
} catch (error) {
console.error('updateAccounts error:', error);
return false;
}
}
export default db export default db

View File

@ -1,5 +1,14 @@
export async function updateAccounts() export async function fetchAccounts(url, startDate) {
{ const { username, password, origin, pathname } = new URL(url);
fetch const start = Math.floor(startDate.getTime() / 1000);
const apiUrl = `${origin}${pathname}/accounts?start-date=${start}`;
const headers = {};
if (username && password) {
headers['Authorization'] = 'Basic ' + btoa(`${username}:${password}`);
}
const response = await fetch(apiUrl, { headers });
return await response.json();
} }

View File

@ -7,6 +7,11 @@
amount: 0, amount: 0,
notes: '' notes: ''
}); });
async function update() {
let res = await fetch('/api/simplefin/update', {
method: 'POST'
});
}
async function saveBudget() { async function saveBudget() {
let res = await fetch('/budget', { let res = await fetch('/budget', {
method: 'POST', method: 'POST',
@ -23,7 +28,6 @@
}); });
newBudget = { name: '', amount: 0, notes: '' }; // Reset the form newBudget = { name: '', amount: 0, notes: '' }; // Reset the form
// Optionally, you can refresh the budgets list or show a success message // Optionally, you can refresh the budgets list or show a success message
} else { } else {
console.error('Failed to save budget'); console.error('Failed to save budget');
} }
@ -32,7 +36,7 @@
<div class="drawer lg:drawer-open"> <div class="drawer lg:drawer-open">
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" /> <input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col items-center justify-center"> <div class="drawer-content flex flex-col m-5">
<!-- Page content here --> <!-- Page content here -->
{@render children()} {@render children()}
<label for="my-drawer-2" class="btn btn-primary drawer-button lg:hidden"> Open drawer </label> <label for="my-drawer-2" class="btn btn-primary drawer-button lg:hidden"> Open drawer </label>
@ -40,14 +44,10 @@
<div class="drawer-side"> <div class="drawer-side">
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label> <label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu bg-base-200 text-base-content min-h-full w-80 p-4"> <ul class="menu bg-base-200 text-base-content min-h-full w-80 p-4">
{#each data.accounts as account}
<li> <li>
<a href={`/account/${account.id}`}> <button onclick={update}>Update</button>
{account.name} ({account.balance})
</a>
</li> </li>
{/each} <li><div class="divider">Budgets</div></li>
<li><div class="divider"></div></li>
{#each budgets as budget} {#each budgets as budget}
<li> <li>
<a href={`/budget/${budget.id}`}> <a href={`/budget/${budget.id}`}>
@ -56,7 +56,7 @@
</li> </li>
{/each} {/each}
<li> <li>
<button class="btn btn-circle" aria-label="Add" onclick={()=>AddBudgetModal.showModal()}> <button class="btn btn-circle" aria-label="Add" onclick={() => AddBudgetModal.showModal()}>
<svg <svg
fill="#F0F0F0F0" fill="#F0F0F0F0"
height="800px" height="800px"
@ -76,28 +76,36 @@
</svg> </svg>
</button> </button>
</li> </li>
<li><div class="divider">Accounts</div></li>
{#each data.accounts as account}
<li>
<a href={`/account/${account.id}`}>
{account.name} ({account.balance})
</a>
</li>
{/each}
</ul> </ul>
</div> </div>
</div> </div>
<dialog id="AddBudgetModal" class="modal"> <dialog id="AddBudgetModal" class="modal">
<div class="modal-box"> <div class="modal-box">
<form method="dialog"> <form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button> <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
</form> </form>
<div> <div>
<input <input
bind:value={newBudget.name} bind:value={newBudget.name}
type="text" type="text"
placeholder="Budget Name" placeholder="Budget Name"
class="input input-bordered w-full max-w-xs"/> class="input input-bordered w-full max-w-xs"
/>
<input <input
bind:value={newBudget.amount} bind:value={newBudget.amount}
type="number" type="number"
placeholder="Budget Amount" placeholder="Budget Amount"
class="input input-bordered w-full max-w-xs mt-2"/> class="input input-bordered w-full max-w-xs mt-2"
/>
<textarea bind:value={newBudget.notes} class="textarea w-100"></textarea> <textarea bind:value={newBudget.notes} class="textarea w-100"></textarea>
</div> </div>
<button onclick={() => saveBudget()} class="btn btn-primary mt-4">Save</button> <button onclick={() => saveBudget()} class="btn btn-primary mt-4">Save</button>

View File

@ -63,7 +63,7 @@
<h4>${currentTransaction?.amount}</h4> <h4>${currentTransaction?.amount}</h4>
<p> {currentTransaction?.date?.toDateString()}</p> <p> {currentTransaction?.date?.toDateString()}</p>
<div> <div>
<textarea bind:value={notes} class="textarea w-100"/> <textarea bind:value={notes} class="textarea w-100"></textarea>
</div> </div>
<button onclick={() => saveNotes()} class="btn btn-primary mt-4">Save</button> <button onclick={() => saveNotes()} class="btn btn-primary mt-4">Save</button>
</div> </div>

View File

@ -0,0 +1,11 @@
import { fetchAccounts } from '$lib/simplefin';
import { updateAccounts } from '$lib/db' ;
const url = "https://19443E0E8171E175EC5DA0C69B35DD50197F234B9A74C00D27FD606121257ECF:DAA3702E2100CFFD3B544251E6D755E86B1EDDFBFCC7F6FA9CE77AB3677E60DE@beta-bridge.simplefin.org/simplefin";
export async function POST() {
const res = await fetchAccounts(url, new Date("2025-07-01"))
return new Response(await updateAccounts(res));
}