Link service accounts to recurring bills with dropdown and display chip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user