feat(form-builder): form_field_configs relational table + non-validation key split + drop validation_rules JSON columns

This commit is contained in:
2026-04-24 22:42:35 +02:00
parent 9d2758a42c
commit d494478c08
31 changed files with 1233 additions and 60 deletions

View File

@@ -1,10 +1,26 @@
# Crewli — Core Database Schema
> Source: Design Document v1.3 — Section 3.5
> **Version: 2.3** — Updated April 2026
> **Version: 2.4** — Updated April 2026
>
> **Changelog:**
>
> - v2.4: WS-5b completion — `form_field_configs` relational table lands
> alongside `form_field_validation_rules` (both from WS-5b). Holds
> non-validation per-field configuration (`tag_categories`,
> `storage_disk`) that would have polluted the validation-rules table
> had it stayed there. Same polymorphic-morph pattern (owner aliases
> `form_field` / `form_field_library`, reused from WS-5a). The
> `validation_rules` JSON columns on `form_fields` and
> `form_field_library` are **dropped** by this migration pair — the
> entire pre-WS-5b bag now lives relationally across two tables.
> Schema snapshots gain a parallel top-level `configs` key on each
> field entry; historical snapshots pre-WS-5b remain immutable with
> the legacy merged shape. Breaking frontend contract: portal +
> organizer SPAs switched from reading `field.validation_rules.min`
> etc. to the canonical post-WS-5b keys (`min_value`, `max_length`,
> `max_selected`, etc.) per ARCH v1.6 §17.5 and addendum Q3 WS-5b
> Uitvoering.
> - v2.3: WS-5b (partial) — `form_field_validation_rules` relational table
> replaces the `validation_rules` JSON on `form_fields` and
> `form_field_library`. Typed `rule_type` column + per-rule `parameters`
@@ -1989,7 +2005,7 @@ that aggregates the user's submitted, non-test `form_submissions`.
| `is_active` | bool | default: true |
| `created_at`, `updated_at` | timestamps | |
**Relations:** `belongsTo` organisation; `hasMany` form_fields via `library_field_id`; `morphMany` form_field_bindings as `owner`; `morphMany` form_field_validation_rules as `owner`
**Relations:** `belongsTo` organisation; `hasMany` form_fields via `library_field_id`; `morphMany` form_field_bindings as `owner`; `morphMany` form_field_validation_rules as `owner`; `morphMany` form_field_configs as `owner`
**Indexes:** `(organisation_id, field_type)`, `(organisation_id, is_active)`
**Unique constraint:** `UNIQUE(organisation_id, slug)`
**Global scope:** `OrganisationScope`
@@ -2045,7 +2061,7 @@ that aggregates the user's submitted, non-test `form_submissions`.
| `created_at`, `updated_at` | timestamps | |
| `deleted_at` | timestamp nullable | Soft delete preserves history |
**Relations:** `belongsTo` schema, section (nullable), libraryField; `hasMany` form_values; `morphMany` form_field_bindings as `owner`; `morphMany` form_field_validation_rules as `owner`
**Relations:** `belongsTo` schema, section (nullable), libraryField; `hasMany` form_values; `morphMany` form_field_bindings as `owner`; `morphMany` form_field_validation_rules as `owner`; `morphMany` form_field_configs as `owner`
**Indexes:** `(form_schema_id, sort_order)`, `(form_schema_id, is_filterable)`, `(library_field_id)`, `(form_schema_id, slug)`
**Soft delete:** yes
@@ -2124,6 +2140,32 @@ that aggregates the user's submitted, non-test `form_submissions`.
---
### `form_field_configs`
> Parallel sibling to `form_field_validation_rules` — holds
> non-validation per-field configuration (tag-picker category filters,
> upload disk selection). Keeps `form_field_validation_rules`
> semantically pure (ARCH-FORM-BUILDER.md §17.5; addendum Q3 strict-
> enterprise decision). Same polymorphic-morph pattern as the binding
> and validation-rules tables.
| Column | Type | Notes |
| -------------- | ----------------- | ---------------------------------------------------------------------- |
| `id` | ULID | PK |
| `owner_type` | string(40) | morph alias: `form_field` or `form_field_library` |
| `owner_id` | ULID | parent row |
| `config_type` | string(40) | `FormFieldConfigType` case (`tag_categories`, `storage_disk`) |
| `parameters` | JSON | Per-config-type bag (`{"categories":[string]}`, `{"disk":string}`) |
| `created_at`, `updated_at` | timestamps | |
**Relations:** `morphTo` owner (`form_field` or `form_field_library`)
**Indexes:** `(config_type)`, `(owner_type, owner_id)`
**Unique constraint:** `UNIQUE(owner_type, owner_id, config_type)`
**Global scope:** `FormFieldConfigScope` — third sibling in the scope family (after `FormFieldBindingScope` and `FormFieldValidationRuleScope`), same UNION shape. Escape hatch: `withoutGlobalScope(FormFieldConfigScope::class)`.
**Soft delete:** no — configs are current state, not audit
---
### `form_submissions`
> One submission per `(schema, subject)` in `single` / `draft_single`