feat(form-builder): FormFieldBindingService + library-to-field row copy + snapshot writer

WS-5a commit 2 of 4.

FormFieldBindingService owns all writes to the relational binding table.
Validation against config/form_binding.php entity-column registry lives here
(ARCH §6.2).

FormFieldService::insertFromLibrary now calls copyBindings instead of
hydrating JSON — the Q3 row-copy mandate. Library and field bindings share
the same table; insertion is a row-clone operation.

Snapshot writer (FormSubmissionService::buildSnapshot) serialises bindings
via toJsonShape so schema_snapshot JSON keeps its ARCH §4.6.1 / §6.3
contract. No snapshot format change.
API resources source binding output from the relational table via the same
serialiser — external shape preserved.

Tests: service transactional behaviour, copyBindings preservation,
snapshot parity, API resource parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 18:48:47 +02:00
parent af8a9da038
commit 6933e6d700
9 changed files with 712 additions and 5 deletions

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Http\Resources\FormBuilder;
use App\Models\FormBuilder\FormFieldLibrary;
use App\Services\FormBuilder\FormFieldBindingService;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -30,7 +31,9 @@ final class FormFieldLibraryResource extends JsonResource
'validation_rules' => $this->validation_rules,
'default_is_required' => (bool) $this->default_is_required,
'default_is_filterable' => (bool) $this->default_is_filterable,
'default_binding' => $this->default_binding,
'default_binding' => app(FormFieldBindingService::class)->toJsonShape(
$this->resource->bindings->first(),
),
'translations' => $this->translations,
'description' => $this->description,
'usage_count' => (int) ($this->usage_count ?? 0),

View File

@@ -7,6 +7,7 @@ namespace App\Http\Resources\FormBuilder;
use App\Enums\FormBuilder\FormFieldType;
use App\Models\FormBuilder\FormField;
use App\Models\PersonTag;
use App\Services\FormBuilder\FormFieldBindingService;
use App\Services\FormBuilder\FormLocaleResolver;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -49,7 +50,9 @@ final class FormFieldResource extends JsonResource
'is_unique' => (bool) $this->is_unique,
'is_pii' => (bool) $this->is_pii,
'display_width' => $this->display_width instanceof \BackedEnum ? $this->display_width->value : $this->display_width,
'binding' => $this->binding,
'binding' => app(FormFieldBindingService::class)->toJsonShape(
$this->resource->bindings->first(),
),
'conditional_logic' => $this->conditional_logic,
'role_restrictions' => $this->role_restrictions,
'translations' => $this->translations,