diff --git a/src/routes/(app)/companies/[companyId]/+page.server.ts b/src/routes/(app)/companies/[companyId]/+page.server.ts index d3af353..8a7e905 100644 --- a/src/routes/(app)/companies/[companyId]/+page.server.ts +++ b/src/routes/(app)/companies/[companyId]/+page.server.ts @@ -1,6 +1,6 @@ import type { PageServerLoad } from './$types'; import { db } from '$lib/server/db/index.js'; -import { projects, expenses } from '$lib/server/db/schema.js'; +import { projects, expenses, sales, saleLineItems } from '$lib/server/db/schema.js'; import { eq, and, sql } from 'drizzle-orm'; export const load: PageServerLoad = async ({ parent }) => { @@ -38,5 +38,20 @@ export const load: PageServerLoad = async ({ parent }) => { .orderBy(sql`${expenses.createdAt} desc`) .limit(10); - return { projects: projectList, recentExpenses }; + // Total confirmed sales income (net of withholding) + const [incomeRow] = await db + .select({ + total: sql`coalesce(sum( + (select sum(${saleLineItems.quantity} * ${saleLineItems.unitPrice} * (1 + ${saleLineItems.taxRate})) from sale_line_items where sale_id = ${sales.id}) + * (1 - ${sales.withholdingTaxRate}) + ), '0')::text` + }) + .from(sales) + .where(and(eq(sales.companyId, company.id), eq(sales.status, 'confirmed'))); + + return { + projects: projectList, + recentExpenses, + totalIncome: incomeRow?.total ?? '0' + }; }; diff --git a/src/routes/(app)/companies/[companyId]/+page.svelte b/src/routes/(app)/companies/[companyId]/+page.svelte index d3c06eb..812103a 100644 --- a/src/routes/(app)/companies/[companyId]/+page.svelte +++ b/src/routes/(app)/companies/[companyId]/+page.svelte @@ -8,6 +8,7 @@ const allocated = $derived(data.projects.reduce((s, p) => s + parseFloat(p.allocatedBudget), 0)); const spent = $derived(data.projects.reduce((s, p) => s + parseFloat(p.spent), 0)); const total = $derived(parseFloat(data.company.totalBudget)); + const income = $derived(parseFloat(data.totalIncome ?? '0')); const remaining = $derived(total - spent); const remainingPct = $derived(total > 0 ? (remaining / total) * 100 : 0); @@ -88,6 +89,16 @@ {total > 0 ? ((allocated / total) * 100).toFixed(1) : '0'}% of total

+ +
+

+ Income (from confirmed sales) +

+

+ {formatCurrency(income, currency)} +

+

Net of withholding tax

+
diff --git a/src/routes/(app)/companies/[companyId]/budget/+page.server.ts b/src/routes/(app)/companies/[companyId]/budget/+page.server.ts index 7e60ea1..b30b080 100644 --- a/src/routes/(app)/companies/[companyId]/budget/+page.server.ts +++ b/src/routes/(app)/companies/[companyId]/budget/+page.server.ts @@ -7,7 +7,9 @@ import { companies, users, expenses, - companyLog + companyLog, + sales, + saleLineItems } from '$lib/server/db/schema.js'; import { and, eq, sql } from 'drizzle-orm'; import { requireCompanyRole } from '$lib/server/authorization.js'; @@ -17,7 +19,7 @@ import { formatCurrency } from '$lib/utils/currency.js'; export const load: PageServerLoad = async ({ parent, params }) => { const { company } = await parent(); - const projectList = await db + const projectListRaw = await db .select({ id: projects.id, name: projects.name, @@ -30,6 +32,36 @@ export const load: PageServerLoad = async ({ parent, params }) => { .groupBy(projects.id) .orderBy(projects.name); + // Income per project from confirmed sales (gross - withholding = net receivable) + const incomeRows = await db + .select({ + projectId: sales.projectId, + income: sql`coalesce(sum( + (select sum(${saleLineItems.quantity} * ${saleLineItems.unitPrice} * (1 + ${saleLineItems.taxRate})) from sale_line_items where sale_id = ${sales.id}) + * (1 - ${sales.withholdingTaxRate}) + ), '0')::text` + }) + .from(sales) + .where( + and( + eq(sales.companyId, params.companyId), + eq(sales.status, 'confirmed') + ) + ) + .groupBy(sales.projectId); + + const incomeByProject = new Map(); + for (const row of incomeRows) { + incomeByProject.set(row.projectId, row.income); + } + + const projectList = projectListRaw.map((p) => ({ + ...p, + income: incomeByProject.get(p.id) ?? '0' + })); + + const unassignedIncome = incomeByProject.get(null) ?? '0'; + const allocations = await db .select({ id: budgetAllocations.id, @@ -64,8 +96,17 @@ export const load: PageServerLoad = async ({ parent, params }) => { .limit(100); const totalAllocated = projectList.reduce((s, p) => s + parseFloat(p.allocatedBudget), 0); + const totalIncome = + projectList.reduce((s, p) => s + parseFloat(p.income), 0) + parseFloat(unassignedIncome); - return { projects: projectList, allocations, totalAllocated, changelog }; + return { + projects: projectList, + allocations, + totalAllocated, + totalIncome, + unassignedIncome, + changelog + }; }; export const actions: Actions = {