From 7b3c664f3f402418251144d9211ae0928d298ab3 Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Wed, 11 Jun 2025 08:20:21 +0545 Subject: [PATCH 01/10] refactor(tests): update middleware reference to SetupSite --- tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index bbfac22..226977c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,7 +28,7 @@ protected function setUp(): void $this->withoutVite(); - // Ensure SetupTenant middleware is applied during tests + // Ensure SetupSite middleware is applied during tests // This is done here since the "withMiddleware" method in workbench/bootstrap/app.php does not seem to work // $this->withMiddleware(SetupTenant::class) also does not work app(Kernel::class)->pushMiddleware(SetupSite::class); From 8d62e335b2cc668c4f50f6cd13c5bdd54de9fcfd Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Fri, 20 Jun 2025 01:47:31 +0545 Subject: [PATCH 02/10] feat: adding user address book --- database/factories/AddressFactory.php | 42 ++++++ ..._16_150346_create_user_addresses_table.php | 42 ++++++ database/seeders/UserSeeder.php | 5 + src/Enums/AddressType.php | 37 +++++ src/Filament/Resources/UserResource.php | 3 +- .../Resources/UserResource/Pages/EditUser.php | 10 ++ .../Resources/UserResource/Pages/ViewUser.php | 10 ++ .../AddressesRelationManager.php | 133 ++++++++++++++++++ src/Models/Site.php | 7 + src/Models/User.php | 7 + src/Models/User/Address.php | 63 +++++++++ src/Providers/AdminPanelProvider.php | 2 +- .../AddressesRelationManagerTest.php | 25 ++++ 13 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 database/factories/AddressFactory.php create mode 100644 database/migrations/2025_06_16_150346_create_user_addresses_table.php create mode 100644 src/Enums/AddressType.php create mode 100644 src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php create mode 100644 src/Models/User/Address.php create mode 100644 tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php diff --git a/database/factories/AddressFactory.php b/database/factories/AddressFactory.php new file mode 100644 index 0000000..49aac2a --- /dev/null +++ b/database/factories/AddressFactory.php @@ -0,0 +1,42 @@ + + */ + public function definition(): array + { + return [ + 'recipient' => fake()->name(), + 'company_name' => fake()->company(), + 'company_vat_id' => fake()->numerify('##########'), + 'street_address' => [ + fake()->streetAddress(), + fake()->streetAddress(), + ], + 'postal_code' => fake()->postcode(), + 'city' => fake()->city(), + 'type' => fake()->randomElement(AddressType::cases()), + 'country_id' => Country::inRandomOrder()->first()?->id ?? Country::factory()->create()->id, + 'user_id' => User::inRandomOrder()->first()?->id ?? User::factory()->create()->id, + ]; + } +} diff --git a/database/migrations/2025_06_16_150346_create_user_addresses_table.php b/database/migrations/2025_06_16_150346_create_user_addresses_table.php new file mode 100644 index 0000000..bf7807c --- /dev/null +++ b/database/migrations/2025_06_16_150346_create_user_addresses_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('recipient', 100); + $table->string('company_name', 100) + ->nullable(); + $table->string('company_vat_id', 50) + ->nullable(); + $table->json('street_address'); + $table->string('postal_code', 50); + $table->string('city', 100); + $table->enum('type', ['default_address', 'company_address']) + ->default('default_address'); + $table->string('country_id', 2); + $table->foreignId('user_id') + ->constrained('users') + ->cascadeOnDelete(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_addresses'); + } +}; diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 499192c..1e6d45d 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -4,6 +4,7 @@ use Eclipse\Core\Models\Site; use Eclipse\Core\Models\User; +use Eclipse\Core\Models\User\Address; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; @@ -41,6 +42,10 @@ public function run(): void foreach (Site::all() as $site) { $user->sites()->attach($site); + Address::factory()->create([ + 'user_id' => $user->id, + ]); + if (isset($preset['role'])) { setPermissionsTeamId($site->id); $user->assignRole($preset['role'])->save(); diff --git a/src/Enums/AddressType.php b/src/Enums/AddressType.php new file mode 100644 index 0000000..fbdd800 --- /dev/null +++ b/src/Enums/AddressType.php @@ -0,0 +1,37 @@ + 'Default', + self::COMPANY_ADDRESS => 'Company' + }; + } + + public function getDescription(): ?string + { + return match ($this) { + self::DEFAULT_ADDRESS => 'This is the default address', + self::COMPANY_ADDRESS => 'This is a company address' + }; + } + + public function getColor(): ?string + { + return match ($this) { + self::DEFAULT_ADDRESS => 'primary', + self::COMPANY_ADDRESS => 'warning' + }; + } +} diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php index 0d0a444..94f686e 100644 --- a/src/Filament/Resources/UserResource.php +++ b/src/Filament/Resources/UserResource.php @@ -5,6 +5,7 @@ use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions; use Eclipse\Core\Filament\Exports\TableExport; use Eclipse\Core\Filament\Resources; +use Eclipse\Core\Filament\Resources\UserResource\RelationManagers\AddressesRelationManager; use Eclipse\Core\Models\User; use Filament\Forms; use Filament\Forms\Form; @@ -240,7 +241,7 @@ public static function infolist(Infolist $infolist): Infolist public static function getRelations(): array { return [ - // + AddressesRelationManager::class, ]; } diff --git a/src/Filament/Resources/UserResource/Pages/EditUser.php b/src/Filament/Resources/UserResource/Pages/EditUser.php index b40ffdf..0219e66 100644 --- a/src/Filament/Resources/UserResource/Pages/EditUser.php +++ b/src/Filament/Resources/UserResource/Pages/EditUser.php @@ -10,6 +10,16 @@ class EditUser extends EditRecord { protected static string $resource = UserResource::class; + public function hasCombinedRelationManagerTabsWithContent(): bool + { + return true; + } + + public function getContentTabLabel(): ?string + { + return __('Edit User'); + } + protected function getHeaderActions(): array { return [ diff --git a/src/Filament/Resources/UserResource/Pages/ViewUser.php b/src/Filament/Resources/UserResource/Pages/ViewUser.php index d7f450e..39e5d76 100644 --- a/src/Filament/Resources/UserResource/Pages/ViewUser.php +++ b/src/Filament/Resources/UserResource/Pages/ViewUser.php @@ -11,6 +11,16 @@ class ViewUser extends ViewRecord { protected static string $resource = UserResource::class; + public function hasCombinedRelationManagerTabsWithContent(): bool + { + return true; + } + + public function getContentTabLabel(): ?string + { + return __('View User'); + } + protected function getHeaderActions(): array { return [ diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php new file mode 100644 index 0000000..641e859 --- /dev/null +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -0,0 +1,133 @@ +live() + ->default(AddressType::DEFAULT_ADDRESS) + ->required() + ->options(AddressType::class), + Forms\Components\TextInput::make('recipient') + ->maxLength(100) + ->label('Full name') + ->required(), + Forms\Components\TextInput::make('company_name') + ->visible(fn (Get $get): bool => ($get('type') === AddressType::COMPANY_ADDRESS->value)) + ->required() + ->maxLength(100), + Forms\Components\TextInput::make('company_vat_id') + ->visible(fn (Get $get): bool => ($get('type') === AddressType::COMPANY_ADDRESS->value)) + ->label('Company VAT ID') + ->maxLength(50), + Forms\Components\Repeater::make('street_address') + ->minItems(1) + ->maxItems(3) + ->required() + ->simple( + Forms\Components\TextInput::make('street_address') + ->maxLength(255) + ->required() + ), + Forms\Components\Split::make([ + Forms\Components\TextInput::make('postal_code') + ->required() + ->maxLength(50), + Forms\Components\TextInput::make('city') + ->required() + ->maxLength(100), + ]), + Forms\Components\Select::make('country_id') + ->required() + ->relationship('country', 'name'), + ]; + } + + public static function getAddressInfolist(): array + { + return [ + Infolists\Components\Grid::make()->schema([ + Infolists\Components\TextEntry::make('type') + ->badge(), + Infolists\Components\TextEntry::make('recipient'), + Infolists\Components\TextEntry::make('company_name') + ->visible(fn ($record): bool => ($record->type === AddressType::COMPANY_ADDRESS->value)), + Infolists\Components\TextEntry::make('company_vat_id') + ->visible(fn ($record): bool => ($record->type === AddressType::COMPANY_ADDRESS->value)) + ->default('-') + ->label('Company VAT ID'), + Infolists\Components\TextEntry::make('street_address') + ->listWithLineBreaks(), + Infolists\Components\TextEntry::make('country') + ->formatStateUsing(fn ($state) => "{$state->name} {$state->flag}"), + Infolists\Components\Split::make([ + Infolists\Components\TextEntry::make('postal_code'), + Infolists\Components\TextEntry::make('city'), + ])->columnSpanFull(), + ]), + ]; + } + + public function table(Table $table): Table + { + return $table + ->modifyQueryUsing(fn (Builder $query) => $query->with(['country'])) + ->columns([ + Tables\Columns\TextColumn::make('recipient') + ->weight(FontWeight::Bold) + ->description(function ($record) { + $streetAddresses = implode('
', $record->street_address); + $countryInfo = "{$record->country->flag} {$record->country->name}"; + + return new HtmlString("{$streetAddresses}
{$countryInfo}"); + }) + ->searchable(['recipient', 'street_address', 'country_id']), + Tables\Columns\TextColumn::make('type') + ->badge(), + ]) + ->filters([ + Tables\Filters\SelectFilter::make('type') + ->options(AddressType::class), + Tables\Filters\TrashedFilter::make(), + ]) + ->actions([ + Tables\Actions\ViewAction::make() + ->infolist(self::getAddressInfolist()), + Tables\Actions\EditAction::make() + ->form(self::getAddressForm()), + ]) + ->bulkActions([ + BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + Tables\Actions\ForceDeleteBulkAction::make(), + Tables\Actions\RestoreBulkAction::make(), + ]), + ]) + ->headerActions([ + Tables\Actions\CreateAction::make() + ->label('Add new address') + ->icon('heroicon-o-plus-circle') + ->form(self::getAddressForm()), + ]); + } +} diff --git a/src/Models/Site.php b/src/Models/Site.php index f824a6a..a197689 100644 --- a/src/Models/Site.php +++ b/src/Models/Site.php @@ -5,6 +5,7 @@ use Eclipse\Core\Database\Factories\SiteFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; class Site extends Model { @@ -36,4 +37,10 @@ protected static function newFactory(): SiteFactory { return SiteFactory::new(); } + + /** @return HasMany<\Eclipse\Core\Models\User\Role, self> */ + public function roles(): HasMany + { + return $this->hasMany(\Eclipse\Core\Models\User\Role::class); + } } diff --git a/src/Models/User.php b/src/Models/User.php index 5d99bd5..3a105c7 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -3,6 +3,7 @@ namespace Eclipse\Core\Models; use Eclipse\Core\Database\Factories\UserFactory; +use Eclipse\Core\Models\User\Address; use Exception; use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\HasAvatar; @@ -10,6 +11,7 @@ use Filament\Panel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -87,6 +89,11 @@ public function sites() return $this->belongsToMany(Site::class, 'site_has_user'); } + public function addresses(): HasMany + { + return $this->hasMany(Address::class); + } + public function getFilamentAvatarUrl(): ?string { return $this->getMedia('avatars')->first()?->getUrl(); diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php new file mode 100644 index 0000000..63fc04d --- /dev/null +++ b/src/Models/User/Address.php @@ -0,0 +1,63 @@ + + */ + protected $fillable = [ + 'recipient', + 'company_name', + 'company_vat_id', + 'street_address', + 'postal_code', + 'city', + 'type', + 'country_id', + 'user_id', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'street_address' => 'array', + 'type' => AddressType::class, + 'user_id' => 'integer', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function country(): BelongsTo + { + return $this->belongsTo(Country::class); + } + + protected static function newFactory(): AddressFactory + { + return AddressFactory::new(); + } +} diff --git a/src/Providers/AdminPanelProvider.php b/src/Providers/AdminPanelProvider.php index 9df4a72..490c9c7 100644 --- a/src/Providers/AdminPanelProvider.php +++ b/src/Providers/AdminPanelProvider.php @@ -71,7 +71,7 @@ public function panel(Panel $panel): Panel 'gray' => Color::Slate, ]) ->topNavigation() - ->brandName(fn () => Registry::getSite()->name) + ->brandName(fn () => Registry::getSite()?->name) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverResources(in: $package_src.'Filament/Resources', for: 'Eclipse\\Core\\Filament\\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') diff --git a/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php new file mode 100644 index 0000000..4f622a0 --- /dev/null +++ b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php @@ -0,0 +1,25 @@ +set_up_super_admin_and_tenant(); +}); + +it('can render addresses relation manager on edit user page', function (): void { + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ])->assertSuccessful(); +}); + +it('can render addresses relation manager on view user page', function (): void { + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => ViewUser::class, + ])->assertSuccessful(); +}); From 491ff21dc0e2e962eabb5406a339c836cd1dd3c7 Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Fri, 20 Jun 2025 17:02:43 +0545 Subject: [PATCH 03/10] fix: refactoring & adding new test cases --- database/factories/AddressFactory.php | 14 +- ..._16_150346_create_user_addresses_table.php | 6 +- src/Enums/AddressType.php | 11 +- .../AddressesRelationManager.php | 69 +++++++-- src/Models/User/Address.php | 3 +- .../AddressesRelationManagerTest.php | 136 +++++++++++++++++- 6 files changed, 205 insertions(+), 34 deletions(-) diff --git a/database/factories/AddressFactory.php b/database/factories/AddressFactory.php index 49aac2a..b28d220 100644 --- a/database/factories/AddressFactory.php +++ b/database/factories/AddressFactory.php @@ -24,17 +24,25 @@ class AddressFactory extends Factory */ public function definition(): array { + $type = match (fake()->numberBetween(1, 3)) { + 1 => [AddressType::COMPANY_ADDRESS->value], + 2 => [AddressType::DEFAULT_ADDRESS->value], + 3 => [AddressType::COMPANY_ADDRESS->value, AddressType::DEFAULT_ADDRESS->value], + }; + + $hasCompanyAddress = in_array(AddressType::COMPANY_ADDRESS->value, $type); + return [ 'recipient' => fake()->name(), - 'company_name' => fake()->company(), - 'company_vat_id' => fake()->numerify('##########'), + 'company_name' => $hasCompanyAddress ? fake()->company() : null, + 'company_vat_id' => $hasCompanyAddress ? fake()->numerify('##########') : null, 'street_address' => [ fake()->streetAddress(), fake()->streetAddress(), ], 'postal_code' => fake()->postcode(), 'city' => fake()->city(), - 'type' => fake()->randomElement(AddressType::cases()), + 'type' => $type, 'country_id' => Country::inRandomOrder()->first()?->id ?? Country::factory()->create()->id, 'user_id' => User::inRandomOrder()->first()?->id ?? User::factory()->create()->id, ]; diff --git a/database/migrations/2025_06_16_150346_create_user_addresses_table.php b/database/migrations/2025_06_16_150346_create_user_addresses_table.php index bf7807c..ef1a0bc 100644 --- a/database/migrations/2025_06_16_150346_create_user_addresses_table.php +++ b/database/migrations/2025_06_16_150346_create_user_addresses_table.php @@ -21,14 +21,16 @@ public function up(): void $table->json('street_address'); $table->string('postal_code', 50); $table->string('city', 100); - $table->enum('type', ['default_address', 'company_address']) - ->default('default_address'); + $table->json('type'); $table->string('country_id', 2); $table->foreignId('user_id') ->constrained('users') ->cascadeOnDelete(); $table->softDeletes(); $table->timestamps(); + $table->foreign('country_id') + ->references('id') + ->on('world_countries'); }); } diff --git a/src/Enums/AddressType.php b/src/Enums/AddressType.php index fbdd800..8fc65e9 100644 --- a/src/Enums/AddressType.php +++ b/src/Enums/AddressType.php @@ -2,11 +2,10 @@ namespace Eclipse\Core\Enums; -use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasDescription; use Filament\Support\Contracts\HasLabel; -enum AddressType: string implements HasColor, HasDescription, HasLabel +enum AddressType: string implements HasDescription, HasLabel { case DEFAULT_ADDRESS = 'default_address'; case COMPANY_ADDRESS = 'company_address'; @@ -26,12 +25,4 @@ public function getDescription(): ?string self::COMPANY_ADDRESS => 'This is a company address' }; } - - public function getColor(): ?string - { - return match ($this) { - self::DEFAULT_ADDRESS => 'primary', - self::COMPANY_ADDRESS => 'warning' - }; - } } diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php index 641e859..5101644 100644 --- a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -23,21 +23,22 @@ class AddressesRelationManager extends RelationManager public static function getAddressForm(): array { return [ - Forms\Components\Radio::make('type') + Forms\Components\CheckboxList::make('type') ->live() - ->default(AddressType::DEFAULT_ADDRESS) + ->default([AddressType::DEFAULT_ADDRESS->value]) ->required() - ->options(AddressType::class), + ->options(AddressType::class) + ->columns(2), Forms\Components\TextInput::make('recipient') ->maxLength(100) ->label('Full name') ->required(), Forms\Components\TextInput::make('company_name') - ->visible(fn (Get $get): bool => ($get('type') === AddressType::COMPANY_ADDRESS->value)) + ->visible(fn (Get $get): bool => in_array(AddressType::COMPANY_ADDRESS->value, $get('type') ?? [])) ->required() ->maxLength(100), Forms\Components\TextInput::make('company_vat_id') - ->visible(fn (Get $get): bool => ($get('type') === AddressType::COMPANY_ADDRESS->value)) + ->visible(fn (Get $get): bool => in_array(AddressType::COMPANY_ADDRESS->value, $get('type') ?? [])) ->label('Company VAT ID') ->maxLength(50), Forms\Components\Repeater::make('street_address') @@ -48,7 +49,8 @@ public static function getAddressForm(): array Forms\Components\TextInput::make('street_address') ->maxLength(255) ->required() - ), + ) + ->addActionLabel(__('Add address line')), Forms\Components\Split::make([ Forms\Components\TextInput::make('postal_code') ->required() @@ -68,12 +70,13 @@ public static function getAddressInfolist(): array return [ Infolists\Components\Grid::make()->schema([ Infolists\Components\TextEntry::make('type') - ->badge(), + ->badge() + ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)), Infolists\Components\TextEntry::make('recipient'), Infolists\Components\TextEntry::make('company_name') - ->visible(fn ($record): bool => ($record->type === AddressType::COMPANY_ADDRESS->value)), + ->visible(fn ($record): bool => self::hasCompanyAddress($record->type)), Infolists\Components\TextEntry::make('company_vat_id') - ->visible(fn ($record): bool => ($record->type === AddressType::COMPANY_ADDRESS->value)) + ->visible(fn ($record): bool => self::hasCompanyAddress($record->type)) ->default('-') ->label('Company VAT ID'), Infolists\Components\TextEntry::make('street_address') @@ -81,13 +84,46 @@ public static function getAddressInfolist(): array Infolists\Components\TextEntry::make('country') ->formatStateUsing(fn ($state) => "{$state->name} {$state->flag}"), Infolists\Components\Split::make([ - Infolists\Components\TextEntry::make('postal_code'), + Infolists\Components\TextEntry::make('postal_code') + ->badge() + ->color('warning'), Infolists\Components\TextEntry::make('city'), ])->columnSpanFull(), ]), ]; } + private static function hasCompanyAddress($types): bool + { + if (! is_array($types)) { + return false; + } + + return collect($types)->contains(function ($type) { + return ($type instanceof AddressType && $type === AddressType::COMPANY_ADDRESS) || + $type === AddressType::COMPANY_ADDRESS->value; + }); + } + + private static function formatAddressTypeLabels($state): string + { + if (is_array($state)) { + return collect($state)->map(function ($type) { + if (is_string($type)) { + return AddressType::from($type)->getLabel(); + } + + return $type->getLabel(); + })->join(', '); + } + + if (is_string($state)) { + return AddressType::from($state)->getLabel(); + } + + return $state->getLabel(); + } + public function table(Table $table): Table { return $table @@ -103,11 +139,19 @@ public function table(Table $table): Table }) ->searchable(['recipient', 'street_address', 'country_id']), Tables\Columns\TextColumn::make('type') - ->badge(), + ->badge() + ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)), ]) ->filters([ Tables\Filters\SelectFilter::make('type') - ->options(AddressType::class), + ->options(AddressType::class) + ->query(function (Builder $query, array $data): Builder { + if (filled($data['value'])) { + return $query->whereJsonContains('type', $data['value']); + } + + return $query; + }), Tables\Filters\TrashedFilter::make(), ]) ->actions([ @@ -115,6 +159,7 @@ public function table(Table $table): Table ->infolist(self::getAddressInfolist()), Tables\Actions\EditAction::make() ->form(self::getAddressForm()), + Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ BulkActionGroup::make([ diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php index 63fc04d..fb32bf3 100644 --- a/src/Models/User/Address.php +++ b/src/Models/User/Address.php @@ -3,7 +3,6 @@ namespace Eclipse\Core\Models\User; use Eclipse\Core\Database\Factories\AddressFactory; -use Eclipse\Core\Enums\AddressType; use Eclipse\Core\Models\User; use Eclipse\World\Models\Country; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -41,7 +40,7 @@ protected function casts(): array { return [ 'street_address' => 'array', - 'type' => AddressType::class, + 'type' => 'array', 'user_id' => 'integer', ]; } diff --git a/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php index 4f622a0..dfb1b6d 100644 --- a/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php +++ b/tests/Feature/Filament/RelationManagers/AddressesRelationManagerTest.php @@ -1,25 +1,151 @@ set_up_super_admin_and_tenant(); + $this->undoRepeaterFake = Repeater::fake(); }); -it('can render addresses relation manager on edit user page', function (): void { +afterEach(function () { + ($this->undoRepeaterFake)(); +}); + +function prepareFactoryDataForForm(): array +{ + $data = Address::factory()->definition(); + unset($data['user_id']); + + $data['street_address'] = collect($data['street_address']) + ->map(fn ($address) => ['street_address' => $address]) + ->toArray(); + + return $data; +} + +it('can render address relation manager', function (): void { livewire(AddressesRelationManager::class, [ 'ownerRecord' => $this->superAdmin, 'pageClass' => EditUser::class, ])->assertSuccessful(); }); -it('can render addresses relation manager on view user page', function (): void { +it('can create address', function (): void { + $data = prepareFactoryDataForForm(); + livewire(AddressesRelationManager::class, [ 'ownerRecord' => $this->superAdmin, - 'pageClass' => ViewUser::class, - ])->assertSuccessful(); + 'pageClass' => EditUser::class, + ]) + ->callTableAction('create', data: $data) + ->assertHasNoTableActionErrors(); + + expect($this->superAdmin->addresses()->count())->toBe(1); +}); + +it('can edit address', function (): void { + $address = Address::factory()->for($this->superAdmin)->create(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->callTableAction('edit', $address, data: ['recipient' => 'Updated Name']) + ->assertHasNoTableActionErrors(); + + expect($address->fresh()->recipient)->toBe('Updated Name'); +}); + +it('can delete address', function (): void { + $address = Address::factory()->for($this->superAdmin)->create(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->callTableAction('delete', $address) + ->assertHasNoTableActionErrors(); + + expect($this->superAdmin->fresh()->addresses)->toHaveCount(0); +}); + +it('can view address', function (): void { + $address = Address::factory()->for($this->superAdmin)->create(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->callTableAction('view', $address) + ->assertHasNoTableActionErrors(); +}); + +it('can bulk delete addresses', function (): void { + $addresses = Address::factory()->count(3)->for($this->superAdmin)->create(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->callTableBulkAction('delete', $addresses) + ->assertHasNoTableBulkActionErrors(); + + expect($this->superAdmin->fresh()->addresses)->toHaveCount(0); +}); + +it('can soft delete and restore address', function (): void { + $address = Address::factory()->for($this->superAdmin)->create(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->callTableBulkAction('delete', [$address]) + ->assertHasNoTableBulkActionErrors(); + + expect($address->fresh()->trashed())->toBeTrue(); + expect($this->superAdmin->addresses()->count())->toBe(0); // Active count + expect($this->superAdmin->addresses()->withTrashed()->count())->toBe(1); // Total count + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->filterTable('trashed', 'with') + ->callTableBulkAction('restore', [$address]) + ->assertHasNoTableBulkActionErrors(); + + expect($address->fresh()->trashed())->toBeFalse(); + expect($this->superAdmin->addresses()->count())->toBe(1); +}); + +it('can force delete address', function (): void { + $address = Address::factory()->for($this->superAdmin)->create(); + + $address->delete(); + + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->filterTable('trashed', 'only') + ->callTableBulkAction('forceDelete', [$address]) + ->assertHasNoTableBulkActionErrors(); + + expect(Address::withTrashed()->find($address->id))->toBeNull(); +}); + +it('can filter addresses by type', function (): void { + livewire(AddressesRelationManager::class, [ + 'ownerRecord' => $this->superAdmin, + 'pageClass' => EditUser::class, + ]) + ->filterTable('type', AddressType::DEFAULT_ADDRESS->value) + ->assertSuccessful(); }); From 6cc582beb632f8dfb1fa51a89bb4bee8cdfc957d Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Sat, 21 Jun 2025 05:19:14 +0545 Subject: [PATCH 04/10] feat: refactoring & adding address book enable/disable feature --- ..._16_150346_create_user_addresses_table.php | 2 +- ...5_05_08_075602_create_eclipse_settings.php | 1 + src/EclipseServiceProvider.php | 26 +++++++++---------- src/Filament/Pages/ManageEclipse.php | 2 ++ src/Filament/Resources/UserResource.php | 13 +++++++--- .../AddressesRelationManager.php | 7 +++-- src/Models/User/Address.php | 12 +++++++++ src/Settings/EclipseSettings.php | 2 ++ 8 files changed, 46 insertions(+), 19 deletions(-) diff --git a/database/migrations/2025_06_16_150346_create_user_addresses_table.php b/database/migrations/2025_06_16_150346_create_user_addresses_table.php index ef1a0bc..95cb9bc 100644 --- a/database/migrations/2025_06_16_150346_create_user_addresses_table.php +++ b/database/migrations/2025_06_16_150346_create_user_addresses_table.php @@ -21,7 +21,7 @@ public function up(): void $table->json('street_address'); $table->string('postal_code', 50); $table->string('city', 100); - $table->json('type'); + $table->json('type')->nullable(); $table->string('country_id', 2); $table->foreignId('user_id') ->constrained('users') diff --git a/database/settings/2025_05_08_075602_create_eclipse_settings.php b/database/settings/2025_05_08_075602_create_eclipse_settings.php index 5cc9fa6..630318a 100644 --- a/database/settings/2025_05_08_075602_create_eclipse_settings.php +++ b/database/settings/2025_05_08_075602_create_eclipse_settings.php @@ -7,5 +7,6 @@ public function up(): void { $this->migrator->add('eclipse.email_verification', false); + $this->migrator->add('eclipse.address_book', false); } }; diff --git a/src/EclipseServiceProvider.php b/src/EclipseServiceProvider.php index 1570b56..6ae9025 100644 --- a/src/EclipseServiceProvider.php +++ b/src/EclipseServiceProvider.php @@ -138,18 +138,18 @@ public function boot(): void }); // Register health checks - Health::checks([ - OptimizedAppCheck::new(), - DebugModeCheck::new(), - EnvironmentCheck::new(), - UsedDiskSpaceCheck::new() - ->warnWhenUsedSpaceIsAbovePercentage(70) - ->failWhenUsedSpaceIsAbovePercentage(90), - CacheCheck::new(), - HorizonCheck::new(), - RedisCheck::new(), - ScheduleCheck::new(), - SecurityAdvisoriesCheck::new(), - ]); + // Health::checks([ + // OptimizedAppCheck::new(), + // DebugModeCheck::new(), + // EnvironmentCheck::new(), + // UsedDiskSpaceCheck::new() + // ->warnWhenUsedSpaceIsAbovePercentage(70) + // ->failWhenUsedSpaceIsAbovePercentage(90), + // CacheCheck::new(), + // HorizonCheck::new(), + // RedisCheck::new(), + // ScheduleCheck::new(), + // SecurityAdvisoriesCheck::new(), + // ]); } } diff --git a/src/Filament/Pages/ManageEclipse.php b/src/Filament/Pages/ManageEclipse.php index f41e8fb..da5b976 100644 --- a/src/Filament/Pages/ManageEclipse.php +++ b/src/Filament/Pages/ManageEclipse.php @@ -22,6 +22,8 @@ public function form(Form $form): Form ->schema([ Forms\Components\Toggle::make('email_verification') ->label('Enable user email verification'), + Forms\Components\Toggle::make('address_book') + ->label('Enable address book'), ]); } diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php index 94f686e..692c127 100644 --- a/src/Filament/Resources/UserResource.php +++ b/src/Filament/Resources/UserResource.php @@ -7,6 +7,7 @@ use Eclipse\Core\Filament\Resources; use Eclipse\Core\Filament\Resources\UserResource\RelationManagers\AddressesRelationManager; use Eclipse\Core\Models\User; +use Eclipse\Core\Settings\EclipseSettings; use Filament\Forms; use Filament\Forms\Form; use Filament\Infolists\Components\Group; @@ -240,9 +241,15 @@ public static function infolist(Infolist $infolist): Infolist public static function getRelations(): array { - return [ - AddressesRelationManager::class, - ]; + $isAddressBookEnabled = app(EclipseSettings::class)->address_book ?? false; + + $relationManagers = []; + + if ($isAddressBookEnabled) { + $relationManagers[] = AddressesRelationManager::class; + } + + return $relationManagers; } public static function getPages(): array diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php index 5101644..e7319ee 100644 --- a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -133,11 +133,14 @@ public function table(Table $table): Table ->weight(FontWeight::Bold) ->description(function ($record) { $streetAddresses = implode('
', $record->street_address); + $companyName = $record->company_name; $countryInfo = "{$record->country->flag} {$record->country->name}"; - return new HtmlString("{$streetAddresses}
{$countryInfo}"); + return new HtmlString("{$companyName}
{$streetAddresses}
{$countryInfo}"); }) - ->searchable(['recipient', 'street_address', 'country_id']), + ->searchable(['recipient', 'company_name', 'street_address', 'country_id']), + Tables\Columns\TextColumn::make('company_vat_id') + ->label('Company VAT ID'), Tables\Columns\TextColumn::make('type') ->badge() ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)), diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php index fb32bf3..6f32083 100644 --- a/src/Models/User/Address.php +++ b/src/Models/User/Address.php @@ -3,6 +3,7 @@ namespace Eclipse\Core\Models\User; use Eclipse\Core\Database\Factories\AddressFactory; +use Eclipse\Core\Enums\AddressType; use Eclipse\Core\Models\User; use Eclipse\World\Models\Country; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -59,4 +60,15 @@ protected static function newFactory(): AddressFactory { return AddressFactory::new(); } + + protected static function booted() + { + static::saving(function (self $address) { + $hasDefaultAddress = self::where('user_id', $address->user_id)->whereJsonContains('type', AddressType::DEFAULT_ADDRESS->value)->exists(); + + if ($hasDefaultAddress) { + $address->type = array_diff($address->type, [AddressType::DEFAULT_ADDRESS->value]); + } + }); + } } diff --git a/src/Settings/EclipseSettings.php b/src/Settings/EclipseSettings.php index 2a65acd..6463a68 100644 --- a/src/Settings/EclipseSettings.php +++ b/src/Settings/EclipseSettings.php @@ -8,6 +8,8 @@ class EclipseSettings extends Settings { public bool $email_verification = false; + public bool $address_book = false; + public static function group(): string { return 'eclipse'; From daa5276731381a39c18568759fcc929c0592e985 Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Sat, 21 Jun 2025 05:30:18 +0545 Subject: [PATCH 05/10] fix: bug fixes --- src/EclipseServiceProvider.php | 26 +++++++++---------- .../AddressesRelationManager.php | 16 +++++++++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/EclipseServiceProvider.php b/src/EclipseServiceProvider.php index 6ae9025..1570b56 100644 --- a/src/EclipseServiceProvider.php +++ b/src/EclipseServiceProvider.php @@ -138,18 +138,18 @@ public function boot(): void }); // Register health checks - // Health::checks([ - // OptimizedAppCheck::new(), - // DebugModeCheck::new(), - // EnvironmentCheck::new(), - // UsedDiskSpaceCheck::new() - // ->warnWhenUsedSpaceIsAbovePercentage(70) - // ->failWhenUsedSpaceIsAbovePercentage(90), - // CacheCheck::new(), - // HorizonCheck::new(), - // RedisCheck::new(), - // ScheduleCheck::new(), - // SecurityAdvisoriesCheck::new(), - // ]); + Health::checks([ + OptimizedAppCheck::new(), + DebugModeCheck::new(), + EnvironmentCheck::new(), + UsedDiskSpaceCheck::new() + ->warnWhenUsedSpaceIsAbovePercentage(70) + ->failWhenUsedSpaceIsAbovePercentage(90), + CacheCheck::new(), + HorizonCheck::new(), + RedisCheck::new(), + ScheduleCheck::new(), + SecurityAdvisoriesCheck::new(), + ]); } } diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php index e7319ee..bbbb149 100644 --- a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -132,17 +132,25 @@ public function table(Table $table): Table Tables\Columns\TextColumn::make('recipient') ->weight(FontWeight::Bold) ->description(function ($record) { - $streetAddresses = implode('
', $record->street_address); - $companyName = $record->company_name; - $countryInfo = "{$record->country->flag} {$record->country->name}"; + $recipient = []; - return new HtmlString("{$companyName}
{$streetAddresses}
{$countryInfo}"); + if ($record->company_name) { + $recipient[] = $record->company_name; + } + + $recipient[] = implode('
', $record->street_address); + + $recipient[] = "{$record->country->flag} {$record->country->name}"; + + return new HtmlString(implode('
', $recipient)); }) ->searchable(['recipient', 'company_name', 'street_address', 'country_id']), Tables\Columns\TextColumn::make('company_vat_id') + ->placeholder('-') ->label('Company VAT ID'), Tables\Columns\TextColumn::make('type') ->badge() + ->placeholder('-') ->formatStateUsing(fn ($state) => self::formatAddressTypeLabels($state)), ]) ->filters([ From 686f4ba0a33f85e7197120e319328098550d1a8d Mon Sep 17 00:00:00 2001 From: thapacodes4u Date: Sat, 21 Jun 2025 05:30:56 +0545 Subject: [PATCH 06/10] fix: bug fixes --- .../UserResource/RelationManagers/AddressesRelationManager.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php index bbbb149..e25f3b7 100644 --- a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -26,7 +26,6 @@ public static function getAddressForm(): array Forms\Components\CheckboxList::make('type') ->live() ->default([AddressType::DEFAULT_ADDRESS->value]) - ->required() ->options(AddressType::class) ->columns(2), Forms\Components\TextInput::make('recipient') From 0debef105a46e57628b9e8728001356b76a58e86 Mon Sep 17 00:00:00 2001 From: ankitcodes4u Date: Mon, 23 Jun 2025 09:13:32 +0000 Subject: [PATCH 07/10] fix: failing test --- src/Filament/Resources/UserResource.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php index 217b651..42d040e 100644 --- a/src/Filament/Resources/UserResource.php +++ b/src/Filament/Resources/UserResource.php @@ -27,6 +27,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use pxlrbt\FilamentExcel\Actions\Tables\ExportBulkAction; use STS\FilamentImpersonate\Tables\Actions\Impersonate; @@ -253,6 +254,11 @@ public static function infolist(Infolist $infolist): Infolist public static function getRelations(): array { + // Temporary Fix: getRelations is being accessed before migrations is running in test + if (! Schema::hasTable('settings')) { + return []; + } + $isAddressBookEnabled = app(EclipseSettings::class)->address_book ?? false; $relationManagers = []; From 7b0a2fdc8f7f814d97ca14ddeae4268917feea5a Mon Sep 17 00:00:00 2001 From: ankitcodes4u Date: Mon, 23 Jun 2025 20:34:08 +0000 Subject: [PATCH 08/10] fix: error related to relation manager --- src/Filament/Resources/UserResource.php | 19 +++---------------- .../AddressesRelationManager.php | 9 +++++++++ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Filament/Resources/UserResource.php b/src/Filament/Resources/UserResource.php index 42d040e..f2f5125 100644 --- a/src/Filament/Resources/UserResource.php +++ b/src/Filament/Resources/UserResource.php @@ -7,7 +7,6 @@ use Eclipse\Core\Filament\Resources; use Eclipse\Core\Filament\Resources\UserResource\RelationManagers\AddressesRelationManager; use Eclipse\Core\Models\User; -use Eclipse\Core\Settings\EclipseSettings; use Filament\Forms; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Form; @@ -27,7 +26,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use pxlrbt\FilamentExcel\Actions\Tables\ExportBulkAction; use STS\FilamentImpersonate\Tables\Actions\Impersonate; @@ -254,20 +252,9 @@ public static function infolist(Infolist $infolist): Infolist public static function getRelations(): array { - // Temporary Fix: getRelations is being accessed before migrations is running in test - if (! Schema::hasTable('settings')) { - return []; - } - - $isAddressBookEnabled = app(EclipseSettings::class)->address_book ?? false; - - $relationManagers = []; - - if ($isAddressBookEnabled) { - $relationManagers[] = AddressesRelationManager::class; - } - - return $relationManagers; + return [ + AddressesRelationManager::class, + ]; } public static function getPages(): array diff --git a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php index e25f3b7..b21748d 100644 --- a/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php +++ b/src/Filament/Resources/UserResource/RelationManagers/AddressesRelationManager.php @@ -5,6 +5,7 @@ namespace Eclipse\Core\Filament\Resources\UserResource\RelationManagers; use Eclipse\Core\Enums\AddressType; +use Eclipse\Core\Settings\EclipseSettings; use Filament\Forms; use Filament\Forms\Get; use Filament\Infolists; @@ -14,12 +15,20 @@ use Filament\Tables\Actions\BulkActionGroup; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\HtmlString; class AddressesRelationManager extends RelationManager { protected static string $relationship = 'addresses'; + public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool + { + $isAddressBookEnabled = app(EclipseSettings::class)->address_book ?? false; + + return $isAddressBookEnabled; + } + public static function getAddressForm(): array { return [ From d9838a3d46c2896b8bcbc41a2565c8d344f1446b Mon Sep 17 00:00:00 2001 From: ankitcodes4u Date: Wed, 25 Jun 2025 05:37:10 +0545 Subject: [PATCH 09/10] fix: refactoring address observer --- src/Models/User/Address.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php index 6f32083..eb24000 100644 --- a/src/Models/User/Address.php +++ b/src/Models/User/Address.php @@ -63,12 +63,25 @@ protected static function newFactory(): AddressFactory protected static function booted() { - static::saving(function (self $address) { + static::saving(function (self $address): void { $hasDefaultAddress = self::where('user_id', $address->user_id)->whereJsonContains('type', AddressType::DEFAULT_ADDRESS->value)->exists(); if ($hasDefaultAddress) { $address->type = array_diff($address->type, [AddressType::DEFAULT_ADDRESS->value]); } }); + + static::deleted(function (self $address): void { + if (in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) { + $oldestAddress = self::where('user_id', $address->user_id) + ->orderBy('created_at', 'asc') + ->first(); + + if ($oldestAddress) { + $oldestAddress->type = array_merge($oldestAddress->type, [AddressType::DEFAULT_ADDRESS->value]); + $oldestAddress->saveQuietly(); + } + } + }); } } From 495461e4e43d5a4f9cc064386ce0c6186f394ab8 Mon Sep 17 00:00:00 2001 From: ankitcodes4u Date: Tue, 1 Jul 2025 06:16:18 +0545 Subject: [PATCH 10/10] fix: default address assigning and removal --- src/Models/User/Address.php | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Models/User/Address.php b/src/Models/User/Address.php index eb24000..179a9aa 100644 --- a/src/Models/User/Address.php +++ b/src/Models/User/Address.php @@ -61,27 +61,36 @@ protected static function newFactory(): AddressFactory return AddressFactory::new(); } - protected static function booted() + protected static function booted(): void { static::saving(function (self $address): void { - $hasDefaultAddress = self::where('user_id', $address->user_id)->whereJsonContains('type', AddressType::DEFAULT_ADDRESS->value)->exists(); - - if ($hasDefaultAddress) { - $address->type = array_diff($address->type, [AddressType::DEFAULT_ADDRESS->value]); + if (! in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) { + return; } + + self::where('user_id', $address->user_id) + ->where('id', '!=', $address->id ?? 0) + ->whereJsonContains('type', AddressType::DEFAULT_ADDRESS->value) + ->get(['id', 'type']) + ->each(function ($existingAddress) { + $existingAddress->timestamps = false; + $existingAddress->updateQuietly([ + 'type' => array_values(array_diff($existingAddress->type, [AddressType::DEFAULT_ADDRESS->value])), + ]); + }); }); static::deleted(function (self $address): void { - if (in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) { - $oldestAddress = self::where('user_id', $address->user_id) - ->orderBy('created_at', 'asc') - ->first(); - - if ($oldestAddress) { - $oldestAddress->type = array_merge($oldestAddress->type, [AddressType::DEFAULT_ADDRESS->value]); - $oldestAddress->saveQuietly(); - } + if (! in_array(AddressType::DEFAULT_ADDRESS->value, $address->type)) { + return; } + + self::where('user_id', $address->user_id) + ->orderBy('created_at', 'asc') + ->first(['id', 'type']) + ?->updateQuietly([ + 'type' => array_merge($address->type ?? [], [AddressType::DEFAULT_ADDRESS->value]), + ]); }); } }