From a6df4c2dd919afe26300d931e25cdb420970bc46 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 17 Jun 2026 11:00:34 -0600 Subject: [PATCH] Fix shipping + dashboard + analytics queries against new schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /admin/shipping page was 500-ing with 'column c.name does not exist' because the getShippingOrders query in src/actions/shipping.ts was still using legacy column names on the new-schema customers table (name, email, phone) — the actual columns are first_name, last_name, primary_email, primary_phone. The dashboard and analytics pages were logging the same kind of errors but catching them, so the UI just showed zeroed stats instead of crashing. Same fix: - customers: name → first_name || ' ' || last_name - customers: email → primary_email - customers: phone → primary_phone - orders: subtotal → total_cents / 100 - orders: created_at → placed_at - orders: customer_name → join customers and concat first/last These are the same kind of fixes that landed in migration 0041 for the command-center RPCs. The application-layer queries just hadn't been updated. --- src/actions/analytics.ts | 2 +- src/actions/dashboard.ts | 74 +++++++++++++++++++++++----------------- src/actions/shipping.ts | 6 ++-- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/actions/analytics.ts b/src/actions/analytics.ts index 1d6c99f..38bd544 100644 --- a/src/actions/analytics.ts +++ b/src/actions/analytics.ts @@ -284,7 +284,7 @@ export async function getRecentOrders(limit: number = 10): Promise( `SELECT o.id::text AS id, - c.name AS customer_name, + TRIM(COALESCE(c.first_name, '') || ' ' || COALESCE(c.last_name, '')) AS customer_name, o.total_cents::float / 100.0 AS subtotal, o.status, o.placed_at::text AS created_at, diff --git a/src/actions/dashboard.ts b/src/actions/dashboard.ts index 97a41c6..3293d2a 100644 --- a/src/actions/dashboard.ts +++ b/src/actions/dashboard.ts @@ -42,24 +42,22 @@ export async function getDashboardStats(): Promise { const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate()); const endOfDay = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000); - // Fetch today's orders. `orders` and `stops` use the legacy schema - // (column names like `subtotal`, `brand_id`, `date`); the new-schema - // Drizzle `orders` table doesn't have these. Raw SQL via the shared - // pg pool. + // Fetch today's orders. New schema: `total_cents` (int, divide by 100 + // for dollars) and `placed_at` (not legacy `subtotal` / `created_at`). const todayOrdersRes = brandId - ? await pool.query<{ subtotal: number; status: string }>( - `SELECT subtotal, status + ? await pool.query<{ total_cents: number; status: string }>( + `SELECT total_cents, status FROM orders - WHERE created_at >= $1 - AND created_at < $2 + WHERE placed_at >= $1 + AND placed_at < $2 AND brand_id = $3`, [startOfDay.toISOString(), endOfDay.toISOString(), brandId], ) - : await pool.query<{ subtotal: number; status: string }>( - `SELECT subtotal, status + : await pool.query<{ total_cents: number; status: string }>( + `SELECT total_cents, status FROM orders - WHERE created_at >= $1 - AND created_at < $2`, + WHERE placed_at >= $1 + AND placed_at < $2`, [startOfDay.toISOString(), endOfDay.toISOString()], ); const todayOrders = todayOrdersRes.rows; @@ -67,7 +65,7 @@ export async function getDashboardStats(): Promise { // Calculate today's revenue and orders const validOrders = todayOrders.filter((o) => o.status !== "cancelled"); const todayRevenue = validOrders.reduce( - (sum, o) => sum + (o.subtotal || 0), + (sum, o) => sum + ((o.total_cents || 0) / 100), 0, ); const todayOrderCount = validOrders.length; @@ -106,7 +104,7 @@ export async function getDashboardStats(): Promise { ); const activeProducts = productsRes.rows.length; - // Fetch weekly orders for chart (last 7 days) + // Fetch weekly orders for chart (last 7 days). New schema uses `placed_at`. const weeklyOrders: number[] = []; for (let i = 6; i >= 0; i--) { const dayStart = new Date(startOfDay); @@ -116,48 +114,60 @@ export async function getDashboardStats(): Promise { const dayRes = brandId ? await pool.query<{ id: string }>( `SELECT id FROM orders - WHERE created_at >= $1 - AND created_at < $2 + WHERE placed_at >= $1 + AND placed_at < $2 AND brand_id = $3 LIMIT 1`, [dayStart.toISOString(), dayEnd.toISOString(), brandId], ) : await pool.query<{ id: string }>( `SELECT id FROM orders - WHERE created_at >= $1 - AND created_at < $2 + WHERE placed_at >= $1 + AND placed_at < $2 LIMIT 1`, [dayStart.toISOString(), dayEnd.toISOString()], ); weeklyOrders.push(dayRes.rows.length > 0 ? 1 : 0); } - // Fetch recent orders (last 10) + // Fetch recent orders (last 10). New schema: `total_cents`/`placed_at` + // on `orders`; customer name comes from a `customers` join (no + // `customer_name` column on `orders`). const recentRes = brandId ? await pool.query<{ id: string; customer_name: string; - subtotal: number; + total_cents: number; status: string; - created_at: string; + placed_at: string; }>( - `SELECT id, customer_name, subtotal, status, created_at - FROM orders - WHERE brand_id = $1 - ORDER BY created_at DESC + `SELECT o.id::text AS id, + TRIM(COALESCE(c.first_name, '') || ' ' || COALESCE(c.last_name, '')) AS customer_name, + o.total_cents, + o.status, + o.placed_at::text AS placed_at + FROM orders o + LEFT JOIN customers c ON c.id = o.customer_id + WHERE o.brand_id = $1 + ORDER BY o.placed_at DESC LIMIT 10`, [brandId], ) : await pool.query<{ id: string; customer_name: string; - subtotal: number; + total_cents: number; status: string; - created_at: string; + placed_at: string; }>( - `SELECT id, customer_name, subtotal, status, created_at - FROM orders - ORDER BY created_at DESC + `SELECT o.id::text AS id, + TRIM(COALESCE(c.first_name, '') || ' ' || COALESCE(c.last_name, '')) AS customer_name, + o.total_cents, + o.status, + o.placed_at::text AS placed_at + FROM orders o + LEFT JOIN customers c ON c.id = o.customer_id + ORDER BY o.placed_at DESC LIMIT 10`, ); @@ -166,9 +176,9 @@ export async function getDashboardStats(): Promise { .map((o) => ({ id: o.id || "", customer_name: o.customer_name || "Guest", - total: o.subtotal || 0, + total: (o.total_cents || 0) / 100, status: o.status || "unknown", - created_at: formatTimeAgo(o.created_at), + created_at: formatTimeAgo(o.placed_at), })); return { diff --git a/src/actions/shipping.ts b/src/actions/shipping.ts index a6ce67d..323f5e0 100644 --- a/src/actions/shipping.ts +++ b/src/actions/shipping.ts @@ -73,9 +73,9 @@ export async function getShippingOrders(): Promise { }>( `SELECT o.id::text AS id, - c.name AS customer_name, - c.email AS customer_email, - c.phone AS customer_phone, + TRIM(COALESCE(c.first_name, '') || ' ' || COALESCE(c.last_name, '')) AS customer_name, + c.primary_email AS customer_email, + c.primary_phone AS customer_phone, o.status, o.total_cents::float / 100.0 AS subtotal, o.placed_at::text AS created_at,