DROP VIEW IF EXISTS public.budget_actuals_monthly CASCADE; CREATE VIEW public.budget_actuals_monthly WITH (security_invoker = true) AS -- Expenses from bills (count as soon as bill is entered, regardless of status, -- excluding voided/cancelled) SELECT b.association_id, date_trunc('month', b.bill_date)::date AS period_month, 'expense'::text AS account_type, b.expense_account_id AS gl_account_id, coa.account_name AS category_name, SUM(COALESCE(b.amount, 0))::numeric AS amount FROM public.bills b LEFT JOIN public.chart_of_accounts coa ON coa.id = b.expense_account_id WHERE b.bill_date IS NOT NULL AND COALESCE(LOWER(b.status), '') NOT IN ('void', 'voided', 'cancelled', 'canceled', 'rejected', 'draft') GROUP BY b.association_id, date_trunc('month', b.bill_date), b.expense_account_id, coa.account_name UNION ALL -- Expenses from billable_expenses (categorised line items) SELECT be.association_id, date_trunc('month', be.date)::date AS period_month, 'expense'::text AS account_type, NULL::uuid AS gl_account_id, COALESCE(be.category, 'Uncategorized') AS category_name, SUM(COALESCE(be.amount, 0))::numeric AS amount FROM public.billable_expenses be WHERE be.date IS NOT NULL AND COALESCE(be.is_credit, false) = false GROUP BY be.association_id, date_trunc('month', be.date), be.category UNION ALL -- Income from admin payments SELECT ap.association_id, date_trunc('month', COALESCE(ap.payment_date, ap.created_at::date))::date AS period_month, 'income'::text AS account_type, NULL::uuid AS gl_account_id, 'Owner Payments'::text AS category_name, SUM(COALESCE(ap.amount, 0))::numeric AS amount FROM public.admin_payments ap WHERE COALESCE(LOWER(ap.status), '') NOT IN ('void', 'voided', 'cancelled', 'canceled', 'failed', 'refunded') GROUP BY ap.association_id, date_trunc('month', COALESCE(ap.payment_date, ap.created_at::date)) UNION ALL -- Income from client invoices (count as soon as issued) SELECT ci.association_id, date_trunc('month', COALESCE(ci.issue_date, ci.paid_date))::date AS period_month, 'income'::text AS account_type, NULL::uuid AS gl_account_id, 'Invoiced Income'::text AS category_name, SUM(COALESCE(ci.total, ci.amount, 0))::numeric AS amount FROM public.client_invoices ci WHERE COALESCE(ci.issue_date, ci.paid_date) IS NOT NULL AND COALESCE(LOWER(ci.status), '') NOT IN ('void', 'voided', 'cancelled', 'canceled', 'draft') GROUP BY ci.association_id, date_trunc('month', COALESCE(ci.issue_date, ci.paid_date)); GRANT SELECT ON public.budget_actuals_monthly TO authenticated, anon, service_role;