Skip to content

Epic: Member manager bulk actions (select + bulk mark/end/email) #414

Description

@jorisjonkers-dev-agents

Bulk actions on selected users in the Member Manager. Design settled via a grilling session; this epic is the authoritative spec. Delivered as a stack of PRs (see sub-issues).

Feature summary

Checkboxes per row (house-styled) + a "select all displayed" header checkbox. A vertical triple-dot menu at the top-right of the members table exposes bulk actions applied to the current selection:

  1. Mark as paid
  2. Mark as unpaid
  3. Send contribution reminder (email)
  4. Send incasso notification (email)
  5. End membership (end all active memberships as of today)

Locked design decisions

  1. Two-phase preview→execute. Frontend POSTs selected userIds + params to a backend preview endpoint; backend resolves member types/fees/exclusions/warnings and returns a structured summary. The confirm dialog renders exactly that. On confirm, frontend calls execute with the same params + the (possibly corrected) per-user amounts + dates. All business logic lives on the backend.
  2. Per-action endpoint pairs, domain-aligned, sharing ONE preview response envelope (counts + per-user rows + warnings) so the frontend uses a single reusable confirm dialog. No per-user PUT/POST spam — one request per action.
  3. Fee resolution (both emails). The period stores halfYearFee, fullYearFee, alumniFee. A "half-year cutoff date" input: regular members whose membership started on/after it resolve to the half-year fee, otherwise full-year; alumni → alumni fee; honorary → excluded. Backend preview computes the suggested per-user amount; the dialog lets the treasurer correct each user's amount before executing; execute carries the final per-user amounts.
  4. Honorary: hard-excluded from both fee emails, shown red in the summary with a warning (they don't pay). Not overridable.
  5. Incasso mismatch: users without the incasso flag selected for the incasso email are flagged (amber) and excluded by default; the treasurer can opt specific users back in.
  6. Already-paid: members already marked paid for the period are excluded by default from reminder and incasso; opt-in to re-include.
  7. Audit + re-send: every send writes an audit row (reuse ContributionReminder; add a parallel IncassoNotification entity). Preview shows "last sent on " + warns for prior sends this period, but re-send is allowed.
  8. Selection: persistent Set<userId> that survives search/filter/sort changes. Header checkbox toggles only currently-displayed rows (checked/indeterminate). Always-visible "N selected" chip + Clear. Confirm dialog lists every selected user by name.
  9. Authorization: BOARD-level (matches existing Contribution/ContributionReminder writes; TREASURER/ADMIN inherit).
  10. All five actions use preview→execute + the shared dialog. Non-email previews show counts + skip reasons (already-paid, no active membership, etc.).
  11. Period anchor: period-relative actions (paid/unpaid/reminder/incasso + already-paid checks) target the member manager's currently-selected contribution period; end-membership is period-independent (effective today). Actions disabled when no period is selected.
  12. End-membership: ends all active memberships (endDate == null) of each selected user, effective today; users with no active membership are skipped/flagged; already-ended memberships untouched.

Date inputs per email action

  • Contribution reminder: payment-due date (shown in mail) + half-year cutoff date (fee resolution).
  • Incasso notification: expected incasso date (shown in mail) + half-year cutoff date (fee resolution).

Open decision (to confirm before PR2/PR3)

  • System-test depth for emails: recommended = assert rendered body content (resolved amount per member type, correct date, honorary excluded, exclusions/opt-ins, per-user overrides) + recipients + audit, not just counts.

Existing infrastructure to build on

  • Mail: Thymeleaf + Markdown, async outbox, per-domain EmailContent builders; InMemoryEmailClient for tests. POST /contributionReminders/batch + ContributionReminder entity already exist.
  • MemberType: ALUMNI, HONORARY, REGULAR, NONE. Fees on ContributionPeriod.
  • "Paid" = a Contribution row for (userId, periodId).
  • incasso boolean on the active Membership; endMembership exists per-membership (need bulk).
  • System tests: @SpringBootTest + MockMvc in the integrationTest sourceset.

Delivery (stacked PRs — see sub-issues)

  1. Backend: bulk mark paid/unpaid + end-membership (preview+execute) + system tests.
  2. Backend: bulk contribution reminder (fee resolution, honorary/already-paid exclusion, per-user override, audit) + system tests.
  3. Backend: new incasso-notification domain (entity, email builder, endpoints, incasso-mismatch, exclusions, override, audit) + system tests.
  4. Frontend: selection UI (house-styled checkboxes, persistent set, select-all-displayed, N-selected chip + clear), triple-dot menu, shared confirm dialog wired to all endpoints, per-user fee edit, date inputs, warnings/red honorary + e2e.

Note: endpoint/DTO changes require openapi.json + client regen, which needs a DB env (not available in the sandbox) — flag per PR.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions