From d913ca194e4fc91d265c19381449642677215518 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 17 Jun 2026 15:17:51 -0600 Subject: [PATCH] feat(db): add get_dashboard_summary RPC for v2 dashboard --- db/migrations/0091_dashboard_summary_rpc.sql | 89 ++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 db/migrations/0091_dashboard_summary_rpc.sql diff --git a/db/migrations/0091_dashboard_summary_rpc.sql b/db/migrations/0091_dashboard_summary_rpc.sql new file mode 100644 index 0000000..54cdd0b --- /dev/null +++ b/db/migrations/0091_dashboard_summary_rpc.sql @@ -0,0 +1,89 @@ +-- 0091_dashboard_summary_rpc.sql +-- +-- Dashboard summary RPC for the mobile-first admin v2 dashboard +-- (`/admin/v2`). +-- +-- Returns a single JSONB document that powers the four stat cards + +-- the "Today's stops" timeline + the 7-day mini-chart: +-- +-- { +-- "orders_today": , -- count of orders placed today +-- "revenue_today": , -- sum of total_cents (excl. canceled) +-- "pending_fulfillment": , -- count of orders awaiting pickup/ship +-- "stops_today": , -- count of stops scheduled for today +-- "orders_last_7_days": [{date, count}] -- 7-element array, oldest → today +-- } +-- +-- Schema notes (vs. the plan's draft): +-- * `orders` uses `placed_at` (TIMESTAMPTZ) — not `created_at` +-- * `orders.status` is the legacy enum: 'pending' | 'confirmed' | +-- 'fulfilled' | 'canceled' (not the v2 vocabulary 'placed'/'ready') +-- * `orders.total_cents` is INTEGER (NOT NULL DEFAULT 0 in 0001) — no +-- column-add step is required +-- * `stops` has a `date DATE` column (not `scheduled_at`); the legacy +-- stops page (and v2 stops page) both key off `stops.date` +-- +-- SECURITY DEFINER so the v2 server action can call it without depending +-- on RLS; brand scoping is enforced inside the function via the +-- `p_brand_id` parameter (NULL → platform_admin "all brands" scope). +-- Re-runs are safe — this is `CREATE OR REPLACE FUNCTION`. + +CREATE OR REPLACE FUNCTION get_dashboard_summary(p_brand_id UUID) +RETURNS JSONB +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + result JSONB; + brand_filter TEXT; +BEGIN + -- The brand filter is the same text fragment for every SELECT in this + -- function. NULL = "all brands" (platform_admin scope); non-NULL = + -- scope to a single brand. + IF p_brand_id IS NULL THEN + brand_filter := ''; + ELSE + brand_filter := format(' AND brand_id = %L', p_brand_id); + END IF; + + EXECUTE format( + $q$ + SELECT jsonb_build_object( + 'orders_today', ( + SELECT COUNT(*) FROM orders + WHERE placed_at::date = CURRENT_DATE %s + ), + 'revenue_today', ( + SELECT COALESCE(SUM(total_cents), 0)::bigint FROM orders + WHERE placed_at::date = CURRENT_DATE + AND status <> 'canceled' %s + ), + 'pending_fulfillment', ( + SELECT COUNT(*) FROM orders + WHERE status IN ('pending', 'confirmed') %s + ), + 'stops_today', ( + SELECT COUNT(*) FROM stops + WHERE "date" = CURRENT_DATE %s + ), + 'orders_last_7_days', ( + SELECT COALESCE(jsonb_agg(jsonb_build_object('date', d::date, 'count', COALESCE(o.cnt, 0)) ORDER BY d), '[]'::jsonb) + FROM generate_series(CURRENT_DATE - INTERVAL '6 days', CURRENT_DATE, '1 day') d + LEFT JOIN ( + SELECT placed_at::date AS day, COUNT(*) AS cnt + FROM orders + WHERE placed_at >= CURRENT_DATE - INTERVAL '7 days' %s + GROUP BY 1 + ) o ON o.day = d::date + ) + ) + $q$, + brand_filter, brand_filter, brand_filter, brand_filter, brand_filter + ) INTO result; + + RETURN result; +END; +$$; + +COMMENT ON FUNCTION get_dashboard_summary(UUID) IS + 'Returns a single JSONB document with the four dashboard stat counts + 7-day order history. p_brand_id = NULL means platform_admin scope (all brands).';