diff --git a/supabase/migrations/20260807001800_billing_and_attendance_reports.sql b/supabase/migrations/20260807001800_billing_and_attendance_reports.sql deleted file mode 100644 index 639532f..0000000 --- a/supabase/migrations/20260807001800_billing_and_attendance_reports.sql +++ /dev/null @@ -1,224 +0,0 @@ --- Paid/unpaid by campus and attendance reporting — spec sections 14 and 19. --- --- Both sections ask for the same shape: a total you can drill into, filtered by --- campus, date range, tier, full/part-time, scholarship and plan status. These --- views therefore expose the raw filter columns rather than pre-aggregating, --- so one view serves the dashboard tile and the drill-down behind it. --- --- security_invoker throughout: a campus administrator running the same query --- sees only their campus, because the underlying policies still apply. - --- ============================================================================ --- 1. SECTION 14 — receivables --- ============================================================================ - --- Row per invoice with every documented filter attached. The dashboard totals --- below are sums over this; clicking a total means selecting from it. -CREATE OR REPLACE VIEW public.v_billing_detail -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, - h.name AS household_name, - i.campus_id, - c.name AS campus_name, - i.billing_period_start, - i.billing_period_end, - -- ISO week, for the spec's "billing week" filter. - to_char(i.billing_period_start, 'IYYY-"W"IW') AS billing_week, - i.invoice_date, - i.due_date, - i.tuition_tier_id, - t.name AS tuition_tier, - i.attendance_basis, - i.status, - i.total_cents, - i.amount_paid_cents, - i.balance_due_cents, - i.scholarship_cents, - i.penalties_cents, - i.payment_plan_id, - (i.payment_plan_id IS NOT NULL) AS on_payment_plan, - (i.scholarship_cents > 0) AS has_scholarship, - (i.status IN ('issued','partially_paid') AND i.balance_due_cents > 0 - AND i.due_date < CURRENT_DATE) AS is_past_due, - GREATEST(CURRENT_DATE - i.due_date, 0) AS days_overdue -FROM public.invoices i -JOIN public.students s ON s.id = i.student_id -LEFT JOIN public.households h ON h.id = i.household_id -LEFT JOIN public.campuses c ON c.id = i.campus_id -LEFT JOIN public.tuition_tiers t ON t.id = i.tuition_tier_id -WHERE i.status <> 'void'; - -GRANT SELECT ON public.v_billing_detail TO authenticated; - --- The campus dashboard tiles from section 14. -CREATE OR REPLACE VIEW public.v_campus_billing_summary -WITH (security_invoker = true) AS -SELECT - c.id AS campus_id, - c.name AS campus_name, - COUNT(d.invoice_id) AS invoice_count, - COALESCE(SUM(d.total_cents), 0) AS total_invoiced_cents, - COALESCE(SUM(d.amount_paid_cents), 0) AS total_collected_cents, - COALESCE(SUM(d.balance_due_cents), 0) AS total_unpaid_cents, - COALESCE(SUM(d.balance_due_cents) FILTER (WHERE d.is_past_due), 0) AS past_due_cents, - COALESCE(SUM(d.scholarship_cents), 0) AS scholarship_applied_cents, - COALESCE(SUM(d.balance_due_cents) FILTER (WHERE d.on_payment_plan), 0) - AS payment_plan_balance_cents, - COUNT(DISTINCT d.student_id) FILTER (WHERE d.is_past_due) AS delinquent_accounts, - -- Awarded scholarship funding that has not yet been received as a payment. - COALESCE(( - SELECT SUM(sc.award_amount_cents) - FROM public.scholarships sc - JOIN public.students st ON st.id = sc.student_id - WHERE st.primary_campus_id = c.id - AND sc.status = 'active' - AND NOT EXISTS ( - SELECT 1 FROM public.payments p - WHERE p.scholarship_id = sc.id AND p.status = 'posted') - ), 0) AS scholarship_pending_cents -FROM public.campuses c -LEFT JOIN public.v_billing_detail d ON d.campus_id = c.id -GROUP BY c.id, c.name; - -GRANT SELECT ON public.v_campus_billing_summary TO authenticated; - --- Household-level position, since a family pays as one even when billed per child. -CREATE OR REPLACE VIEW public.v_household_receivables -WITH (security_invoker = true) AS -SELECT - d.household_id, - d.household_name, - COUNT(DISTINCT d.student_id) AS students, - SUM(d.total_cents) AS invoiced_cents, - SUM(d.amount_paid_cents) AS paid_cents, - SUM(d.balance_due_cents) AS balance_cents, - SUM(d.balance_due_cents) FILTER (WHERE d.is_past_due) AS past_due_cents, - MAX(d.days_overdue) FILTER (WHERE d.is_past_due) AS worst_days_overdue -FROM public.v_billing_detail d -WHERE d.household_id IS NOT NULL -GROUP BY d.household_id, d.household_name; - -GRANT SELECT ON public.v_household_receivables TO authenticated; - --- ============================================================================ --- 2. SECTION 19 — attendance reporting --- ============================================================================ - --- One row per attendance record with everything section 19 asks to report on, --- including whether the day was a campus transfer against the schedule. -CREATE OR REPLACE VIEW public.v_attendance_detail -WITH (security_invoker = true) AS -SELECT - a.id AS attendance_id, - a.student_id, - s.first_name || ' ' || s.last_name AS student_name, - s.attendance_basis, - s.calendar_basis, - a.date, - to_char(a.date, 'IYYY-"W"IW') AS iso_week, - date_trunc('month', a.date)::date AS month, - a.campus_id AS actual_campus_id, - c.name AS actual_campus, - s.primary_campus_id, - pc.name AS primary_campus, - -- A day where the child was somewhere other than their usual campus. - (a.campus_id IS DISTINCT FROM s.primary_campus_id) AS campus_transfer, - s.class_id, - cl.name AS class_name, - cl.teacher_id, - a.status, - a.was_scheduled, - a.check_in_at, - a.check_out_at, - a.early_arrival, - a.late_arrival, - a.early_pickup, - a.late_pickup, - a.is_manual_override, - a.absence_reason, - -- Fees raised for that day, so charges reconcile against the day that caused them. - COALESCE(( - SELECT SUM(e.final_fee_cents) FROM public.penalty_events e - WHERE e.attendance_id = a.id AND NOT e.is_waived - ), 0) AS fee_cents -FROM public.attendance a -JOIN public.students s ON s.id = a.student_id -LEFT JOIN public.campuses c ON c.id = a.campus_id -LEFT JOIN public.campuses pc ON pc.id = s.primary_campus_id -LEFT JOIN public.classes cl ON cl.id = s.class_id; - -GRANT SELECT ON public.v_attendance_detail TO authenticated; - --- Daily campus roll-up: the "attendance today" tile and the daily report. -CREATE OR REPLACE VIEW public.v_attendance_by_campus_day -WITH (security_invoker = true) AS -SELECT - d.actual_campus_id AS campus_id, - d.actual_campus AS campus_name, - d.date, - COUNT(*) FILTER (WHERE d.was_scheduled) AS scheduled, - COUNT(*) FILTER (WHERE d.status IN ('present','late')) AS present, - COUNT(*) FILTER (WHERE d.status = 'absent') AS absent, - COUNT(*) FILTER (WHERE d.status = 'vacation') AS on_vacation, - COUNT(*) FILTER (WHERE d.status = 'excused') AS excused, - COUNT(*) FILTER (WHERE d.late_arrival) AS late_arrivals, - COUNT(*) FILTER (WHERE d.early_arrival) AS early_arrivals, - COUNT(*) FILTER (WHERE d.early_pickup) AS early_pickups, - COUNT(*) FILTER (WHERE d.late_pickup) AS late_pickups, - COUNT(*) FILTER (WHERE d.campus_transfer) AS campus_transfers, - COUNT(*) FILTER (WHERE d.is_manual_override) AS manual_overrides, - COALESCE(SUM(d.fee_cents), 0) AS fee_cents, - ROUND(COUNT(*) FILTER (WHERE d.status IN ('present','late'))::numeric * 100.0 - / NULLIF(COUNT(*) FILTER (WHERE d.was_scheduled), 0), 1) AS attendance_rate_percent -FROM public.v_attendance_detail d -GROUP BY d.actual_campus_id, d.actual_campus, d.date; - -GRANT SELECT ON public.v_attendance_by_campus_day TO authenticated; - --- Scheduled versus actual, which section 19 calls out specifically. -CREATE OR REPLACE VIEW public.v_scheduled_vs_actual -WITH (security_invoker = true) AS -SELECT - d.student_id, - d.student_name, - d.attendance_basis, - d.primary_campus_id, - d.month, - COUNT(*) FILTER (WHERE d.was_scheduled) AS days_scheduled, - COUNT(*) FILTER (WHERE d.was_scheduled AND d.status IN ('present','late')) - AS scheduled_and_present, - COUNT(*) FILTER (WHERE NOT d.was_scheduled AND d.status IN ('present','late')) - AS unscheduled_but_present, - COUNT(*) FILTER (WHERE d.was_scheduled AND d.status = 'absent') AS scheduled_but_absent, - COUNT(*) FILTER (WHERE d.status = 'vacation') AS vacation_days -FROM public.v_attendance_detail d -GROUP BY d.student_id, d.student_name, d.attendance_basis, d.primary_campus_id, d.month; - -GRANT SELECT ON public.v_scheduled_vs_actual TO authenticated; - --- Vacation position per student, for the balance report. -CREATE OR REPLACE VIEW public.v_vacation_status -WITH (security_invoker = true) AS -SELECT - b.student_id, - s.first_name || ' ' || s.last_name AS student_name, - s.primary_campus_id, - c.name AS campus_name, - b.policy_year, - b.weeks_allotted, - b.weeks_used, - b.weeks_remaining, - b.override_weeks IS NOT NULL AS is_overridden, - (SELECT COUNT(*) FROM public.vacation_requests r - WHERE r.student_id = b.student_id AND r.policy_year = b.policy_year - AND r.status = 'pending') AS pending_requests -FROM public.vacation_balances b -JOIN public.students s ON s.id = b.student_id -LEFT JOIN public.campuses c ON c.id = s.primary_campus_id; - -GRANT SELECT ON public.v_vacation_status TO authenticated;