FormSchema::class, 'fk' => 'form_schema_id']; } protected $fillable = [ 'form_schema_id', 'form_schema_section_id', 'library_field_id', 'field_type', 'slug', 'label', 'help_text', 'section', 'options', 'is_required', 'is_filterable', 'is_portal_visible', 'is_admin_only', 'is_unique', 'is_pii', 'display_width', 'role_restrictions', 'translations', 'value_storage_hint', 'review_required', 'sort_order', ]; /** @var array */ protected $casts = [ 'options' => 'array', 'role_restrictions' => 'array', 'translations' => 'array', 'is_required' => 'bool', 'is_filterable' => 'bool', 'is_portal_visible' => 'bool', 'is_admin_only' => 'bool', 'is_unique' => 'bool', 'is_pii' => 'bool', 'review_required' => 'bool', 'display_width' => FormFieldDisplayWidth::class, 'value_storage_hint' => FormValueStorageHint::class, 'sort_order' => 'int', ]; public function schema(): BelongsTo { return $this->belongsTo(FormSchema::class, 'form_schema_id'); } public function section(): BelongsTo { return $this->belongsTo(FormSchemaSection::class, 'form_schema_section_id'); } public function libraryField(): BelongsTo { return $this->belongsTo(FormFieldLibrary::class, 'library_field_id'); } public function values(): HasMany { return $this->hasMany(FormValue::class); } public function bindings(): MorphMany { return $this->morphMany(FormFieldBinding::class, 'owner'); } public function validationRules(): MorphMany { return $this->morphMany(FormFieldValidationRule::class, 'owner'); } public function configs(): MorphMany { return $this->morphMany(FormFieldConfig::class, 'owner'); } public function conditionalLogicGroups(): HasMany { return $this->hasMany(FormFieldConditionalLogicGroup::class, 'form_field_id'); } /** * The tree root: the group with `parent_group_id IS NULL`. Fields * without any conditional logic return null. Only one root is * supported per field — enforced by the service layer's `replaceLogic`. */ public function rootConditionalLogicGroup(): ?FormFieldConditionalLogicGroup { return $this->conditionalLogicGroups() ->whereNull('parent_group_id') ->first(); } /** * Nuanced activity log (ARCH §17.1; S1 Phase 4b). Callers choose which * events are worth logging — e.g. created/deleted/restored, field_type * changed (value storage changes), binding changed, is_pii toggled, * is_filterable toggled (triggers backfill), structural options changes. * Conditional-logic changes emit `field.conditional_logic_replaced` * via FormFieldConditionalLogicService (ARCH §8; WS-5c commit 2). * NOT logged (noise): label/help_text/sort_order/translations. * * Bulk-fixture suppression: the activitylog.enabled config key is the * kill-switch. Seeders and one-shot commands wrap themselves in * App\Support\ActivityLog::suppressed(...). activity()->log() becomes * a silent no-op while disabled, so no guard is needed here. * * @param array $properties */ public function logFieldChange(string $event, array $properties = []): void { activity() ->performedOn($this) ->withProperties($properties) ->log($event); } }