Fix shipping + dashboard + analytics queries against new schema
Deploy to route.crispygoat.com / deploy (push) Successful in 4m48s

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.
This commit is contained in:
Tyler
2026-06-17 11:00:34 -06:00
parent 4b781d3c76
commit a6df4c2dd9
3 changed files with 46 additions and 36 deletions
+1 -1
View File
@@ -284,7 +284,7 @@ export async function getRecentOrders(limit: number = 10): Promise<RecentOrder[]
}>(
`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,
+42 -32
View File
@@ -42,24 +42,22 @@ export async function getDashboardStats(): Promise<DashboardStats> {
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<DashboardStats> {
// 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<DashboardStats> {
);
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<DashboardStats> {
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<DashboardStats> {
.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 {
+3 -3
View File
@@ -73,9 +73,9 @@ export async function getShippingOrders(): Promise<GetShippingOrdersResult> {
}>(
`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,