-- ===================================================== -- Attendance Rollup -- -- RPC that returns a per-participant attendance summary -- for a given course: total sessions, sessions attended, -- and attendance rate (%). -- ===================================================== CREATE OR REPLACE FUNCTION public.get_course_attendance_summary(p_course_id uuid) RETURNS TABLE ( participant_id uuid, participant_name text, enrollment_status public.enrollment_status, total_sessions bigint, sessions_attended bigint, attendance_rate numeric ) LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = '' AS $$ BEGIN -- Access check IF NOT public.has_role_on_account( (SELECT account_id FROM public.courses WHERE id = p_course_id) ) THEN RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501'; END IF; RETURN QUERY WITH session_count AS ( SELECT count(*)::bigint AS cnt FROM public.course_sessions WHERE course_id = p_course_id AND is_cancelled = false ) SELECT cp.id AS participant_id, (cp.first_name || ' ' || cp.last_name)::text AS participant_name, cp.status AS enrollment_status, sc.cnt AS total_sessions, COALESCE(count(ca.id) FILTER (WHERE ca.present = true), 0)::bigint AS sessions_attended, CASE WHEN sc.cnt > 0 THEN ROUND( COALESCE(count(ca.id) FILTER (WHERE ca.present = true), 0)::numeric / sc.cnt * 100, 1 ) ELSE 0 END AS attendance_rate FROM public.course_participants cp CROSS JOIN session_count sc LEFT JOIN public.course_attendance ca ON ca.participant_id = cp.id LEFT JOIN public.course_sessions cs ON cs.id = ca.session_id AND cs.is_cancelled = false WHERE cp.course_id = p_course_id AND cp.status IN ('enrolled', 'completed') GROUP BY cp.id, cp.first_name, cp.last_name, cp.status, sc.cnt ORDER BY cp.last_name, cp.first_name; END; $$; GRANT EXECUTE ON FUNCTION public.get_course_attendance_summary(uuid) TO authenticated; GRANT EXECUTE ON FUNCTION public.get_course_attendance_summary(uuid) TO service_role;