-- 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.