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:
- Mark as paid
- Mark as unpaid
- Send contribution reminder (email)
- Send incasso notification (email)
- End membership (end all active memberships as of today)
Locked design decisions
- 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.
- 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.
- 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.
- Honorary: hard-excluded from both fee emails, shown red in the summary with a warning (they don't pay). Not overridable.
- 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.
- Already-paid: members already marked paid for the period are excluded by default from reminder and incasso; opt-in to re-include.
- 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.
- 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.
- Authorization: BOARD-level (matches existing Contribution/ContributionReminder writes; TREASURER/ADMIN inherit).
- All five actions use preview→execute + the shared dialog. Non-email previews show counts + skip reasons (already-paid, no active membership, etc.).
- 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.
- 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)
- Backend: bulk mark paid/unpaid + end-membership (preview+execute) + system tests.
- Backend: bulk contribution reminder (fee resolution, honorary/already-paid exclusion, per-user override, audit) + system tests.
- Backend: new incasso-notification domain (entity, email builder, endpoints, incasso-mismatch, exclusions, override, audit) + system tests.
- 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.
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:
Locked design decisions
userIds+ params to a backendpreviewendpoint; backend resolves member types/fees/exclusions/warnings and returns a structured summary. The confirm dialog renders exactly that. On confirm, frontend callsexecutewith the same params + the (possibly corrected) per-user amounts + dates. All business logic lives on the backend.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.incassoflag selected for the incasso email are flagged (amber) and excluded by default; the treasurer can opt specific users back in.ContributionReminder; add a parallelIncassoNotificationentity). Preview shows "last sent on " + warns for prior sends this period, but re-send is allowed.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.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
Open decision (to confirm before PR2/PR3)
Existing infrastructure to build on
EmailContentbuilders;InMemoryEmailClientfor tests.POST /contributionReminders/batch+ContributionReminderentity already exist.MemberType: ALUMNI, HONORARY, REGULAR, NONE. Fees onContributionPeriod.Contributionrow for(userId, periodId).incassoboolean on the activeMembership;endMembershipexists per-membership (need bulk).@SpringBootTest+ MockMvc in theintegrationTestsourceset.Delivery (stacked PRs — see sub-issues)
Note: endpoint/DTO changes require
openapi.json+ client regen, which needs a DB env (not available in the sandbox) — flag per PR.