101 lines
2.9 KiB
PL/PgSQL
101 lines
2.9 KiB
PL/PgSQL
-- Migration: Enhanced member search and quick stats
|
|
-- Adds: full-text search index, quick stats RPC, next member number function
|
|
|
|
-- Full-text search index (German) for faster member search
|
|
CREATE INDEX IF NOT EXISTS ix_members_fulltext ON public.members
|
|
USING gin(
|
|
to_tsvector(
|
|
'german',
|
|
coalesce(first_name, '') || ' ' ||
|
|
coalesce(last_name, '') || ' ' ||
|
|
coalesce(email, '') || ' ' ||
|
|
coalesce(member_number, '') || ' ' ||
|
|
coalesce(city, '')
|
|
)
|
|
);
|
|
|
|
-- Trigram index on names for fuzzy / ILIKE search
|
|
CREATE INDEX IF NOT EXISTS ix_members_name_trgm
|
|
ON public.members
|
|
USING gin ((lower(first_name || ' ' || last_name)) gin_trgm_ops);
|
|
|
|
-- Quick stats RPC — returns a single row with KPI counts
|
|
-- Includes has_role_on_account guard to prevent cross-tenant data leaks
|
|
CREATE OR REPLACE FUNCTION public.get_member_quick_stats(p_account_id uuid)
|
|
RETURNS TABLE(
|
|
total bigint,
|
|
active bigint,
|
|
inactive bigint,
|
|
pending bigint,
|
|
resigned bigint,
|
|
new_this_year bigint,
|
|
pending_applications bigint
|
|
)
|
|
LANGUAGE plpgsql STABLE SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
BEGIN
|
|
-- Verify caller has access to this account
|
|
IF NOT public.has_role_on_account(p_account_id) THEN
|
|
RAISE EXCEPTION 'Access denied to account %', p_account_id;
|
|
END IF;
|
|
|
|
RETURN QUERY
|
|
SELECT
|
|
count(*)::bigint AS total,
|
|
count(*) FILTER (WHERE m.status = 'active')::bigint AS active,
|
|
count(*) FILTER (WHERE m.status = 'inactive')::bigint AS inactive,
|
|
count(*) FILTER (WHERE m.status = 'pending')::bigint AS pending,
|
|
count(*) FILTER (WHERE m.status = 'resigned')::bigint AS resigned,
|
|
count(*) FILTER (WHERE m.status = 'active'
|
|
AND m.entry_date >= date_trunc('year', current_date)::date)::bigint AS new_this_year,
|
|
(
|
|
SELECT count(*)
|
|
FROM public.membership_applications a
|
|
WHERE a.account_id = p_account_id
|
|
AND a.status = 'submitted'
|
|
)::bigint AS pending_applications
|
|
FROM public.members m
|
|
WHERE m.account_id = p_account_id;
|
|
END;
|
|
$$;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.get_member_quick_stats(uuid) TO authenticated;
|
|
|
|
-- Next member number: returns max(member_number) + 1 as text
|
|
-- Includes has_role_on_account guard
|
|
CREATE OR REPLACE FUNCTION public.get_next_member_number(p_account_id uuid)
|
|
RETURNS text
|
|
LANGUAGE plpgsql STABLE SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_result text;
|
|
BEGIN
|
|
-- Verify caller has access to this account
|
|
IF NOT public.has_role_on_account(p_account_id) THEN
|
|
RAISE EXCEPTION 'Access denied to account %', p_account_id;
|
|
END IF;
|
|
|
|
SELECT LPAD(
|
|
(COALESCE(
|
|
MAX(
|
|
CASE
|
|
WHEN member_number ~ '^\d+$' THEN member_number::integer
|
|
ELSE 0
|
|
END
|
|
),
|
|
0
|
|
) + 1)::text,
|
|
4,
|
|
'0'
|
|
) INTO v_result
|
|
FROM public.members
|
|
WHERE account_id = p_account_id;
|
|
|
|
RETURN v_result;
|
|
END;
|
|
$$;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.get_next_member_number(uuid) TO authenticated;
|