From 87b038d4fd04f44210cead3ea2c562b272b4ea0a Mon Sep 17 00:00:00 2001 From: Groom Book CEO Date: Wed, 18 Mar 2026 13:23:31 +0000 Subject: [PATCH] Fix reports crash: serialize Date as ISO string in churn risk query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /api/reports/clients endpoint crashes with a 500 because Drizzle's sql template literal in a HAVING clause cannot serialize a JavaScript Date object — the postgres driver expects a string. Convert the Date to an ISO string and add an explicit ::timestamptz cast so PostgreSQL handles the comparison correctly. Closes groombook/groombook#49 Co-Authored-By: Paperclip Co-Authored-By: Claude Opus 4.6 --- apps/api/src/routes/reports.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/reports.ts b/apps/api/src/routes/reports.ts index 7a56a31..3d53bda 100644 --- a/apps/api/src/routes/reports.ts +++ b/apps/api/src/routes/reports.ts @@ -279,6 +279,7 @@ reportsRouter.get("/clients", async (c) => { // Clients with no appointment in last 90 days (churn risk) const ninetyDaysAgo = new Date(); ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90); + const ninetyDaysAgoISO = ninetyDaysAgo.toISOString(); const churnRisk = await db .select({ @@ -290,7 +291,7 @@ reportsRouter.get("/clients", async (c) => { .leftJoin(appointments, eq(appointments.clientId, clients.id)) .groupBy(clients.id, clients.name) .having( - sql`MAX(${appointments.startTime}) < ${ninetyDaysAgo} OR MAX(${appointments.startTime}) IS NULL` + sql`MAX(${appointments.startTime}) < ${ninetyDaysAgoISO}::timestamptz OR MAX(${appointments.startTime}) IS NULL` ) .orderBy(sql`MAX(${appointments.startTime}) ASC NULLS FIRST`); -- 2.52.0