Step-by-Step Guide — Follow each step exactly as written. All values are provided — do not improvise.
TC-DUES-004: Quarterly / Lifetime Dues Generation (Detailed)
Module: Dues Management — Generation / Notices
Admin Test User: admin@rcme.membervu.com / Admin123!
Finance Test User: treasurer@demo.membervu.com / Treasurer123!
Priority: P1
URLs for this test:
Frontend:
Membership Types tab:
Dues & Billing Summary:
MailHog (email viewer):
Frontend:
https://stg-rcme.membervu.com/loginMembership Types tab:
https://stg-rcme.membervu.com/admin/members/settings?tab=typesDues & Billing Summary:
https://stg-rcme.membervu.com/admin/reports/dues-summaryMailHog (email viewer):
https://stg-webmail.membervu.com
Payment gateway is INERT on staging (Paynamics). Nothing in this TC triggers a live charge — the manual/bank-transfer "Record Payment" + "Approve" flow only creates Invoice/Payment records.
Relationship to TC-MEM-008: the Membership module's TC-MEM-008 already has exhaustive coverage of membership-type CRUD (SCEN-01/02), the expiry-engine correctness for LIFETIME/QUARTERLY activation (SCEN-03/04), cross-path expiry agreement (SCEN-07), and the full #527 manual-dues-activation regression (SCEN-08) with its own false-pass warning. This page does not repeat that depth — Steps 1-2 below do the minimum type setup needed as a fixture, then Steps 3-6 focus specifically on what the dues job (
duesJob.service.ts) and dues calculation (duesCalculation.service.ts) do with these two periods, which TC-MEM-008 does not exercise (it drives activation via the membership approve/reactivate paths, not the bulk dues-run endpoint).
Step 1: Create QUARTERLY and LIFETIME membership types (fixture setup)
| Step | Action (EXACT clicks/typing) | Expected Result (EXACT text/behavior) | Test Value |
|---|---|---|---|
| 1 | Go to https://stg-rcme.membervu.com/login; sign in. | Admin dashboard loads. | admin@rcme.membervu.com / Admin123! |
| 2 | Go to /admin/members/settings?tab=types. Fill the "Create Membership Type" form: Name QA Dues Quarterly, Billing Period Quarterly, Dues Amount 3000. Submit. | Toast "Membership type created". Row appears with period Quarterly. | QA Dues Quarterly / 3000 |
| 3 | Repeat: Name QA Dues Lifetime, Billing Period Lifetime, Price 20000. Submit. | Toast "Membership type created". Row appears with period Lifetime. Price-field label was "One-time Price" while composing this row. | QA Dues Lifetime / 20000 |
| 4 | Call GET /api/membership/types (devtools Network tab, or refresh the list). | Both new types are present: { name: "QA Dues Quarterly", period: "quarterly", amountCents: 300000 } and { name: "QA Dues Lifetime", period: "lifetime", amountCents: 2000000 }. Existing Regular/Associate/Student (ANNUAL) types are unchanged. | — |
Step 2: Assign the new types to two test members
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Register a fresh applicant at /register (incognito): First QuarterlyDues, Last QATest, email qa-dues-quarterly@test.membervu.com, plus required fields. Submit. | Application submitted, status PENDING_APPROVAL. | qa-dues-quarterly@test.membervu.com |
| 2 | As admin, open the new member → Actions ▾ → Change Membership Type → select QA Dues Quarterly → save. | Member's type is now "QA Dues Quarterly". | — |
| 3 | Repeat steps 1-2 for a second applicant: email qa-dues-lifetime@test.membervu.com, type QA Dues Lifetime. | Second member's type is "QA Dues Lifetime". Both members remain PENDING_APPROVAL for now — do NOT approve yet. | qa-dues-lifetime@test.membervu.com |
Step 3: Preview dues calculation for the QUARTERLY member (DUES4-02)
Code reference:
duesCalculation.service.ts — the QUARTERLY branch sets proratedAmountCents = baseAmountCents (the type's own amountCents, no division), monthsRemaining = 3, isProrated = false. It never touches the fiscal-year/proration math used by ANNUAL.
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | You need this member ACTIVE first (dues preview/calculate requires an existing member record — approve them now via the skip-dues or dues-required path per your tenant config; if dues-required, the resulting first invoice is separate from what we test in Step 4). As admin, approve qa-dues-quarterly@test.membervu.com. | Member reaches ACTIVE or PENDING_PAYMENT (if an initial dues invoice was auto-created, that's fine — it's a different invoice/period than the one we generate in Step 4). | — |
| 2 | Call GET /api/billing/admin/members/:memberId/dues/calculate?period=2026Q3 for this member (replace :memberId with their ID from the member detail URL; use any not-yet-invoiced period string). | 200 response body: billingPeriod: "QUARTERLY", proratedAmountCents: 300000 (exactly the type's ₱3,000 price — NOT ₱15,000 global annual ÷ 4 = 375000, and NOT prorated by join date), totalMonths: 3, isProrated: false. | period=2026Q3 |
Step 4: Run bulk dues job — QUARTERLY member IS invoiced, cadence check (DUES4-03)
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Note the exact current date/time (or the member's membershipStartDate if already set from Step 3). | Recorded for cross-check, e.g. 2026-07-01. | — |
| 2 | Call POST /api/billing/internal/dues/run with body { "period": "2026Q3-QA", "memberIds": ["<qa-dues-quarterly member id>"] } (scope to just this member so the run is fast and isolated). | 200. Response summary shows invoicesCreated: 1 for this member, amount 300000 cents. | period: 2026Q3-QA |
| 3 | Open Finance → Invoices, find the new dues invoice for this member. | Invoice exists, source DUES, amount ₱3,000.00, status PENDING/OUTSTANDING. | — |
| 4 | As treasurer, open the invoice → Record Payment (method Bank Transfer or any manual method) for the full ₱3,000.00 → confirm/approve so it reaches PAID. | Invoice → PAID. onDuesInvoicePaid() fires. | ₱3,000.00 |
| 5 | Open the member's detail panel and read membershipExpiresAt. | Expiry = the date noted in Step 4.1, advanced by exactly 3 calendar months (e.g. started Jul 1, 2026 → expires Oct 1, 2026) — via computeMembershipExpiry()'s expiry.setMonth(expiry.getMonth() + 3). This is a calendar-month add, not a fixed 90/91/92-day offset — verify the day-of-month matches (Jul 1 → Oct 1, not Sep 29 or Oct 2). | — |
Step 5: Run bulk dues job — LIFETIME member is EXCLUDED, no crash (DUES4-04 / DUES4-05)
This is a defect-hunt step, not a formality. The bug class here would be: the dues job silently 500s on a null
membershipExpiresAt/LIFETIME member instead of cleanly skipping them. Confirm BOTH that no invoice was created AND that the job completed successfully for the rest of the batch.
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | As admin, approve qa-dues-lifetime@test.membervu.com (their type is QA Dues Lifetime). If your tenant requires dues on approval, the LIFETIME type triggers a one-time join charge, not a recurring dues invoice — pay that initial invoice if generated, same as any other member, then continue. | Member reaches ACTIVE. membershipExpiresAt is null (confirm on the member detail panel — reads as blank/"Lifetime"/"No expiry", not an error or garbled date). | — |
| 2 | Call POST /api/billing/internal/dues/run with body { "period": "2026Q3-QA", "memberIds": ["<qa-dues-quarterly id>", "<qa-dues-lifetime id>"] } — include BOTH members in one call. | 200 (no 500). Response summary: the QUARTERLY member is either skippedExisting (if already invoiced from Step 4) or creates a new invoice for a different period; the LIFETIME member does NOT appear in invoicesCreated at all — they were excluded by the job's member query (membershipType: { period: { not: 'LIFETIME' } }) before calculation ever ran. | period: 2026Q3-QA |
| 3 | Open Finance → Invoices, filter/search for the LIFETIME member. | Only their original one-time join invoice (if any) exists — zero recurring DUES-source invoices with this period were created for them. | — |
| 4 | Check the backend logs or job response for any error/exception referencing this member. | None. The job completed cleanly — LIFETIME exclusion happens at the query level, so calculateMemberDues()'s defensive LIFETIME branch (which would return proratedAmountCents: 0) is not even normally reached in production use. | — |
Step 6: LIFETIME member's dues-related emails don't error on the null date
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Check MailHog (https://stg-webmail.membervu.com) for the membership-activation email sent to qa-dues-lifetime@test.membervu.com from Step 5.1. | Email renders successfully (no broken template, no missing fields). The expiry-date field reads exactly Lifetime (no expiry) — from buildMembershipActivationEmail's membershipExpiresAt ? formatDateForEmail(...) : "Lifetime (no expiry)" branch — not blank, not Invalid Date, not a fallback date like Dec 31. | — |
Step 7: Manual bank-transfer dues approval activates the member (#527) — quick cross-check
Full regression for this fix is TC-MEM-008 SCEN-08 (including the "common false-pass" warning about checking member status, not just the payment toast). This step is a lighter confirmation using the QUARTERLY dues invoice created in Step 4, to prove #527 also works for the new QUARTERLY period specifically.
| Step | Action | Expected Result | Test Value |
|---|---|---|---|
| 1 | Register a THIRD fresh applicant: email qa-dues-quarterly-activation@test.membervu.com, and assign it the QA Dues Quarterly type (same as Step 2). Have an admin approve with dues required so a DUES invoice is auto-created and the member sits in PENDING_PAYMENT. | Member status PENDING_PAYMENT; unpaid DUES invoice exists, amount ₱3,000.00. | qa-dues-quarterly-activation@test.membervu.com |
| 2 | As the member (or admin on their behalf), submit a manual bank-transfer payment with proof against that invoice — "Submit for Approval", method Bank Transfer, full amount, reference BT-2026-DUES4-001. | Payment record PENDING. Invoice still OUTSTANDING. Member status still PENDING_PAYMENT. | BT-2026-DUES4-001 |
| 3 | As treasurer, open the invoice's Payments tab → click Approve on the pending payment. | POST /api/billing/admin/payments/:paymentId/approve → 200, status: "SUCCEEDED". | — |
| 4 | Immediately (same test pass, no waiting) check the member's detail panel. | Member status is now ACTIVE — flipped in the same approval request. membershipStartDate and membershipExpiresAt are both populated, with expiry = approval date + 3 calendar months (QUARTERLY cadence, per Step 4.5). | — |
CLEANUP
- Delete the three
qa-dues-*@test.membervu.comtest members/applications if the environment needs to stay clean for other testers, or leave for a scheduled reseed. - Delete the QA Dues Quarterly / QA Dues Lifetime membership types via the Types tab (only allowed once no members are assigned) — or leave for reseed.
- A full reset is available via:
docker exec membervu-backend npm run seed:rcme(clears all QA fixtures created in this TC). - Do not leave any invoice from Step 4/7 in a state that could be mistaken for a real member's unpaid dues in later regression passes.