TC-DUES-004: Quarterly / Lifetime Dues Generation

πŸ†• New on stage β€” 2026-06-30 β†’ 2026-07-01. This TC covers the two new membership billing periods added to MembershipType.period β€” QUARTERLY and LIFETIME (#504) β€” from the dues-generation and dues-notification side, plus the manual bank-transfer dues approval β†’ member activation fix (#527). Type CRUD and the membership-expiry engine itself are covered in the Membership module's TC-MEM-008 β€” this page cross-links that and focuses on what the dues job and dues emails do with these two periods. See the Test Plan banner and the Jul 1 release notes (#504, #527).

Module: Dues Management β€” Generation / Notices

Primary Test User: admin@rcme.membervu.com / Admin123! (Admin); treasurer@demo.membervu.com / Treasurer123! (Finance Manager) for approval

Priority: P1 (money-adjacent β€” wrong cadence or a null-date crash blocks billing for an entire membership tier)

Objectives

  1. Configure a QUARTERLY membership type, generate dues for a member on it, and confirm the resulting membership expiry (next-due date) advances by exactly +3 calendar months β€” not a prorated annual/4 figure.
  2. Configure a LIFETIME membership type and confirm the recurring dues job never generates a repeat dues invoice for a LIFETIME member, and that the member's membershipExpiresAt stays null without erroring the job or the activation email.
  3. Confirm a manual bank-transfer dues payment, once approved, flips an inactive/pending member to ACTIVE in the same step (#527) β€” including for the QUARTERLY/LIFETIME types exercised here.
Not seeded β€” you must create the types. RCME's seed data only has ANNUAL membership types (Regular/Associate/Student). QUARTERLY and LIFETIME types do not exist until you create them via Member Settings β†’ Membership Types (/admin/members/settings?tab=types).

Scenario summary

IDScenarioKey data assertion
DUES4-01Setup β€” create QUARTERLY + LIFETIME types (brief; full CRUD coverage is TC-MEM-008 SCEN-01/02)POST /membership/types with period: "quarterly" / "lifetime" β†’ 201; stored MembershipType.period === "QUARTERLY" / "LIFETIME".
DUES4-02QUARTERLY dues amount is billed as-is, not annualΓ·4GET .../dues/calculate?period=… for a QUARTERLY member returns proratedAmountCents === MembershipType.amountCents (the type's own price), totalMonths: 3, isProrated: false β€” never a fiscal-year proration of the global annual amount.
DUES4-03Bulk dues run: QUARTERLY member gets invoiced, cadence is +3 calendar monthsPOST /api/billing/internal/dues/run creates a dues invoice for the QUARTERLY member; once PAID, Member.membershipExpiresAt equals membershipStartDate advanced by exactly 3 calendar months (via computeMembershipExpiry) β€” e.g. Jan 15 β†’ Apr 15, not a generic +90 days or fiscal year-end.
DUES4-04Bulk dues run: LIFETIME member is excluded entirely (never invoiced)The same POST /api/billing/internal/dues/run call does not create any dues invoice for the LIFETIME member β€” the job's member query explicitly filters membershipType: { period: { not: 'LIFETIME' } }, and defensively calculateMemberDues() also returns proratedAmountCents: 0 for LIFETIME if ever reached.
DUES4-05LIFETIME member has null next-due date, no crash anywhere in the dues pathMember.membershipExpiresAt is null after activation; the bulk dues job run (DUES4-04) and the dues/activation emails complete without error (HTTP 200 / no 500), and any email body referencing the expiry reads "Lifetime (no expiry)" rather than an invalid/blank date.
DUES4-06Manual bank-transfer dues approval activates the member (#527) β€” brief cross-check; full scenario is TC-MEM-008 SCEN-08Approving a PENDING bank-transfer dues payment (POST /api/billing/admin/payments/:paymentId/approve) on an inactive/pending QUARTERLY member flips Member.status to ACTIVE and sets membershipStartDate/membershipExpiresAt in the same request β€” no separate activation step needed.

➑️ Full click-by-click steps: TC-DUES-004 (Detailed).