This commit is contained in:
2026-08-07 20:42:58 +00:00
parent a54a988314
commit 1f3de0d7ff
@@ -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;