Skip to content

Commit 9cbd6fe

Browse files
feat: add feature tests and address handling for case identity editing
- Implemented feature tests for editing and saving legal and effective residence addresses in case identity. - Updated `EditCaseIdentity` and `CreateCase` to support address handling, including the ability to copy legal residence to effective residence. - Enhanced display and formatting of `violence_types` across multiple schemas for better readability. - Adjusted `EnumEntry` to improve state handling for empty and non-standard enum values.
1 parent 96951f5 commit 9cbd6fe

File tree

7 files changed

+278
-26
lines changed

7 files changed

+278
-26
lines changed

app/Filament/Organizations/Resources/Cases/Pages/CreateCase.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\Filament\Organizations\Resources\Cases\Pages;
66

7+
use App\Enums\AddressType;
78
use App\Enums\CaseStatus;
89
use App\Filament\Organizations\Resources\Cases\CaseResource;
910
use App\Filament\Organizations\Resources\Cases\Schemas\AggressorFormSchema;
@@ -355,6 +356,8 @@ protected function afterCreate(): void
355356
}
356357
}
357358

359+
$this->createAddressesFromFormState($beneficiary, $state);
360+
358361
$roleIds = self::normalizeCaseTeamSelection($state['case_team'] ?? []);
359362
$currentUserId = auth()->id();
360363
if (! $currentUserId || $roleIds === null || empty($roleIds)) {
@@ -402,6 +405,57 @@ private static function normalizeCaseTeamSelection(array $selected): ?array
402405
return array_values(array_unique($roleIds));
403406
}
404407

408+
/**
409+
* Create legal_residence and effective_residence addresses from form state.
410+
*
411+
* @param array<int|string, mixed> $state
412+
*/
413+
private function createAddressesFromFormState(Beneficiary $beneficiary, array $state): void
414+
{
415+
$legalData = $state['legal_residence'] ?? [];
416+
$effectiveData = $state['effective_residence'] ?? [];
417+
$sameAsLegal = (bool) ($state['same_as_legal_residence'] ?? false);
418+
419+
if ($this->hasAddressData($legalData)) {
420+
$beneficiary->legal_residence()->create($this->buildAddressAttributes($legalData, AddressType::LEGAL_RESIDENCE));
421+
}
422+
423+
$effectivePayload = $sameAsLegal ? ($legalData ?? []) : $effectiveData;
424+
if ($this->hasAddressData($effectivePayload)) {
425+
$beneficiary->effective_residence()->create($this->buildAddressAttributes($effectivePayload, AddressType::EFFECTIVE_RESIDENCE));
426+
}
427+
}
428+
429+
/**
430+
* @param array<string, mixed> $data
431+
*/
432+
private function hasAddressData(array $data): bool
433+
{
434+
$countyId = $data['county_id'] ?? null;
435+
$cityId = $data['city_id'] ?? null;
436+
$address = $data['address'] ?? null;
437+
438+
return $countyId !== null || $cityId !== null || filled($address);
439+
}
440+
441+
/**
442+
* @param array<string, mixed> $data
443+
* @return array<string, mixed>
444+
*/
445+
private function buildAddressAttributes(array $data, AddressType $type): array
446+
{
447+
$attrs = [
448+
'country_id' => $data['country_id'] ?? null,
449+
'county_id' => $data['county_id'] ?? null,
450+
'city_id' => $data['city_id'] ?? null,
451+
'address' => $data['address'] ?? null,
452+
'environment' => $data['environment'] ?? null,
453+
'address_type' => $type,
454+
];
455+
456+
return array_filter($attrs, fn ($v) => $v !== null && $v !== '');
457+
}
458+
405459
/**
406460
* @param array<string, mixed> $item
407461
* @return array<string, mixed>

app/Filament/Organizations/Resources/Cases/Pages/EditCaseIdentity.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use App\Actions\BackAction;
88
use App\Concerns\PreventSubmitFormOnEnter;
9+
use App\Enums\AddressType;
910
use App\Filament\Organizations\Resources\Cases\CaseResource;
1011
use App\Filament\Organizations\Resources\Cases\Schemas\BeneficiaryIdentityFormSchema;
1112
use App\Models\Beneficiary;
@@ -99,4 +100,79 @@ protected function mutateFormDataBeforeFill(array $data): array
99100

100101
return $data;
101102
}
103+
104+
/**
105+
* @param array<string, mixed> $data
106+
* @return array<string, mixed>
107+
*/
108+
protected function mutateFormDataBeforeSave(array $data): array
109+
{
110+
unset($data['legal_residence'], $data['effective_residence']);
111+
112+
return $data;
113+
}
114+
115+
protected function afterSave(): void
116+
{
117+
$record = $this->getRecord();
118+
119+
if (! $record instanceof Beneficiary) {
120+
return;
121+
}
122+
123+
$state = $this->form->getState();
124+
$legalData = $state['legal_residence'] ?? [];
125+
$effectiveData = $state['effective_residence'] ?? [];
126+
$sameAsLegal = (bool) ($state['same_as_legal_residence'] ?? false);
127+
128+
if ($this->hasAddressData($legalData)) {
129+
$attrs = array_merge(
130+
$this->buildAddressAttributes($legalData, AddressType::LEGAL_RESIDENCE),
131+
['address_type' => AddressType::LEGAL_RESIDENCE]
132+
);
133+
$record->legal_residence()->updateOrCreate([], $attrs);
134+
} else {
135+
$record->legal_residence?->delete();
136+
}
137+
138+
$effectivePayload = $sameAsLegal ? $legalData : $effectiveData;
139+
if ($this->hasAddressData($effectivePayload)) {
140+
$attrs = array_merge(
141+
$this->buildAddressAttributes($effectivePayload, AddressType::EFFECTIVE_RESIDENCE),
142+
['address_type' => AddressType::EFFECTIVE_RESIDENCE]
143+
);
144+
$record->effective_residence()->updateOrCreate([], $attrs);
145+
} else {
146+
$record->effective_residence?->delete();
147+
}
148+
}
149+
150+
/**
151+
* @param array<string, mixed> $data
152+
*/
153+
private function hasAddressData(array $data): bool
154+
{
155+
$countyId = $data['county_id'] ?? null;
156+
$cityId = $data['city_id'] ?? null;
157+
$address = $data['address'] ?? null;
158+
159+
return $countyId !== null || $cityId !== null || filled($address);
160+
}
161+
162+
/**
163+
* @param array<string, mixed> $data
164+
* @return array<string, mixed>
165+
*/
166+
private function buildAddressAttributes(array $data, AddressType $type): array
167+
{
168+
$attrs = [
169+
'country_id' => $data['country_id'] ?? null,
170+
'county_id' => $data['county_id'] ?? null,
171+
'city_id' => $data['city_id'] ?? null,
172+
'address' => $data['address'] ?? null,
173+
'environment' => $data['environment'] ?? null,
174+
];
175+
176+
return array_filter($attrs, fn ($v) => $v !== null && $v !== '');
177+
}
102178
}

