Skip to content

feat(events): move assignment eligibility onto parts, merge midweek talk kinds - #364

Merged
mindsers merged 3 commits into
mainfrom
feat/part-eligibility-on-parts
Aug 24, 2026
Merged

feat(events): move assignment eligibility onto parts, merge midweek talk kinds#364
mindsers merged 3 commits into
mainfrom
feat/part-eligibility-on-parts

Conversation

@mindsers

Copy link
Copy Markdown
Contributor

Why

Trying the preset feature showed that assignment eligibility (which roles may fill a slot) was modeled at the wrong level. Two parts of the same kind — two "Sujet VCM" — can legitimately be done by different populations depending on where they sit in the programme. Eligibility is a property of the slot, not of the kind. Keeping it on the preset also required a subtle precedence rule (preset wins only when non-empty, because empty means "any member"), hidden role pickers in the part editors, and dormant part-level rows — all complexity serving the wrong model.

What

Eligibility moves onto the parts

  • PartPresetAllowedRole is removed: schema model, setPartPresetAllowedRoles, the resolveAllowedRoleIds/partAllowedRolesToWrite precedence module, and the preset editor's role pickers
  • getPartAssignmentAllowedRoleIds and the batch programme query now read the part's own rows only
  • Part editors always show the role pickers; labels and the external-speaker rule stay preset-owned (capability)
  • The migration first materializes the previously effective eligibility into TemplatePartAllowedRole/EventPartAllowedRole (preset roles replace part rows only for slots the preset had configured), so existing data behaves identically, then drops the table

Seeded kinds simplified

  • With eligibility gone, nothing distinguishes the three midweek talk kinds any more: spiritual-gems, spiritual-pearls and christian-life-talk merge into one midweek-talk kind ("Sujet VCM" / "Midweek Talk") with a generic share message that includes {{partName}}
  • The migration creates the merged preset per congregation, repoints template/event parts, and deletes the old rows (custom wording stored on those three rows is dropped; the merged kind starts on catalogue defaults)

Data transfer

  • Archive format bumps to 2.6: part-preset-allowed-roles.ndjson is gone
  • Importing a 2.5 archive folds the three legacy system kinds into one midweek-talk row (all three old ids map to it, so parts stay linked) and discards preset-level eligibility with a warning log

Testing

  • pnpm test:unit — 3108 passing
  • pnpm test:integration — 322 passing (includes the data-transfer round-trip)
  • pnpm test:lint, pnpm test:typecheck, and all architecture guards pass
  • Migration applied and verified against the dev database: old kinds gone, midweek-talk present per congregation, linked parts repointed, join table dropped

… campaigns

The campaigns migration collapsed all legacy type='campaign' attributions
into one synthetic ended 'Campagne' per congregation. This follow-up data
migration splits each synthetic into one campaign per date cluster (a gap of
more than 30 days between an attribution's start and the latest end seen so
far opens a new cluster), named by its period ('Campagne 03/2024'),
repoints the attributions and drops the emptied synthetic. Synthetics are
recognized by the backfill's exact stamps (name + activatedAt = startDate +
endedAt = endDate), so campaigns that went through the real lifecycle —
even ones named 'Campagne' — and single-cluster synthetics are untouched.
Single data-modifying CTE chain (temp tables don't survive Prisma's
statement execution); verified against seeded multi-cluster, decoy and
single-cluster states on the dev database.
…alk kinds

Trying the preset feature showed eligibility sits at the wrong level: two
parts of the same kind can be done by completely different roles, so which
roles may fill a slot belongs to the part, not to the kind. The preset keeps
capability only (reader slot, slot labels, external-speaker rule, share
message).

- Drop PartPresetAllowedRole (schema, services, preset editor pickers) and
  the preset-wins resolution rule; part rows are the single source of truth
- Part editors always show the role pickers, preset chosen or not
- Migration materializes the previously effective eligibility into
  TemplatePartAllowedRole/EventPartAllowedRole so no part widens or narrows
- With eligibility gone, nothing distinguishes the three seeded midweek talk
  kinds: merge spiritual-gems, spiritual-pearls and christian-life-talk into
  one midweek-talk kind ("Sujet VCM"), repointing existing parts
- Archive format 2.6: no part-preset-allowed-roles file; importing a 2.5
  archive folds the legacy kinds into midweek-talk and discards preset-level
  eligibility with a log line
… shows

Review of the eligibility move found the reader half of 78a9219 reopened.
The reader picker is hidden whenever the chosen kind has no second slot, an
unrendered picker submits nothing, and the schema turns that absence into [] —
so saving a part after giving it such a kind deleted the reader restriction it
already carried and widened the slot to every member.

The form now declares which pickers it drew, and partAllowedRolesToWrite maps
that to the services' undefined ("not managed") versus [] ("managed, and
empty"). Declaring it beats re-deriving it in the action: the render condition
and the write decision were two copies of one rule in different features, free
to drift, which is how this arrived the first time.

Also from the review:

- validateImport now warns, before the user confirms, that a v2.5 archive
  loses its preset-level eligibility and that the three midweek kinds merge
  with their custom wording dropped. A logger.warn alone told nobody who
  would act on it.
- The migration carries an integration test that runs the shipped SQL over a
  fixture and asserts each part keeps the eligibility it actually resolved to.
  It fails if the DELETE stops matching on asKind.
- The migration claimed nothing widens. Merging the three kinds does widen
  allowExternalSpeaker for parts that were Joyaux or Perles, since one kind
  holds one answer; it keeps yes deliberately, and now says so.
- The PartPreset doc comment listed eligibility under capability, where a
  reader deciding a new field's home would have been misled.
@mindsers
mindsers merged commit 0de0d50 into main Aug 24, 2026
7 checks passed
@mindsers
mindsers deleted the feat/part-eligibility-on-parts branch August 24, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant