docs(forms): SCHEMA crosswalk, foundation concept page, getting-started + migration playbook, copy catalogue init

SCHEMA.md
- New §3.5.12 "Form Builder" with the legacy-tables-retained note
  placed prominently directly under the section header (per S1 wrap-up
  Path 3 decision: Phase 8 deferred to S2).
- Crosswalk: every legacy volunteer_profiles column → its new home
  (user_profiles columns vs form_fields vs person_tags).
- Summary table for the 13 new tables with one-line purpose + ARCH §
  pointer each.
- Activity log strategy and multi-tenancy discipline noted.
- §3.5.4 marked SUPERSEDED with a pointer to the new section.

/dev-docs/form-builder-migration-playbook.md (new)
- Operator runbook for forms:migrate-legacy-data on real legacy data.
- Pre-flight audit, dry-run, migrate, verify, spot-check, rollback
  paths spelled out. Same legacy-tables-retained note prominently.

/dev-docs/form-builder-getting-started.md (new)
- Developer onboarding. Mental model, code samples for creating a
  schema/field/submission/value, adding a new subject type, registering
  a custom field type, suppressing activity log via
  App\Support\ActivityLog::suppressed.

/dev-docs/COPY_CATALOGUE.md (new)
- Seeded verbatim from ARCH §30 (naming conventions, tooltip catalogue,
  warning catalogue) with a header explaining purpose, growth strategy,
  and the per-PR update workflow.

/docs/organizer/forms/concepts/wat-is-een-formulier.md (new VitePress)
- Dutch, informal je/jij. Follows /docs/.templates/concept-page.md.
- Three example use-cases: vrijwilligersregistratie, artist advance,
  incidentrapportage. Light foundation; depth arrives in S2-S5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 17:06:53 +02:00
parent cd7a804024
commit cfc7610497
5 changed files with 676 additions and 0 deletions

View File

@@ -669,6 +669,13 @@ $effectiveDate = $shift->end_date ?? $shift->timeSlot->date;
## 3.5.4 Volunteer Profile & History
> **SUPERSEDED by S1 of the form-builder refactor.** The old
> `volunteer_profiles` table was a planning placeholder that was never
> physically created. It has been split: user-universal columns moved
> to the new `user_profiles` table (§3.5.12), and event-variable / skill
> columns moved to `form_fields` and `person_tags`. See §3.5.12 for the
> new structure and the column-by-column crosswalk.
### `volunteer_profiles`
| Column | Type | Notes |
@@ -1924,3 +1931,59 @@ Immutable audit record of every email sent. No soft deletes.
**Indexes:** `(organisation_id, created_at)`, `(recipient_email, created_at)`, `(template_type, status)`, `(event_id)`, `(person_id)`
**Relations:** `belongsTo` organisation (nullable), event (nullable), person (nullable), user (nullable), triggeredBy → user
**Soft delete:** no — immutable audit table
---
## 3.5.12 Form Builder
> See `/dev-docs/ARCH-FORM-BUILDER.md` v1.2 for the authoritative
> specification. This SCHEMA.md section is a summary only and will be
> fully rewritten at the end of S6.
>
> **Legacy tables retained intentionally.** The tables
> `registration_form_fields`, `person_field_values`, and
> `registration_field_templates` remain in the schema through end of S1.
> They are dropped atomically in the first S2 commit together with the
> removal of legacy controllers, services, requests, resources, policies,
> and routes. DevSeeder and FormBuilderDevSeeder no longer write to them;
> they hold zero rows in dev but the schema is preserved for environments
> with real legacy data that will be migrated via
> `forms:migrate-legacy-data`.
**Crosswalk: legacy `volunteer_profiles` → new locations**
| Legacy column | New location |
| ------------------------------ | ----------------------------------------------- |
| `bio` | `user_profiles.bio` |
| `photo_url` | `user_profiles.photo_url` |
| `tshirt_size` | `form_fields` (Pattern B, per event) |
| `first_aid` | `person_tags` (system-seeded, S3) |
| `driving_licence` | `person_tags` (system-seeded, S3) |
| `allergies` | `form_fields` (Pattern B, per event) |
| `access_requirements` | `form_fields` (Pattern B, per event) |
| `emergency_contact_name` | `user_profiles.emergency_contact_name` |
| `emergency_contact_phone` | `user_profiles.emergency_contact_phone` |
| `reliability_score` | `user_profiles.reliability_score` |
| `is_ambassador` | `user_profiles.is_ambassador` |
### New tables (S1)
| Table | Purpose |
| --- | --- |
| `user_profiles` | User-universal profile (bio, photo, emergency contact, reliability_score, is_ambassador, opaque settings JSON). 1:1 with `users`, auto-created by `UserObserver`. ARCH §4.13. |
| `form_schemas` | Form definition. Polymorphic owner (event / user_profile / artist / company / null). Carries purpose, submission_mode, is_published, public_token, schema_snapshot policy, freeze_on_submit, retention/consent. ARCH §4.1. |
| `form_schema_sections` | Optional sections within a schema (used when `section_level_submit=true`). ARCH §4.8. |
| `form_field_library` | Reusable cross-schema field definitions. ARCH §4.7. |
| `form_fields` | Field within a schema. `field_type` stored as string (custom types via `CustomFieldTypeRegistry`). Carries binding (Pattern A/B/C), is_filterable, is_pii, conditional_logic, value_storage_hint. ARCH §4.2. |
| `form_submissions` | One submission per `(schema, subject)`. Polymorphic subject. Carries status, review_status, schema_snapshot copy, submission lifecycle timestamps, search_index. ARCH §4.3. |
| `form_submission_section_statuses` | Per-section status when `section_level_submit=true`. ARCH §4.9. |
| `form_submission_delegations` | "X fills in this submission on behalf of Y". ARCH §4.10. |
| `form_values` | EAV row per `(submission, field)`. Integer AI PK for fast joins. Typed columns (value_indexed/number/date/bool) populated by `FormValueObserver`. ARCH §4.4. |
| `form_value_options` | Filter pivot for multi-value fields (MULTISELECT/CHECKBOX_LIST/TAG_PICKER). Rebuilt by observer. ARCH §4.5. |
| `form_templates` | Org-scoped reusable schema snapshots. `is_system` for shipped templates. ARCH §4.6. |
| `form_schema_webhooks` | Webhook subscriptions per schema. URL/secret encrypted via Eloquent cast. ARCH §4.11. |
| `form_webhook_deliveries` | Webhook delivery audit + retry queue. ARCH §4.12. |
**Activity log strategy:** explicit calls via `FormSchema::logSchemaChange()` and `FormField::logFieldChange()` — no `LogsActivity` trait (would produce noise). Only impactful events logged (publish toggle, purpose change, binding change, is_pii toggle, etc.). Bulk-fixture suppression via `App\Support\ActivityLog::suppressed(fn() => …)` which flips `config('activitylog.enabled')` for the callback.
**Multi-tenancy:** `OrganisationScope` applied on `FormSchema`, `FormTemplate`, `FormFieldLibrary`. Other form-builder tables inherit isolation through their parent schema; `FormSchemaWebhook` documents this discipline explicitly via a docblock warning to never query directly without an eager constraint.