Terminal
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user