233 lines
8.3 KiB
SQL
233 lines
8.3 KiB
SQL
-- 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;
|