1feba25ced
* inspect_xlsx.py — one-off xlsx dumper used to discover the Stop Directory sheet structure (14 lines, kept for reference when the tour schedule xlsx is updated next season). * scripts/seed_tuxedo_tour.py — Python+openpyxl seed script that parsed the 3-week Tuxedo Corn 2026 tour schedule and inserted 269 stops via admin_create_stops_batch. Also linked each stop to its Stop Directory venue (address/phone/contact) and self-published by flipping status='draft' -> 'active'. * supabase/migrations/202_delete_stop_orders_guard_fix.sql — fixes a 400 error on the admin Stop Table delete button. The delete_stop RPC referenced o.deleted_at on the orders table, but that column doesn't exist, so the SELECT raised 'column does not exist' and PostgREST surfaced it as HTTP 400. The fix drops the redundant o.deleted_at IS NULL clause (pickup_complete = false is sufficient on its own); re-add it here if/when orders grows a deleted_at column.
71 lines
2.7 KiB
PL/PgSQL
71 lines
2.7 KiB
PL/PgSQL
-- Migration 202: Fix delete_stop RPC — orders table has no deleted_at column
|
|
--
|
|
-- The delete_stop RPC added in migration 075 guards against deleting a stop
|
|
-- that has active orders:
|
|
--
|
|
-- SELECT COUNT(*) INTO v_active_order_count
|
|
-- FROM orders o JOIN stops s ON o.stop_id = s.id
|
|
-- WHERE s.id = p_stop_id
|
|
-- AND o.pickup_complete = false
|
|
-- AND o.deleted_at IS NULL; -- <-- column does not exist on orders
|
|
--
|
|
-- The orders table never got a deleted_at column, so the SELECT raises
|
|
-- "column o.deleted_at does not exist" and the RPC returns HTTP 400 from
|
|
-- PostgREST. Stop deletion has been silently broken since 075 landed.
|
|
--
|
|
-- Fix: drop the o.deleted_at IS NULL clause. The pickup_complete = false
|
|
-- guard is sufficient for now (no soft-deleted orders can exist).
|
|
-- If/when orders get a deleted_at column, re-add the clause.
|
|
--
|
|
-- This migration only touches the function. Schema is unchanged.
|
|
|
|
CREATE OR REPLACE FUNCTION public.delete_stop(p_stop_id UUID, p_brand_id UUID DEFAULT NULL)
|
|
RETURNS JSONB
|
|
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_stop RECORD;
|
|
v_active_order_count INT;
|
|
BEGIN
|
|
-- Lock the stop row for update
|
|
SELECT * INTO v_stop FROM stops WHERE id = p_stop_id FOR UPDATE;
|
|
|
|
IF NOT FOUND THEN
|
|
RETURN jsonb_build_object('success', false, 'error', 'Stop not found');
|
|
END IF;
|
|
|
|
-- Brand scoping: brand_admin can only delete their own stops
|
|
IF p_brand_id IS NOT NULL AND v_stop.brand_id != p_brand_id THEN
|
|
RETURN jsonb_build_object('success', false, 'error', 'Stop not found');
|
|
ELSIF p_brand_id IS NULL AND v_stop.brand_id IS NOT NULL THEN
|
|
-- Caller didn't pass brand_id but stop has one — guard against platform admin accident
|
|
RETURN jsonb_build_object('success', false, 'error', 'Stop not found');
|
|
END IF;
|
|
|
|
-- Guard: refuse to delete a stop that still has active (un-picked-up) orders.
|
|
-- Note: orders has no deleted_at column (hard delete pattern) so we only
|
|
-- check pickup_complete. Re-add `AND o.deleted_at IS NULL` if/when orders
|
|
-- grows a soft-delete column.
|
|
SELECT COUNT(*) INTO v_active_order_count
|
|
FROM orders o
|
|
JOIN stops s ON o.stop_id = s.id
|
|
WHERE s.id = p_stop_id
|
|
AND o.pickup_complete = false;
|
|
|
|
IF v_active_order_count > 0 THEN
|
|
RETURN jsonb_build_object(
|
|
'success', false,
|
|
'error', 'Cannot delete stop — it has ' || v_active_order_count || ' active order(s). Mark orders as picked up first.'
|
|
);
|
|
END IF;
|
|
|
|
-- Soft delete
|
|
UPDATE stops SET deleted_at = NOW() WHERE id = p_stop_id;
|
|
|
|
RETURN jsonb_build_object('success', true);
|
|
END;
|
|
$$;
|
|
|
|
-- Grants are inherited from 075; verify they're present (no-op if so).
|
|
GRANT EXECUTE ON FUNCTION public.delete_stop(UUID, UUID) TO anon, authenticated, service_role;
|