This commit is contained in:
2026-08-22 23:11:02 +00:00
parent 3978e5980c
commit c592c8b6b7
@@ -1,232 +0,0 @@
-- Reporting and dashboards — spec section 14.
--
-- Every view is declared WITH (security_invoker = true). Without it a view runs
-- with the privileges of its owner and would hand any authenticated caller the
-- whole table, quietly defeating the row-level security the rest of this build
-- depends on. With it, a teacher querying v_student_directory sees exactly the
-- students their own policies allow.
-- ============================================================================
-- STUDENTS
-- ============================================================================
CREATE OR REPLACE VIEW public.v_student_directory
WITH (security_invoker = true) AS
SELECT
s.id AS student_id,
s.first_name,
s.middle_name,
s.last_name,
s.preferred_name,
s.dob,
s.grade_level,
s.enrollment_status,
s.attendance_basis,
s.calendar_basis,
s.is_support_student,
c.id AS primary_campus_id,
c.name AS primary_campus,
t.name AS tuition_tier,
hs.household_id,
h.name AS household_name,
-- Campuses beyond the primary one, for the multi-campus case.
(SELECT array_agg(DISTINCT cc.name)
FROM public.student_campus_schedules sc
JOIN public.campuses cc ON cc.id = sc.campus_id
WHERE sc.student_id = s.id
AND (sc.effective_end IS NULL OR sc.effective_end >= CURRENT_DATE)
) AS scheduled_campuses,
(SELECT COALESCE(SUM(sc.scheduled_days_per_week), 0)
FROM public.student_campus_schedules sc
WHERE sc.student_id = s.id
AND sc.effective_start <= CURRENT_DATE
AND (sc.effective_end IS NULL OR sc.effective_end >= CURRENT_DATE)
) AS scheduled_days_per_week,
(SELECT COUNT(*) FROM public.student_alerts a
WHERE a.student_id = s.id AND a.is_active
AND a.effective_date <= CURRENT_DATE
AND (a.expiration_date IS NULL OR a.expiration_date >= CURRENT_DATE)
) AS active_alert_count
FROM public.students s
LEFT JOIN public.campuses c ON c.id = s.primary_campus_id
LEFT JOIN public.tuition_tiers t ON t.id = s.tuition_tier_id
LEFT JOIN public.household_students hs
ON hs.student_id = s.id AND hs.is_primary_household
LEFT JOIN public.households h ON h.id = hs.household_id;
GRANT SELECT ON public.v_student_directory TO authenticated;
-- ============================================================================
-- BILLING
-- ============================================================================
CREATE OR REPLACE VIEW public.v_outstanding_invoices
WITH (security_invoker = true) AS
SELECT
i.id AS invoice_id,
i.invoice_number,
i.student_id,
s.first_name || ' ' || s.last_name AS student_name,
i.household_id,
i.campus_id,
c.name AS campus_name,
i.billing_period_start,
i.billing_period_end,
i.invoice_date,
i.due_date,
i.total_cents,
i.amount_paid_cents,
i.balance_due_cents,
i.status,
CURRENT_DATE - i.due_date AS days_overdue
FROM public.invoices i
JOIN public.students s ON s.id = i.student_id
LEFT JOIN public.campuses c ON c.id = i.campus_id
WHERE i.status IN ('issued','partially_paid')
AND i.balance_due_cents > 0;
GRANT SELECT ON public.v_outstanding_invoices TO authenticated;
-- Combined household position — the spec's "combined household account".
CREATE OR REPLACE VIEW public.v_household_balances
WITH (security_invoker = true) AS
SELECT
h.id AS household_id,
h.name AS household_name,
COUNT(DISTINCT hs.student_id) AS student_count,
COALESCE(SUM(i.total_cents), 0) AS invoiced_cents,
COALESCE(SUM(i.amount_paid_cents), 0) AS paid_cents,
COALESCE(SUM(i.balance_due_cents) FILTER (
WHERE i.status IN ('issued','partially_paid')), 0) AS balance_due_cents
FROM public.households h
LEFT JOIN public.household_students hs ON hs.household_id = h.id
LEFT JOIN public.invoices i ON i.household_id = h.id AND i.status <> 'void'
GROUP BY h.id, h.name;
GRANT SELECT ON public.v_household_balances TO authenticated;
-- ============================================================================
-- CAMPUS + ATTENDANCE
-- ============================================================================
CREATE OR REPLACE VIEW public.v_campus_enrollment
WITH (security_invoker = true) AS
SELECT
c.id AS campus_id,
c.name AS campus_name,
c.is_active,
c.student_capacity,
COUNT(DISTINCT s.id) FILTER (WHERE s.enrollment_status = 'enrolled') AS enrolled_count,
COUNT(DISTINCT s.id) FILTER (
WHERE s.enrollment_status = 'enrolled' AND s.attendance_basis = 'full_time') AS full_time_count,
COUNT(DISTINCT s.id) FILTER (
WHERE s.enrollment_status = 'enrolled' AND s.attendance_basis = 'part_time') AS part_time_count,
COUNT(DISTINCT s.id) FILTER (
WHERE s.enrollment_status = 'enrolled' AND s.is_support_student) AS support_count,
CASE WHEN c.student_capacity IS NULL OR c.student_capacity = 0 THEN NULL
ELSE ROUND(COUNT(DISTINCT s.id) FILTER (WHERE s.enrollment_status = 'enrolled')::numeric
* 100.0 / c.student_capacity, 1)
END AS capacity_used_percent
FROM public.campuses c
LEFT JOIN public.students s ON s.primary_campus_id = c.id
GROUP BY c.id, c.name, c.is_active, c.student_capacity;
GRANT SELECT ON public.v_campus_enrollment TO authenticated;
CREATE OR REPLACE VIEW public.v_attendance_summary
WITH (security_invoker = true) AS
SELECT
a.student_id,
s.first_name || ' ' || s.last_name AS student_name,
s.primary_campus_id,
date_trunc('month', a.date)::date AS month,
COUNT(*) AS recorded_days,
COUNT(*) FILTER (WHERE a.status = 'present') AS present_days,
COUNT(*) FILTER (WHERE a.status = 'absent') AS absent_days,
COUNT(*) FILTER (WHERE a.status = 'late') AS late_days,
COUNT(*) FILTER (WHERE a.status = 'excused') AS excused_days,
ROUND(COUNT(*) FILTER (WHERE a.status IN ('present','late'))::numeric
* 100.0 / NULLIF(COUNT(*), 0), 1) AS attendance_rate_percent
FROM public.attendance a
JOIN public.students s ON s.id = a.student_id
GROUP BY a.student_id, s.first_name, s.last_name, s.primary_campus_id, date_trunc('month', a.date);
GRANT SELECT ON public.v_attendance_summary TO authenticated;
-- ============================================================================
-- COMPLIANCE + MANAGEMENT
-- ============================================================================
CREATE OR REPLACE VIEW public.v_certification_expiry
WITH (security_invoker = true) AS
SELECT
sc.id AS certification_id,
sc.user_id,
p.full_name AS staff_name,
ct.name AS certification_name,
sc.issued_on,
sc.expires_on,
sc.status,
sc.expires_on - CURRENT_DATE AS days_until_expiry,
CASE
WHEN sc.expires_on IS NULL THEN 'no_expiry'
WHEN sc.expires_on < CURRENT_DATE THEN 'expired'
WHEN sc.expires_on < CURRENT_DATE + make_interval(days => ct.renewal_reminder_days)
THEN 'expiring_soon'
ELSE 'current'
END AS expiry_state
FROM public.staff_certifications sc
JOIN public.certification_types ct ON ct.id = sc.certification_type_id
LEFT JOIN public.profiles p ON p.id = sc.user_id
WHERE sc.status IN ('active','pending');
GRANT SELECT ON public.v_certification_expiry TO authenticated;
-- The management dashboard's task queue.
CREATE OR REPLACE VIEW public.v_open_tasks
WITH (security_invoker = true) AS
SELECT
t.id AS task_id,
t.title,
t.priority,
t.status,
t.due_date,
t.campus_id,
c.name AS campus_name,
t.assigned_to,
p.full_name AS assigned_to_name,
t.related_student_id,
CASE
WHEN t.due_date IS NULL THEN 'no_due_date'
WHEN t.due_date < CURRENT_DATE THEN 'overdue'
WHEN t.due_date <= CURRENT_DATE + 7 THEN 'due_soon'
ELSE 'scheduled'
END AS due_state
FROM public.tasks t
LEFT JOIN public.campuses c ON c.id = t.campus_id
LEFT JOIN public.profiles p ON p.id = t.assigned_to
WHERE t.status IN ('open','in_progress','blocked');
GRANT SELECT ON public.v_open_tasks TO authenticated;
-- Per-student follow-ups still owed, from the notes system.
CREATE OR REPLACE VIEW public.v_pending_followups
WITH (security_invoker = true) AS
SELECT
n.id AS note_id,
n.student_id,
s.first_name || ' ' || s.last_name AS student_name,
nc.name AS category,
n.title,
n.follow_up_date,
n.resolution_status,
n.author_id,
n.campus_id,
CURRENT_DATE - n.follow_up_date AS days_overdue
FROM public.student_notes n
JOIN public.students s ON s.id = n.student_id
JOIN public.note_categories nc ON nc.id = n.category_id
WHERE n.follow_up_date IS NOT NULL
AND n.resolution_status IN ('open','in_progress');
GRANT SELECT ON public.v_pending_followups TO authenticated;