$this->id, 'client_number' => $this->client_number, 'name' => $this->name, 'notes' => $this->notes ?? [], 'created_at' => $this->created_at->toISOString(), 'updated_at' => $this->updated_at->toISOString(), // List/search endpoints load these via withCount/withMax; absent on // the detail endpoint, so guard before exposing them. 'history_count' => $this->whenNotNull($this->history_entries_count), 'last_history_at' => $this->when( $this->history_entries_count !== null, fn () => $this->history_entries_max_created_at ? Carbon::parse($this->history_entries_max_created_at)->toISOString() : null, ), // Only present on the detail endpoint (where the relation is eager // loaded); omitted from list responses to avoid per-row queries. 'custom_fields' => $this->when( $this->resource->relationLoaded('customFieldValues'), fn () => $this->buildCustomFields(), ), ]; } /** * Merge every active field definition (ordered) with this client's value, * so unset fields still render with an empty state. * * @return array> */ private function buildCustomFields(): array { $valuesByDefinition = $this->customFieldValues->keyBy('custom_field_definition_id'); return CustomFieldDefinition::orderBy('sort_order')->orderBy('id')->get() ->map(function (CustomFieldDefinition $definition) use ($valuesByDefinition) { $value = $valuesByDefinition->get($definition->id); return [ 'definition_id' => $definition->id, 'key' => $definition->key, 'label' => $definition->label, 'type' => $definition->type->value, 'options' => $definition->options ?? [], 'is_required' => $definition->is_required, 'value' => $value?->value, 'author_name' => $value?->author?->name, 'updated_at' => $value?->updated_at?->toISOString(), ]; }) ->all(); } }