Mirrors the frontend catch-up in 570f17e: a substantial amount of
finished-but-never-committed backend work had accumulated locally.
- New CustomField module: admin-managed field definitions per client,
reorderable, typed values stored per client (backing the frontend's
ClientCustomFieldCard + settings/fields page)
- History entries switch from a hardcoded type enum to a dynamic,
admin-managed HistoryType module (reorderable, backing
settings/history-types)
- New Import module: CSV client import (backing settings/import)
- Clients table gains soft deletes (deleted_at)
- Supporting factories, seeders, policies, and feature tests for all
of the above
All 63 backend tests pass locally before this commit.
67 lines
2.6 KiB
PHP
67 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Client\Resources;
|
|
|
|
use App\Modules\CustomField\Models\CustomFieldDefinition;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\JsonResource;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class ClientResource extends JsonResource
|
|
{
|
|
public function toArray(Request $request): array
|
|
{
|
|
return [
|
|
'id' => $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<int, array<string, mixed>>
|
|
*/
|
|
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();
|
|
}
|
|
}
|