docs(form-builder): document S3a PR 2 complex field types and update FORM-05 stub note
- Add VitePress pages for AVAILABILITY_PICKER and SECTION_PRIORITY
and a TAG_PICKER configuration note. Wire them into the organisator
sidebar under a new Formulieren section alongside the existing
"Wat is een formulier" page.
- BACKLOG.md: nuance FORM-05 — the stub-shaped behaviour for public
event_registration submissions is already shipping via the existing
TriggerPersonIdentityMatchOnFormSubmit listener (writes 'pending').
The real work (PersonIdentityService::detectMatchesByValues + an
extra branch in resolveStatus) is what remains. Added a done entry
for S3a PR 2 to the Opgeloste items list.
- API.md: add VALIDATION_FAILED to the public-form error code table
and document the SECTION_PRIORITY shape error messages (Dutch copy
served under errors."values.{slug}").
- COPY_CATALOGUE.md: new S3a PR 2 section capturing the seeder
help_text, the IdentityMatchBanner copy (clearly marking the
backend message as authoritative), all empty/error state copy for
the three new components, and the SECTION_PRIORITY shape error
strings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -809,10 +809,29 @@ Codes:
|
||||
| `SCHEMA_NOT_FOUND` | 404 | public token does not resolve |
|
||||
| `SUBMISSION_ALREADY_SUBMITTED`| 409 | submit on finalised submission |
|
||||
| `RATE_LIMITED` | 429 | includes `Retry-After` header |
|
||||
| `VALIDATION_FAILED` | 422 | per-field validation — see `errors` map below |
|
||||
|
||||
Authoritative source: `api/app/Exceptions/FormBuilder/PublicFormApiException.php`
|
||||
and its concrete subclasses.
|
||||
|
||||
`VALIDATION_FAILED` returns the `errors` field keyed by `values.{slug}`
|
||||
with an array of Dutch messages. Field-shape triggers include:
|
||||
|
||||
- **SECTION_PRIORITY** — values must be `{ section_id, priority }[]`
|
||||
with unique section_ids and priorities in `1..5`, max 5 entries, and
|
||||
section_ids scoped to the schema's owner event tree. Specific
|
||||
messages land under `errors."values.{slug}"`:
|
||||
- `"Dezelfde sectie mag slechts één keer worden opgegeven."`
|
||||
- `"Elke prioriteit mag slechts één keer worden toegekend."`
|
||||
- `"priority moet tussen 1 en 5 liggen (positie {n})."`
|
||||
- `"Je kunt maximaal 5 voorkeuren opgeven."`
|
||||
- `"Eén of meer secties horen niet bij dit evenement."`
|
||||
- `"Ongeldig formaat voor sectievoorkeuren."` / `"Ongeldig voorkeur-element op positie {n}."`
|
||||
/ `"section_id ontbreekt op positie {n}."` / `"priority ontbreekt of is ongeldig op positie {n}."`
|
||||
|
||||
Authoritative source for shape rules:
|
||||
`api/app/Services/FormBuilder/FormValueService::validateSectionPriorityShape`.
|
||||
|
||||
### `GET /public/forms/{public_token}`
|
||||
|
||||
Returns `PublicFormSchemaResource`. Shape:
|
||||
|
||||
@@ -341,21 +341,28 @@ shifts claimen zonder toegang tot de Organizer app.
|
||||
|
||||
### FORM-05 — Smart identity-match on public submission values
|
||||
|
||||
Public form submissions (subject_type=null) currently always get
|
||||
identity_match_status='pending' because the listener needs a Person
|
||||
to match against and public submissions don't create one yet.
|
||||
**Stub-status (S3a PR 2, 2026-04-23):** Public event_registration
|
||||
submissions landen al met `identity_match_status='pending'` via de
|
||||
bestaande `TriggerPersonIdentityMatchOnFormSubmit` listener. De portal
|
||||
`IdentityMatchBanner` leest dit veld en toont de juiste copy. Contract
|
||||
ligt vast in `tests/Feature/FormBuilder/Listeners/TriggerPersonIdentityMatchOnFormSubmitTest`.
|
||||
|
||||
Improve: when a public event_registration submission arrives, extract
|
||||
email + first_name + last_name from the submission values (via the
|
||||
schema's binding config) and call a new PersonIdentityService method:
|
||||
detectMatchesByValues(array $values, string $organisationId): MatchResult
|
||||
**Resterend werk (de eigenlijke FORM-05):** public form submissions
|
||||
(subject_type=null) krijgen momenteel *altijd* 'pending' omdat er nog
|
||||
geen Person bestaat om tegen te matchen. Breid uit met:
|
||||
|
||||
Set identity_match_status to matched / pending / none based on actual
|
||||
lookup. This gives the portal-form UX a meaningful signal instead of
|
||||
a constant pending.
|
||||
- Nieuwe methode op PersonIdentityService:
|
||||
`detectMatchesByValues(array $values, string $organisationId): MatchResult`
|
||||
- Een extra tak in `TriggerPersonIdentityMatchOnFormSubmit::resolveStatus`
|
||||
die voor public submissions de values uit `FormSubmission->values`
|
||||
extraheert (email / first_name / last_name via de schema binding),
|
||||
deze methode aanroept, en 'matched' / 'pending' / 'none' schrijft.
|
||||
|
||||
Priority: Medium. Can bundle with organizer person_identity_matches UI
|
||||
(which is also still a frontend gap).
|
||||
Zo krijgt de portal-UX een betekenisvol signaal in plaats van een
|
||||
constante 'pending'.
|
||||
|
||||
Prioriteit: Medium. Kan gebundeld worden met de organizer
|
||||
`person_identity_matches` UI (ook nog een frontend gap).
|
||||
|
||||
---
|
||||
|
||||
@@ -491,6 +498,7 @@ De volgende items zijn geïmplementeerd en afgerond (673+ tests):
|
||||
- ~~Registration settings (show_in_registration)~~ ✅
|
||||
- ~~Premium portal wizard (banner, branding, success page)~~ ✅
|
||||
- ~~Global error handling (useNotificationStore + axios 422 interceptor)~~ ✅
|
||||
- ~~S3a PR 2: TAG_PICKER / AVAILABILITY_PICKER / SECTION_PRIORITY renderen in het publieke registratieformulier. Seeder uitgebreid met twee showcase-velden + parent-level VOLUNTEER time slot + duplicate section name voor dedup-dekking. SECTION_PRIORITY waarde-shape gevalideerd in FormValueService. `FormSubmissionResource` krijgt admin-facing `identity_match` block. 64 nieuwe assertions over backend + Vitest.~~ ✅
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -118,3 +118,89 @@ public_token_rotation:
|
||||
kunnen nog 7 dagen inzenden met de oude link; daarna krijgen ze een
|
||||
410 Gone foutmelding."
|
||||
```
|
||||
|
||||
## S3a PR 2 — Public form field types & identity-match
|
||||
|
||||
### Seeder help_text (showcase demo fields)
|
||||
|
||||
```
|
||||
beschikbaarheid.help_text:
|
||||
"Vink alle dagdelen aan waarop je kunt werken."
|
||||
|
||||
sectie_voorkeur.help_text:
|
||||
"Sleep je voorkeuren in volgorde. Nummer 1 is je eerste keuze."
|
||||
```
|
||||
|
||||
### IdentityMatchBanner — single source of truth is the backend
|
||||
|
||||
Copy is served by PublicFormSubmissionResource::formatIdentityMatch()
|
||||
in `identity_match.message`. The frontend fallbacks in
|
||||
`IdentityMatchBanner.vue` duplicate these exactly and must stay in sync
|
||||
whenever the backend copy changes.
|
||||
|
||||
```
|
||||
identity_match.pending.title: "We controleren je gegevens"
|
||||
identity_match.pending.body:
|
||||
"We kijken of je al bekend bent bij de organisator. Je gegevens
|
||||
worden automatisch gekoppeld zodra zij dit bevestigen."
|
||||
(backend message:
|
||||
"We controleren of je al bekend bent bij de organisator. Je gegevens
|
||||
worden gekoppeld zodra zij dit bevestigen.")
|
||||
|
||||
identity_match.matched.title: "Gegevens gekoppeld"
|
||||
identity_match.matched.body:
|
||||
"Je bent automatisch gekoppeld aan je bestaande account bij de
|
||||
organisator."
|
||||
(backend message:
|
||||
"Je account is gekoppeld aan een bekende deelnemer.")
|
||||
|
||||
identity_match.none.title: "Aanmelding ontvangen"
|
||||
identity_match.none.body:
|
||||
"De organisator neemt contact met je op zodra je aanmelding is
|
||||
verwerkt."
|
||||
(backend message:
|
||||
"Geen bestaand account gevonden — je wordt als nieuwe deelnemer
|
||||
geregistreerd.")
|
||||
```
|
||||
|
||||
### Empty-state copy (public form field components)
|
||||
|
||||
```
|
||||
FieldTagPicker.empty:
|
||||
"Er zijn nog geen tags beschikbaar voor dit formulier."
|
||||
|
||||
FieldAvailabilityPicker.empty:
|
||||
"Er zijn nog geen tijdsloten beschikbaar."
|
||||
|
||||
FieldAvailabilityPicker.error:
|
||||
"Kon beschikbaarheidsopties niet laden." [button: "Opnieuw proberen"]
|
||||
|
||||
FieldSectionPriority.empty:
|
||||
"Er zijn nog geen secties gepubliceerd voor registratie."
|
||||
|
||||
FieldSectionPriority.error:
|
||||
"Kon secties niet laden." [button: "Opnieuw proberen"]
|
||||
|
||||
FieldSectionPriority.cap_hint:
|
||||
"Maximaal {max_priorities} voorkeuren"
|
||||
|
||||
FieldSectionPriority.first_rank_hint:
|
||||
"Tik of sleep een sectie hieronder om je eerste voorkeur te kiezen."
|
||||
```
|
||||
|
||||
### SECTION_PRIORITY — FormValueService shape errors
|
||||
|
||||
Returned under `errors."values.{slug}"` in the standard
|
||||
`VALIDATION_FAILED` envelope.
|
||||
|
||||
```
|
||||
"Ongeldig formaat voor sectievoorkeuren."
|
||||
"Je kunt maximaal 5 voorkeuren opgeven."
|
||||
"Ongeldig voorkeur-element op positie {n}."
|
||||
"section_id ontbreekt op positie {n}."
|
||||
"priority ontbreekt of is ongeldig op positie {n}."
|
||||
"priority moet tussen 1 en 5 liggen (positie {n})."
|
||||
"Dezelfde sectie mag slechts één keer worden opgegeven."
|
||||
"Elke prioriteit mag slechts één keer worden toegekend."
|
||||
"Eén of meer secties horen niet bij dit evenement."
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user