app/Filament/Organizations/Resources/Cases/Schemas/CaseInfolist.php

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,14 @@ public static function configure(Schema $schema): Schema
149149
->placeholder(''),
150150
TextEntry::make('violence_types')
151151
->label(__('field.aggressor_violence_types'))
152-
->formatStateUsing(function ($state): string {
153-
if ($state === null) {
152+
->state(function (Aggressor $record): string {
153+
$types = $record->violence_types;
154+
155+
if ($types === null || $types->isEmpty()) {
154156
return '';
155157
}
156-
if ($state instanceof Collection) {
157-
return $state->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->join(', ');
158-
}
159-
if (is_iterable($state)) {
160-
return collect($state)->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->join(', ');
161-
}
162158

163-
return '';
159+
return $types->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->implode(', ');
164160
})
165161
->placeholder(''),
166162
TextEntry::make('has_protection_order')
@@ -424,10 +420,24 @@ public static function configure(Schema $schema): Schema
424420
RepeatableEntry::make('specialistsTeam')
425421
->state(fn (Beneficiary $record) => $record->specialistsTeam()->with(['user', 'roleForDisplay'])->get())
426422
->schema([
427-
TextEntry::make('roleForDisplay.name')
428-
->label(__('case.view.role')),
429423
TextEntry::make('user.full_name')
430-
->label(__('case.view.specialist')),
424+
->label(__('beneficiary.section.specialists.labels.name'))
425+
->formatStateUsing(function (mixed $state, TextEntry $entry): string {
426+
$specialist = $entry->getContainer()?->getRecord();
427+
428+
return $specialist instanceof \App\Models\Specialist && $specialist->user !== null
429+
? $specialist->user->full_name
430+
: '';
431+
}),
432+
TextEntry::make('roleForDisplay.name')
433+
->label(__('beneficiary.section.specialists.labels.roles'))
434+
->formatStateUsing(function (mixed $state, TextEntry $entry): string {
435+
$specialist = $entry->getContainer()?->getRecord();
436+
437+
return $specialist instanceof \App\Models\Specialist && $specialist->roleForDisplay !== null
438+
? $specialist->roleForDisplay->name
439+
: '';
440+
}),
431441
])
432442
->columns(2)
433443
->contained(false),

app/Filament/Organizations/Resources/Cases/Schemas/PersonalInfoInfolist.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,17 @@ protected static function aggressorSection(): array
303303
->placeholder(__('placeholder.select_one')),
304304

305305
TextEntry::make('violence_types')
306-
->label(__('field.aggressor_violence_types')),
306+
->label(__('field.aggressor_violence_types'))
307+
->state(function (\App\Models\Aggressor $record): string {
308+
$types = $record->violence_types;
309+
310+
if ($types === null || $types->isEmpty()) {
311+
return '';
312+
}
313+
314+
return $types->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->implode(', ');
315+
})
316+
->placeholder(''),
307317
]),
308318

309319
Grid::make()

app/Filament/Organizations/Schemas/BeneficiaryResource/InitialEvaluationSchema.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,19 +187,28 @@ public static function getViolenceInfolistComponents(): array
187187
->columns(2)
188188
->schema([
189189
TextEntry::make('violence_types')
190-
->label(__('beneficiary.section.initial_evaluation.labels.violence_type')),
190+
->label(__('beneficiary.section.initial_evaluation.labels.violence_type'))
191+
->formatStateUsing(fn (mixed $state): ?string => filled($state)
192+
? collect($state)->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->implode('; ')
193+
: null)
194+
->placeholder(''),
191195

192196
EnumEntry::make('violence_primary_type')
193197
->label(__('beneficiary.section.initial_evaluation.labels.violence_primary_type'))
198+
->enumClass(Violence::class)
194199
->placeholder(__('beneficiary.placeholder.violence_primary_type')),
195200

196201
EnumEntry::make('frequency_violence')
197202
->label(__('beneficiary.section.initial_evaluation.labels.frequency_violence'))
203+
->enumClass(Frequency::class)
198204
->placeholder(__('beneficiary.placeholder.frequency_violence')),
199205

200206
TextEntry::make('violence_means')
201207
->label(__('beneficiary.section.initial_evaluation.labels.violence_means'))
202-
->placeholder(__('beneficiary.placeholder.violence_means_specify')),
208+
->formatStateUsing(fn (mixed $state): ?string => filled($state)
209+
? collect($state)->map(fn ($v) => is_object($v) && method_exists($v, 'getLabel') ? $v->getLabel() : (string) $v)->implode('; ')
210+
: null)
211+
->placeholder(''),
203212

204213
TextEntry::make('violence_means_specify')
205214
->label(__('beneficiary.section.initial_evaluation.labels.violence_means_specify'))

app/Infolists/Components/EnumEntry.php

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
class EnumEntry extends TextEntry
1212
{
13-
protected string | null $enumClass = null;
13+
protected ?string $enumClass = null;
1414

15-
public function enumClass(string | null $enumClass): static
15+
public function enumClass(?string $enumClass): static
1616
{
1717
$this->enumClass = $enumClass;
1818

@@ -21,30 +21,37 @@ public function enumClass(string | null $enumClass): static
2121

2222
protected function setUp(): void
2323
{
24-
$this->formatStateUsing(function ($state) {
24+
$this->formatStateUsing(function (mixed $state): mixed {
25+
if ($state === null || $state === '') {
26+
return null;
27+
}
28+
2529
if ($state instanceof BackedEnum) {
26-
return $state->getLabel();
30+
return method_exists($state, 'getLabel') ? $state->getLabel() : (string) $state->value;
2731
}
2832

2933
if ($state === '-') {
3034
return $state;
3135
}
3236

3337
if ($this->enumClass) {
34-
$state = collect(explode(',', $state));
38+
$stateCollection = collect(explode(',', (string) $state))->map(fn (string $item): string => trim($item))->filter();
3539
$reflectionClass = new ReflectionEnum($this->enumClass);
36-
$returnType = $reflectionClass->getBackingtype();
40+
$returnType = $reflectionClass->getBackingType();
3741

38-
return $state->map(
39-
function ($item) use ($returnType) {
40-
$item = match ($returnType->getName()) {
42+
return $stateCollection->map(
43+
function ($item) use ($returnType): ?string {
44+
$converted = match ($returnType->getName()) {
4145
'string' => trim((string) $item),
4246
'int' => (int) $item,
47+
default => $item,
4348
};
4449

45-
return $this->enumClass::tryFrom($item)?->getLabel();
50+
$enum = $this->enumClass::tryFrom($converted);
51+
52+
return $enum !== null && method_exists($enum, 'getLabel') ? $enum->getLabel() : (string) $item;
4653
}
47-
)->join(', ');
54+
)->filter()->join(', ') ?: null;
4855
}
4956

5057
return $state;

0 commit comments

Comments
 (0)