diff --git a/supabase/migrations/20260807002100_billing_views_student_names.sql b/supabase/migrations/20260807002100_billing_views_student_names.sql deleted file mode 100644 index 15d5a7f..0000000 --- a/supabase/migrations/20260807002100_billing_views_student_names.sql +++ /dev/null @@ -1,173 +0,0 @@ --- The rest of the billing views had the same fault as v_billing_detail. --- --- 20260807002000 fixed v_billing_detail, but three sibling views still inner --- joined public.students purely to build a display name. A billing administrator --- can read every invoice, payment and ledger entry in the organisation and no --- student rows at all, so that join silently emptied each of them: --- --- v_outstanding_invoices 0 rows, while public.invoices showed 5 --- v_student_ledger 0 rows, while public.payments showed 1 --- --- Both now resolve the name through student_display_name(), which re-checks --- entitlement inside its SECURITY DEFINER body. Student row access is unchanged; --- only the name lookup moved. v_payment_detail is added for the same reason — --- the receivables screen was reaching for payments.students(...) directly and --- rendering "—" for every payer. - --- ============================================================================ --- 1. Outstanding invoices --- ============================================================================ -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, - public.student_display_name(i.student_id) 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 -LEFT JOIN public.campuses c ON c.id = i.campus_id -WHERE i.status = ANY (ARRAY['issued'::text, 'partially_paid'::text]) - AND i.balance_due_cents > 0; - -GRANT SELECT ON public.v_outstanding_invoices TO authenticated; - --- ============================================================================ --- 2. Payments, with the payer named --- ============================================================================ -CREATE OR REPLACE VIEW public.v_payment_detail -WITH (security_invoker = true) AS -SELECT - p.id AS payment_id, - p.student_id, - public.student_display_name(p.student_id) AS student_name, - p.household_id, - p.campus_id, - c.name AS campus_name, - p.kind, - p.method, - p.amount_cents, - p.received_at, - p.effective_date, - p.reference_number, - p.payer_name, - p.third_party_name, - p.scholarship_id, - p.status, - p.reverses_payment_id, - p.void_reason, - p.notes, - p.received_by -FROM public.payments p -LEFT JOIN public.campuses c ON c.id = p.campus_id; - -GRANT SELECT ON public.v_payment_detail TO authenticated; - --- ============================================================================ --- 3. Student ledger --- ============================================================================ --- Reproduced from the deployed definition with only the trailing students join --- removed, so the running-balance window and the four-way UNION behind it are --- untouched. -CREATE OR REPLACE VIEW public.v_student_ledger -WITH (security_invoker = true) AS - WITH events AS ( - SELECT i.student_id, - i.campus_id, - i.invoice_date AS txn_date, - i.billing_period_start AS effective_date, - i.created_at AS sort_ts, - 'invoice'::text AS source, - 'charge'::text AS direction, - i.total_cents AS amount_cents, - i.invoice_number AS reference, - NULL::text AS method, - i.issued_by AS entered_by, - i.id AS ref_id - FROM invoices i - WHERE i.status <> 'void'::text - UNION ALL - SELECT p.student_id, - p.campus_id, - p.received_at::date AS received_at, - p.effective_date, - p.created_at, - 'payment'::text, - CASE - WHEN p.kind = 'payment'::text THEN 'credit'::text - ELSE 'charge'::text - END AS "case", - CASE - WHEN p.kind = 'payment'::text THEN - p.amount_cents - ELSE p.amount_cents - END AS amount_cents, - p.reference_number, - p.method, - p.received_by, - p.id - FROM payments p - WHERE p.status <> 'voided'::text AND p.student_id IS NOT NULL - UNION ALL - SELECT c.student_id, - NULL::uuid AS uuid, - c.issued_at::date AS issued_at, - c.issued_at::date AS issued_at, - c.issued_at, - 'credit'::text, - 'credit'::text, - - c.amount_cents, - NULL::text, - NULL::text, - c.issued_by, - c.id - FROM student_credits c - WHERE NOT c.is_void - UNION ALL - SELECT l.student_id, - NULL::uuid AS uuid, - l.date, - l.date, - l.created_at, - 'ledger_entry'::text, - CASE - WHEN l.kind = 'charge'::ledger_kind THEN 'charge'::text - ELSE 'credit'::text - END AS "case", - CASE - WHEN l.kind = 'charge'::ledger_kind THEN l.amount_cents - ELSE - l.amount_cents - END AS "case", - NULL::text, - l.category::text AS category, - l.created_by, - l.id - FROM ledger_entries l - ) - SELECT e.student_id, - e.campus_id, - e.txn_date, - e.effective_date, - e.sort_ts, - e.source, - e.direction, - e.amount_cents, - e.reference, - e.method, - e.entered_by, - e.ref_id, - public.student_display_name(e.student_id) AS student_name, - sum(e.amount_cents) OVER (PARTITION BY e.student_id ORDER BY e.effective_date, e.sort_ts, e.ref_id ROWS UNBOUNDED PRECEDING) AS running_balance_cents - FROM events e; - -GRANT SELECT ON public.v_student_ledger TO authenticated;