TC-FUND-002: Admin Fundraising Console (Detailed) — #623

Step-by-Step Guide — Follow each step exactly as written. All values are provided — do not improvise.
USE A REAL PENDING DONATION. This guide assumes you've run TC-FUND-001 first and have at least one PENDING donation with an uploaded proof sitting in the queue. If none exists, create one: log in as the donor, donate on an ACTIVE campaign, upload proof, then continue here as admin.
Module
Fundraising & Campaigns — Admin Console
Staging Admin
https://stg-admin.membervu.com
Staging Frontend
https://stg-rcme.membervu.com
Staging MailHog
https://stg-webmail.membervu.com
Tenant
RCME (Rotary Club of Manila Expats)
Priority
P1

TC-FUND-ADMIN-01: Pending-Proof Queue Lists Oldest-First

StepActionExpected ResultTest Value
1 Log in as the finance admin. Admin dashboard loads. admin@rcme.membervu.com / Admin123!
2 Navigate to the "Proof Review" sidebar item / /admin/campaigns/donations/pending. GET /api/campaigns/admin/donations/pending fires. Page shows all tenant PENDING donations across ALL campaigns (not just one), oldest submission first. /admin/campaigns/donations/pending
3 Inspect a row. Each row shows donor name/email, amount, campaign name, and the uploaded proof image inline (from invoice.payments[].proofUrl).
4 Click Approve (or Reject) directly from the row. Same endpoints as TC-FUND-001 (approve/reject). The row disappears from the queue immediately without a full page reload.

TC-FUND-ADMIN-02: Nav Badge Tracks Pending Count

StepActionExpected ResultTest Value
1 As admin, note the number badge on the "Proof Review" sidebar link. Badge shows the current pending count (e.g. 2).
2 In a second browser/incognito tab, log in as the donor and create a NEW donation + upload proof on an ACTIVE campaign. A new PENDING donation with proof exists. 150.00 PHP
3 Back in the admin tab, refresh (or wait for the badge's polling interval). Badge count increments by exactly 1 (backed by listPendingDonations().total, badge key pendingProofDonations).
4 Approve or reject the new donation from the queue. Badge count decrements by 1, back to the step-1 baseline.
5 Log out. Log in as a role WITHOUT finance permissions (e.g. a plain MEMBER role, if such an account can reach an admin-shell view, or simulate via a non-finance admin role if available). The underlying badge fetch returns 403.
6 Observe the badge. Badge renders 0 — the 403 is treated as "no pending items", not surfaced as a console error or broken UI.

TC-FUND-ADMIN-03: Campaign Close-Out — Final Totals

StepActionExpected ResultTest Value
1 Pick (or set up) an ACTIVE campaign with a mix of donation states: at least 1 APPROVED, 1 PENDING, 1 REJECTED. Note each donation's amount for the manual sum check in step 3. e.g. APPROVED=500, PENDING=200, REJECTED=100
2 Open the campaign's admin detail page (/admin/campaigns/:id) and click Close Campaign / trigger Close-Out. POST /api/billing/admin/campaigns/:id/close-out returns 200. FundraisingCampaign.status flips ACTIVE → CLOSED.
3 Inspect the response body. finalTotal equals the SUM of APPROVED donation amounts ONLY (500 in the example) — PENDING (200) and REJECTED (100) are excluded. donationCount equals the count of APPROVED donations (1 in the example).
4 Re-trigger Close-Out (click the button again, or repeat the API call) on the now-CLOSED campaign. 200 — NOT a 400. Idempotent no-op; response returns the SAME finalTotal/donationCount as step 3.

TC-FUND-ADMIN-04: Close-Out Donor CSV Export

StepActionExpected ResultTest Value
1 Ensure the target CLOSED campaign has at least one anonymous APPROVED donation (donate via direct API with isAnonymous:true if the UI has no checkbox — see TC-FUND-003). Anonymous APPROVED donation exists.
2 Download the donor CSV: GET /api/billing/admin/campaigns/:id/close-out/donors.csv (via the admin UI's export button or direct URL). CSV downloads successfully.
3 Open the CSV and inspect rows. Only APPROVED donations appear (no PENDING/REJECTED rows). The anonymous row's name/email columns are redacted to Anonymous / empty string — NOT the donor's real name/email.

TC-FUND-ADMIN-05: Close-Out Bulk Thank-You Email

StepActionExpected ResultTest Value
1 On the same CLOSED campaign, trigger the bulk thank-you send (POST .../close-out/thank-you) via the admin UI. Response shape: {total, sent, skipped, failed}.
2 Check the numbers: total should equal the APPROVED donation count; skipped should equal the anonymous-donor-with-no-linked-member count. skipped > 0 for the anonymous donation from TC-FUND-ADMIN-04 (assuming it has no linked member); sent covers the rest.
3 Open MailHog. One thank-you email per non-anonymous APPROVED donor with a member account. No email sent for the skipped anonymous donor. https://stg-webmail.membervu.com

TC-FUND-ADMIN-06: Auto-Close on Deadline Does NOT Auto-Run Close-Out

TIMING-DEPENDENT. This case requires a campaign whose endsAt has passed (or will pass shortly). If you can set endsAt to a near-future timestamp via the edit form, do so and wait for the sweep; otherwise mark this case for manual/scheduled execution.
StepActionExpected ResultTest Value
1 Create/edit a campaign with an endsAt a few minutes in the future, and add a PENDING donation with proof before it lapses. Campaign is ACTIVE, has a near-future endsAt, and one PENDING+proof donation.
2 Wait for endsAt to pass and for the auto-close sweep to run. Campaign status auto-transitions ACTIVE → CLOSED. The public donate button is now dark/disabled.
3 Check the PENDING donation from step 1. It is STILL PENDING — close-out was NOT auto-run. It remains visible in the Proof Review queue.
4 As admin, approve that PENDING donation. Approval succeeds normally even though the campaign is already CLOSED (approving after auto-close is allowed).

CLEANUP

Data created during this test:

  1. One or more CLOSED campaigns from close-out testing — safe to leave closed; do not attempt to reopen (no reopen endpoint exists).
  2. Anonymous test donation created via direct API — safe to leave; useful for future CSV/thank-you regression.
  3. Thank-you emails sent to real donor test accounts — informational only, no cleanup needed.