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 { import {
recurringBills, recurringBills,
companyAccounts, companyAccounts,
companyServiceAccounts,
projects, projects,
categories, categories,
parties parties
@@ -60,6 +61,7 @@ type BillFormFields = {
projectId: string; projectId: string;
categoryId: string | null; categoryId: string | null;
partyId: string | null; partyId: string | null;
serviceAccountId: string | null;
description: string | null; description: string | null;
currency: string; currency: string;
startDate: string; startDate: string;
@@ -106,6 +108,7 @@ function extractFields(fd: FormData): BillFormFields | string {
projectId, projectId,
categoryId: trimOrNull(fd.get('categoryId')), categoryId: trimOrNull(fd.get('categoryId')),
partyId: trimOrNull(fd.get('partyId')), partyId: trimOrNull(fd.get('partyId')),
serviceAccountId: trimOrNull(fd.get('serviceAccountId')),
description: trimOrNull(fd.get('description')), description: trimOrNull(fd.get('description')),
currency, currency,
startDate, startDate,
@@ -118,7 +121,8 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
await requireCompanyRoleAny(locals, params.companyId, ['admin', 'manager', 'accountant']); await requireCompanyRoleAny(locals, params.companyId, ['admin', 'manager', 'accountant']);
await parent(); await parent();
const [billRows, accountRows, projectRows, categoryRows, partyRows] = await Promise.all([ const [billRows, accountRows, projectRows, categoryRows, partyRows, serviceAccountRows] =
await Promise.all([
db db
.select({ .select({
id: recurringBills.id, id: recurringBills.id,
@@ -144,6 +148,9 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
categoryName: categories.name, categoryName: categories.name,
partyId: recurringBills.partyId, partyId: recurringBills.partyId,
partyName: parties.name, partyName: parties.name,
serviceAccountId: recurringBills.serviceAccountId,
serviceAccountProvider: companyServiceAccounts.providerName,
serviceAccountNumber: companyServiceAccounts.accountNumber,
createdAt: recurringBills.createdAt, createdAt: recurringBills.createdAt,
updatedAt: recurringBills.updatedAt updatedAt: recurringBills.updatedAt
}) })
@@ -152,6 +159,7 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
.leftJoin(projects, eq(recurringBills.projectId, projects.id)) .leftJoin(projects, eq(recurringBills.projectId, projects.id))
.leftJoin(categories, eq(recurringBills.categoryId, categories.id)) .leftJoin(categories, eq(recurringBills.categoryId, categories.id))
.leftJoin(parties, eq(recurringBills.partyId, parties.id)) .leftJoin(parties, eq(recurringBills.partyId, parties.id))
.leftJoin(companyServiceAccounts, eq(recurringBills.serviceAccountId, companyServiceAccounts.id))
.where( .where(
and(eq(recurringBills.companyId, params.companyId), isNull(recurringBills.deletedAt)) 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 }) .select({ id: parties.id, name: parties.name })
.from(parties) .from(parties)
.where(and(eq(parties.companyId, params.companyId), isNull(parties.deletedAt))) .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 { return {
@@ -198,7 +224,8 @@ export const load: PageServerLoad = async ({ locals, params, parent }) => {
accounts: accountRows, accounts: accountRows,
projects: projectRows, projects: projectRows,
categories: categoryRows, categories: categoryRows,
parties: partyRows parties: partyRows,
serviceAccounts: serviceAccountRows
}; };
}; };
@@ -259,6 +286,7 @@ export const actions: Actions = {
accountId: parsed.accountId, accountId: parsed.accountId,
categoryId: parsed.categoryId, categoryId: parsed.categoryId,
partyId: parsed.partyId, partyId: parsed.partyId,
serviceAccountId: parsed.serviceAccountId,
name: parsed.name, name: parsed.name,
description: parsed.description, description: parsed.description,
cycle: parsed.cycle, cycle: parsed.cycle,
@@ -342,6 +370,7 @@ export const actions: Actions = {
accountId: parsed.accountId, accountId: parsed.accountId,
categoryId: parsed.categoryId, categoryId: parsed.categoryId,
partyId: parsed.partyId, partyId: parsed.partyId,
serviceAccountId: parsed.serviceAccountId,
name: parsed.name, name: parsed.name,
description: parsed.description, description: parsed.description,
cycle: parsed.cycle, cycle: parsed.cycle,
@@ -71,6 +71,7 @@
projectId?: string; projectId?: string;
categoryId?: string; categoryId?: string;
partyId?: string; partyId?: string;
serviceAccountId?: string;
description?: string; description?: string;
currency?: string; currency?: string;
startDate?: string; startDate?: string;
@@ -211,6 +212,20 @@
{/each} {/each}
</select> </select>
</div> </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> <div>
<label class={labelCls} for="bill-start-{billId ?? 'new'}">Start date <span class="text-red-500">*</span></label> <label class={labelCls} for="bill-start-{billId ?? 'new'}">Start date <span class="text-red-500">*</span></label>
<input <input
@@ -379,6 +394,11 @@
Vendor: {bill.partyName} Vendor: {bill.partyName}
</div> </div>
{/if} {/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} {#if bill.status === 'paused' && bill.pausedAt}
<div class="text-xs text-amber-600 dark:text-amber-400"> <div class="text-xs text-amber-600 dark:text-amber-400">
Paused since {formatDate(bill.pausedAt)} Paused since {formatDate(bill.pausedAt)}
@@ -526,6 +546,7 @@
projectId: bill.projectId, projectId: bill.projectId,
categoryId: bill.categoryId ?? '', categoryId: bill.categoryId ?? '',
partyId: bill.partyId ?? '', partyId: bill.partyId ?? '',
serviceAccountId: bill.serviceAccountId ?? '',
description: bill.description ?? '', description: bill.description ?? '',
currency: bill.currency, currency: bill.currency,
startDate: bill.startDate, startDate: bill.startDate,