Link service accounts to recurring bills with dropdown and display chip
Deploy to LXC / deploy (push) Successful in 2m1s
Validate / validate (push) Successful in 35s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 13:58:43 +07:00
parent 1ce614186d
commit dbfd229ba8
2 changed files with 53 additions and 3 deletions
@@ -4,6 +4,7 @@ import { db } from '$lib/server/db/index.js';
import {
recurringBills,
companyAccounts,
companyServiceAccounts,
projects,
categories,
parties
@@ -60,6 +61,7 @@ type BillFormFields = {
projectId: string;
categoryId: string | null;
partyId: string | null;
serviceAccountId: string | null;
description: string | null;
currency: string;
startDate: string;
@@ -106,6 +108,7 @@ function extractFields(fd: FormData): BillFormFields | string {
projectId,
categoryId: trimOrNull(fd.get('categoryId')),
partyId: trimOrNull(fd.get('partyId')),
serviceAccountId: trimOrNull(fd.get('serviceAccountId')),
description: trimOrNull(fd.get('description')),
currency,
startDate,
@@ -118,7 +121,8 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
await requireCompanyRoleAny(locals, params.companyId, ['admin', 'manager', 'accountant']);
await parent();
const [billRows, accountRows, projectRows, categoryRows, partyRows] = await Promise.all([
const [billRows, accountRows, projectRows, categoryRows, partyRows, serviceAccountRows] =
await Promise.all([
db
.select({
id: recurringBills.id,
@@ -144,6 +148,9 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
categoryName: categories.name,
partyId: recurringBills.partyId,
partyName: parties.name,
serviceAccountId: recurringBills.serviceAccountId,
serviceAccountProvider: companyServiceAccounts.providerName,
serviceAccountNumber: companyServiceAccounts.accountNumber,
createdAt: recurringBills.createdAt,
updatedAt: recurringBills.updatedAt
})
@@ -152,6 +159,7 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
.leftJoin(projects, eq(recurringBills.projectId, projects.id))
.leftJoin(categories, eq(recurringBills.categoryId, categories.id))
.leftJoin(parties, eq(recurringBills.partyId, parties.id))
.leftJoin(companyServiceAccounts, eq(recurringBills.serviceAccountId, companyServiceAccounts.id))
.where(
and(eq(recurringBills.companyId, params.companyId), isNull(recurringBills.deletedAt))
)
@@ -190,7 +198,25 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
.select({ id: parties.id, name: parties.name })
.from(parties)
.where(and(eq(parties.companyId, params.companyId), isNull(parties.deletedAt)))
.orderBy(asc(parties.name))
.orderBy(asc(parties.name)),
db
.select({
id: companyServiceAccounts.id,
type: companyServiceAccounts.type,
providerName: companyServiceAccounts.providerName,
accountNumber: companyServiceAccounts.accountNumber,
customLabel: companyServiceAccounts.customLabel
})
.from(companyServiceAccounts)
.where(
and(
eq(companyServiceAccounts.companyId, params.companyId),
isNull(companyServiceAccounts.deletedAt),
eq(companyServiceAccounts.isActive, true)
)
)
.orderBy(asc(companyServiceAccounts.type), asc(companyServiceAccounts.providerName))
]);
return {
@@ -198,7 +224,8 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
accounts: accountRows,
projects: projectRows,
categories: categoryRows,
parties: partyRows
parties: partyRows,
serviceAccounts: serviceAccountRows
};
};
@@ -259,6 +286,7 @@ export const actions: Actions = {
accountId: parsed.accountId,
categoryId: parsed.categoryId,
partyId: parsed.partyId,
serviceAccountId: parsed.serviceAccountId,
name: parsed.name,
description: parsed.description,
cycle: parsed.cycle,
@@ -342,6 +370,7 @@ export const actions: Actions = {
accountId: parsed.accountId,
categoryId: parsed.categoryId,
partyId: parsed.partyId,
serviceAccountId: parsed.serviceAccountId,
name: parsed.name,
description: parsed.description,
cycle: parsed.cycle,
@@ -71,6 +71,7 @@
projectId?: string;
categoryId?: string;
partyId?: string;
serviceAccountId?: string;
description?: string;
currency?: string;
startDate?: string;
@@ -211,6 +212,20 @@
{/each}
</select>
</div>
<div>
<label class={labelCls} for="bill-sa-{billId ?? 'new'}">Service Account</label>
<select
id="bill-sa-{billId ?? 'new'}"
name="serviceAccountId"
value={values.serviceAccountId ?? ''}
class={inputCls}
>
<option value=""></option>
{#each data.serviceAccounts as sa (sa.id)}
<option value={sa.id}>[{sa.type}] {sa.providerName} #{sa.accountNumber}</option>
{/each}
</select>
</div>
<div>
<label class={labelCls} for="bill-start-{billId ?? 'new'}">Start date <span class="text-red-500">*</span></label>
<input
@@ -379,6 +394,11 @@
Vendor: {bill.partyName}
</div>
{/if}
{#if bill.serviceAccountProvider}
<div class="text-xs text-indigo-600 dark:text-indigo-400">
{bill.serviceAccountProvider} #{bill.serviceAccountNumber}
</div>
{/if}
{#if bill.status === 'paused' && bill.pausedAt}
<div class="text-xs text-amber-600 dark:text-amber-400">
Paused since {formatDate(bill.pausedAt)}
@@ -526,6 +546,7 @@
projectId: bill.projectId,
categoryId: bill.categoryId ?? '',
partyId: bill.partyId ?? '',
serviceAccountId: bill.serviceAccountId ?? '',
description: bill.description ?? '',
currency: bill.currency,
startDate: bill.startDate,