3d4b98d703
Deploy to route.crispygoat.com / deploy (push) Successful in 4m17s
Migration 0040 was written against an assumed schema and referenced
columns that don't exist in the prod orders table:
- orders.created_at → use orders.placed_at
- orders.subtotal → use orders.total_cents / 100
- stops.date regex → column is already DATE, not TEXT, drop the
~ '^\d{4}-...$' check
Migration 0041 fixes both broken functions in place via
CREATE OR REPLACE FUNCTION.
The action layer at src/actions/platform/command-center.ts used
SELECT fn() AS "fn" for all platform RPCs, but two of the three
return TABLE(...) (setof) — the correct syntax is SELECT * FROM fn().
The platformRPC<T> helper now takes a kind: 'scalar' | 'setof' flag
and emits the right SQL.
Verified on prod:
METRICS: { orders_today:0, active_brands:1, active_routes:538, ... }
BRAND_HEALTH: 1 row (Tuxedo Corn), 538 active_stops, 0 failed, healthy schema
ACTIVITY: 0 rows (operational_events has no brand_id in payload, no error)
148 lines
5.2 KiB
PL/PgSQL
148 lines
5.2 KiB
PL/PgSQL
-- Migration 0041: Fix command-center RPCs against actual prod schema
|
|
--
|
|
-- Migration 0040 was written against an assumed schema and references columns
|
|
-- that don't exist in the prod `orders` table:
|
|
-- - `orders.created_at` does not exist → use `orders.placed_at`
|
|
-- - `orders.subtotal` does not exist → use `orders.total_cents / 100`
|
|
-- - `stops.date` is already DATE, not TEXT → drop the `~ '^\d{4}-...' ` regex
|
|
--
|
|
-- All three functions are fixed in place via CREATE OR REPLACE FUNCTION.
|
|
-- The action layer (src/actions/platform/command-center.ts) is updated
|
|
-- separately to use `SELECT * FROM fn()` for the two setof-returning functions.
|
|
--
|
|
-- Reference (prod schema as of 2026-06-17):
|
|
-- orders: id, brand_id, customer_id, stop_id, total_cents INT,
|
|
-- status TEXT, fulfillment TEXT, customer_*,
|
|
-- idempotency_key, notes, placed_at TIMESTAMPTZ, updated_at
|
|
-- stops: id, brand_id, name, location, address, city, state, zip,
|
|
-- date DATE, time, cutoff_date, status, is_public, notes,
|
|
-- created_at, updated_at
|
|
-- operational_events: id, brand_id, event_type, entity_type, entity_id,
|
|
-- actor_type, actor_id, source, payload JSONB, created_at
|
|
-- brands: (assumed present per CLAUDE.md)
|
|
|
|
-- ── Platform-wide metrics ──────────────────────────────────────────────────
|
|
CREATE OR REPLACE FUNCTION get_platform_command_center_metrics()
|
|
RETURNS JSONB
|
|
LANGUAGE plpgsql SECURITY DEFINER
|
|
AS $$
|
|
DECLARE
|
|
result JSONB;
|
|
BEGIN
|
|
result := jsonb_build_object(
|
|
'active_brands', (
|
|
SELECT COUNT(*) FROM brands WHERE slug IN ('tuxedo', 'indian-river-direct')
|
|
),
|
|
'orders_today', (
|
|
SELECT COUNT(*) FROM orders
|
|
WHERE DATE(placed_at) = CURRENT_DATE
|
|
),
|
|
'revenue_today', (
|
|
SELECT COALESCE(SUM(total_cents), 0)::NUMERIC / 100
|
|
FROM orders
|
|
WHERE DATE(placed_at) = CURRENT_DATE
|
|
AND status NOT IN ('cancelled', 'refunded')
|
|
),
|
|
'active_routes', (
|
|
SELECT COUNT(DISTINCT s.id)
|
|
FROM stops s
|
|
WHERE s.date >= CURRENT_DATE
|
|
AND s.status = 'active'
|
|
),
|
|
'failed_orders_today', (
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE DATE(placed_at) = CURRENT_DATE
|
|
AND status IN ('payment_failed', 'failed')
|
|
),
|
|
'pending_orders_today', (
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE DATE(placed_at) = CURRENT_DATE
|
|
AND status = 'pending'
|
|
)
|
|
);
|
|
|
|
RETURN result;
|
|
END;
|
|
$$;
|
|
|
|
-- ── Per-brand health snapshot ──────────────────────────────────────────────
|
|
CREATE OR REPLACE FUNCTION get_brand_health_snapshot()
|
|
RETURNS TABLE (
|
|
brand_id UUID,
|
|
brand_name TEXT,
|
|
brand_slug TEXT,
|
|
plan_tier TEXT,
|
|
orders_today INT,
|
|
revenue_today NUMERIC,
|
|
active_stops INT,
|
|
failed_orders INT,
|
|
pending_orders INT,
|
|
last_activity TIMESTAMPTZ,
|
|
open_pain_items INT,
|
|
health_status TEXT
|
|
)
|
|
LANGUAGE plpgsql SECURITY DEFINER
|
|
AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
WITH bm AS (
|
|
SELECT
|
|
b.id AS bid,
|
|
b.name AS bname,
|
|
b.slug AS bslug,
|
|
COUNT(DISTINCT o.id) FILTER (WHERE DATE(o.placed_at) = CURRENT_DATE) AS ords_today,
|
|
(COALESCE(SUM(o.total_cents) FILTER (
|
|
WHERE DATE(o.placed_at) = CURRENT_DATE
|
|
AND o.status NOT IN ('cancelled', 'refunded')
|
|
), 0)::NUMERIC / 100) AS rev_today,
|
|
COUNT(DISTINCT s.id) FILTER (
|
|
WHERE s.date >= CURRENT_DATE AND s.status = 'active'
|
|
) AS act_stops,
|
|
COUNT(DISTINCT o.id) FILTER (WHERE DATE(o.placed_at) = CURRENT_DATE AND o.status IN ('payment_failed', 'failed')) AS fail_ords,
|
|
COUNT(DISTINCT o.id) FILTER (WHERE DATE(o.placed_at) = CURRENT_DATE AND o.status = 'pending') AS pend_ords,
|
|
MAX(oe.created_at) AS last_act
|
|
FROM brands b
|
|
LEFT JOIN orders o ON o.brand_id = b.id
|
|
LEFT JOIN stops s ON s.brand_id = b.id
|
|
LEFT JOIN operational_events oe
|
|
ON oe.payload->>'brand_id' = b.id::TEXT
|
|
AND oe.payload->>'brand_id' ~ '^[0-9a-fA-F-]{36}$'
|
|
WHERE b.slug IN ('tuxedo', 'indian-river-direct')
|
|
GROUP BY b.id, b.name, b.slug
|
|
),
|
|
pc AS (
|
|
SELECT f.brand_id AS pc_bid, COUNT(*) AS open_cnt
|
|
FROM founder_pain_log f
|
|
WHERE f.status = 'open'
|
|
GROUP BY f.brand_id
|
|
)
|
|
SELECT
|
|
bm.bid,
|
|
bm.bname,
|
|
bm.bslug,
|
|
NULL::TEXT,
|
|
COALESCE(bm.ords_today, 0)::INT,
|
|
COALESCE(bm.rev_today, 0)::NUMERIC,
|
|
COALESCE(bm.act_stops, 0)::INT,
|
|
COALESCE(bm.fail_ords, 0)::INT,
|
|
COALESCE(bm.pend_ords, 0)::INT,
|
|
bm.last_act,
|
|
COALESCE(pc.open_cnt, 0)::INT,
|
|
CASE
|
|
WHEN COALESCE(bm.fail_ords, 0) > 0 THEN 'critical'
|
|
WHEN COALESCE(bm.pend_ords, 0) > 5 THEN 'warning'
|
|
WHEN COALESCE(pc.open_cnt, 0) > 0 THEN 'warning'
|
|
WHEN bm.last_act IS NULL OR bm.last_act < (NOW() - INTERVAL '24 hours') THEN 'warning'
|
|
ELSE 'healthy'
|
|
END
|
|
FROM bm
|
|
LEFT JOIN pc ON pc.pc_bid = bm.bid;
|
|
END;
|
|
$$;
|
|
|
|
-- get_platform_activity_feed from 0040 is already correct against the prod
|
|
-- schema (operational_events.payload->>'brand_id' is a text→UUID cast that
|
|
-- nulls out on missing keys). No body change required.
|