feat: add analytics query helpers for admin financial reporting
Provides getAnalyticsByYear, getMonthlyCollected, and getAvailableYears to power the admin dashboard analytics view (not yet wired to a page). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
import { db } from "@/db";
|
||||||
|
import { clients, payments } from "@/db/schema";
|
||||||
|
import { sql, and, eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
export async function getAnalyticsByYear(year: number) {
|
||||||
|
const [contracted] = await db
|
||||||
|
.select({ total: sql<string>`coalesce(sum(${clients.accepted_total}::numeric), 0)` })
|
||||||
|
.from(clients)
|
||||||
|
.where(sql`extract(year from ${clients.created_at}) = ${year}`);
|
||||||
|
|
||||||
|
const [clientsRow] = await db
|
||||||
|
.select({ count: sql<string>`count(*)` })
|
||||||
|
.from(clients)
|
||||||
|
.where(sql`extract(year from ${clients.created_at}) = ${year}`);
|
||||||
|
|
||||||
|
const [collected] = await db
|
||||||
|
.select({ total: sql<string>`coalesce(sum(${payments.amount}::numeric), 0)` })
|
||||||
|
.from(payments)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(payments.status, "saldato"),
|
||||||
|
sql`${payments.paid_at} is not null and extract(year from ${payments.paid_at}) = ${year}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [pending] = await db
|
||||||
|
.select({ total: sql<string>`coalesce(sum(${payments.amount}::numeric), 0)` })
|
||||||
|
.from(payments)
|
||||||
|
.where(sql`${payments.status} in ('da_saldare', 'inviata')`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
contracted: parseFloat(contracted?.total ?? "0"),
|
||||||
|
collected: parseFloat(collected?.total ?? "0"),
|
||||||
|
clientsAcquired: parseInt(clientsRow?.count ?? "0"),
|
||||||
|
pending: parseFloat(pending?.total ?? "0"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMonthlyCollected(year: number): Promise<number[]> {
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
month: sql<number>`extract(month from ${payments.paid_at})::int`,
|
||||||
|
total: sql<string>`coalesce(sum(${payments.amount}::numeric), 0)`,
|
||||||
|
})
|
||||||
|
.from(payments)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(payments.status, "saldato"),
|
||||||
|
sql`${payments.paid_at} is not null and extract(year from ${payments.paid_at}) = ${year}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.groupBy(sql`extract(month from ${payments.paid_at})`);
|
||||||
|
|
||||||
|
const byMonth: number[] = Array(12).fill(0);
|
||||||
|
for (const row of rows) {
|
||||||
|
byMonth[(row.month as number) - 1] = parseFloat(row.total);
|
||||||
|
}
|
||||||
|
return byMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAvailableYears(): Promise<number[]> {
|
||||||
|
const rows = await db
|
||||||
|
.select({ year: sql<number>`extract(year from ${clients.created_at})::int` })
|
||||||
|
.from(clients)
|
||||||
|
.groupBy(sql`extract(year from ${clients.created_at})`);
|
||||||
|
|
||||||
|
const years = rows.map((r) => r.year as number);
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
if (!years.includes(currentYear)) years.push(currentYear);
|
||||||
|
return years.sort((a, b) => b - a);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user