From 1f3de0d7ff6f4c7905b680bdf63aece5a64b24f9 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 20:42:58 +0000 Subject: [PATCH] Terminal --- .../20260807002000_reporting_view_fixes.sql | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 supabase/migrations/20260807002000_reporting_view_fixes.sql diff --git a/supabase/migrations/20260807002000_reporting_view_fixes.sql b/supabase/migrations/20260807002000_reporting_view_fixes.sql new file mode 100644 index 0000000..2d12ee4 --- /dev/null +++ b/supabase/migrations/20260807002000_reporting_view_fixes.sql @@ -0,0 +1,82 @@ +-- Two honesty fixes to the section 14 reporting views. +-- +-- Both are about a number that reads as a fact when it is really an absence. +-- Neither changes what anyone is permitted to see. + +-- ============================================================================ +-- 1. A household with nothing past due owes zero, not "unknown" +-- ============================================================================ +-- +-- SUM(...) FILTER over no matching rows returns NULL, so past_due_cents came +-- back NULL for any household that is simply up to date. v_campus_billing_summary +-- already wraps the identical expression in COALESCE; this brings the household +-- view into line so callers can add the two together without special-casing. +-- +-- worst_days_overdue is deliberately left NULL: there is no meaningful "zero +-- days overdue" for an invoice that was never overdue, and 0 would read as +-- "due today". +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, + COALESCE(SUM(d.balance_due_cents) FILTER (WHERE d.is_past_due), 0) 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. Unfunded scholarship money: absent is not the same as zero +-- ============================================================================ +-- +-- scholarship_pending_cents reads public.scholarships and public.payments, +-- whose RLS admits only billing admins, auditors and a student's own parents. +-- Under security_invoker a campus administrator's subquery therefore matched +-- nothing and the tile rendered $0 — indistinguishable from "all funding +-- received", which is the opposite of the truth and the more reassuring of the +-- two readings. +-- +-- The column now returns NULL for anyone who cannot see scholarship data, so +-- the dashboard can render "—" and say nothing rather than something false. +-- The gate mirrors the scholarships SELECT policy exactly; widening who may see +-- this number is a policy decision, not a reporting one, and is not made here. +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, + CASE + WHEN public.is_billing_admin() OR public.is_auditor() THEN + 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) + ELSE NULL + END 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;