diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index a05a755..86e54ae 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -130,6 +130,9 @@ export const expenses = pgTable( accountId: uuid('account_id').references((): any => companyAccounts.id, { onDelete: 'set null' }), + invoiceId: uuid('invoice_id').references((): any => invoices.id, { + onDelete: 'set null' + }), submittedBy: text('submitted_by') .notNull() .references(() => users.id), diff --git a/src/routes/(app)/companies/[companyId]/expenses/+page.server.ts b/src/routes/(app)/companies/[companyId]/expenses/+page.server.ts index d4bebac..8622a10 100644 --- a/src/routes/(app)/companies/[companyId]/expenses/+page.server.ts +++ b/src/routes/(app)/companies/[companyId]/expenses/+page.server.ts @@ -6,9 +6,11 @@ import { projects, users, categories, - companyAccounts + companyAccounts, + invoices, + parties } from '$lib/server/db/schema.js'; -import { asc, eq, and, sql, isNull } from 'drizzle-orm'; +import { asc, eq, and, ne, sql, isNull } from 'drizzle-orm'; import { requireCompanyRole, requireCompanyRoleAny } from '$lib/server/authorization.js'; import { logCompanyEvent } from '$lib/server/audit.js'; import { formatCurrency } from '$lib/utils/currency.js'; @@ -40,6 +42,7 @@ export const load: PageServerLoad = async ({ parent, params, url }) => { categoryName: categories.name, accountId: expenses.accountId, accountName: companyAccounts.name, + invoiceId: expenses.invoiceId, createdAt: expenses.createdAt }) .from(expenses) @@ -87,12 +90,33 @@ export const load: PageServerLoad = async ({ parent, params, url }) => { .where(eq(categories.companyId, params.companyId)) .orderBy(asc(categories.name)); + const invoiceList = await db + .select({ + id: invoices.id, + invoiceNumber: invoices.invoiceNumber, + direction: invoices.direction, + partyName: parties.name, + total: invoices.total, + currency: invoices.currency + }) + .from(invoices) + .innerJoin(parties, eq(invoices.partyId, parties.id)) + .where( + and( + eq(invoices.companyId, params.companyId), + ne(invoices.status, 'voided'), + ne(invoices.status, 'cancelled') + ) + ) + .orderBy(asc(invoices.invoiceNumber)); + return { expenses: expenseList, statusFilter: status, accounts: accountsList, projects: projectList, - categories: categoryList + categories: categoryList, + invoices: invoiceList }; }; @@ -122,6 +146,7 @@ export const actions: Actions = { const projectId = fd.get('projectId')?.toString().trim() || null; const categoryId = fd.get('categoryId')?.toString().trim() || null; const accountId = fd.get('accountId')?.toString().trim() || null; + const invoiceId = fd.get('invoiceId')?.toString().trim() || null; const expenseDate = fd.get('expenseDate')?.toString().trim(); const description = fd.get('description')?.toString().trim() || null; @@ -144,6 +169,7 @@ export const actions: Actions = { projectId: resolvedProjectId, categoryId: categoryId || null, accountId: accountId || null, + invoiceId: invoiceId || null, submittedBy: user.id, title, description, @@ -322,6 +348,22 @@ export const actions: Actions = { { expenseId, accountId, previousAccountId: expense.accountId } ); + return { success: true }; + }, + + linkInvoice: async ({ request, locals, params }) => { + await requireCompanyRoleAny(locals, params.companyId, ['admin', 'manager', 'accountant']); + const fd = await request.formData(); + const expenseId = fd.get('expenseId')?.toString(); + const invoiceId = fd.get('invoiceId')?.toString().trim() || null; + + if (!expenseId) return fail(400, { error: 'Expense ID required' }); + + await db + .update(expenses) + .set({ invoiceId, updatedAt: new Date() }) + .where(eq(expenses.id, expenseId)); + return { success: true }; } }; diff --git a/src/routes/(app)/companies/[companyId]/expenses/+page.svelte b/src/routes/(app)/companies/[companyId]/expenses/+page.svelte index 1589fe4..dc53156 100644 --- a/src/routes/(app)/companies/[companyId]/expenses/+page.svelte +++ b/src/routes/(app)/companies/[companyId]/expenses/+page.svelte @@ -88,6 +88,15 @@ {/each} +
{formatCurrency(expense.amount, expense.currency)}