From 04bf03fc2eaccfe795af1700e198a82caa3a14f3 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Wed, 29 Jul 2026 14:09:47 -0300 Subject: [PATCH] feat(website): add website type and extras models with migration and tests --- app/Domains/Tenant/Models/Tenant.php | 17 +++ app/Domains/Tenant/Models/WebsiteExtra.php | 47 +++++++ app/Domains/Tenant/Models/WebsiteType.php | 35 +++++ .../Tenant/Models/WebsiteTypeExtra.php | 51 +++++++ ...29_000200_create_website_extras_tables.php | 71 ++++++++++ tests/Feature/Tenant/WebsiteExtrasTest.php | 124 ++++++++++++++++++ 6 files changed, 345 insertions(+) create mode 100644 app/Domains/Tenant/Models/WebsiteExtra.php create mode 100644 app/Domains/Tenant/Models/WebsiteType.php create mode 100644 app/Domains/Tenant/Models/WebsiteTypeExtra.php create mode 100644 database/migrations/2026_07_29_000200_create_website_extras_tables.php create mode 100644 tests/Feature/Tenant/WebsiteExtrasTest.php diff --git a/app/Domains/Tenant/Models/Tenant.php b/app/Domains/Tenant/Models/Tenant.php index 0b183ba..7d6ebb4 100644 --- a/app/Domains/Tenant/Models/Tenant.php +++ b/app/Domains/Tenant/Models/Tenant.php @@ -30,6 +30,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; 'footer_logo_id', 'hero_config', 'event_config', + 'website_type_code', 'search_product_layout', 'search_group_layout', 'search_items_per_page', @@ -89,6 +90,14 @@ class Tenant extends Model return $this->belongsTo(Attachment::class, 'hero_bg_image_id'); } + /** + * @return BelongsTo + */ + public function websiteType(): BelongsTo + { + return $this->belongsTo(WebsiteType::class, 'website_type_code', 'codigo'); + } + /** * @return BelongsToMany */ @@ -136,6 +145,14 @@ class Tenant extends Model ->orderByPivot('orden'); } + /** + * @return HasMany + */ + public function websiteExtras(): HasMany + { + return $this->hasMany(WebsiteExtra::class, 'website_code', 'codigo'); + } + public function menues(): BelongsToMany { return $this->belongsToMany( diff --git a/app/Domains/Tenant/Models/WebsiteExtra.php b/app/Domains/Tenant/Models/WebsiteExtra.php new file mode 100644 index 0000000..5cd2cbe --- /dev/null +++ b/app/Domains/Tenant/Models/WebsiteExtra.php @@ -0,0 +1,47 @@ + + */ + protected function casts(): array + { + return [ + 'website_type_extra_id' => 'integer', + 'config' => 'array', + ]; + } + + /** + * @return BelongsTo + */ + public function website(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'website_code', 'codigo'); + } + + /** + * @return BelongsTo + */ + public function websiteTypeExtra(): BelongsTo + { + return $this->belongsTo(WebsiteTypeExtra::class, 'website_type_extra_id'); + } +} diff --git a/app/Domains/Tenant/Models/WebsiteType.php b/app/Domains/Tenant/Models/WebsiteType.php new file mode 100644 index 0000000..77d8e70 --- /dev/null +++ b/app/Domains/Tenant/Models/WebsiteType.php @@ -0,0 +1,35 @@ + + */ + public function extras(): HasMany + { + return $this->hasMany(WebsiteTypeExtra::class, 'website_type'); + } + + /** + * @return HasMany + */ + public function tenants(): HasMany + { + return $this->hasMany(Tenant::class, 'website_type_code', 'codigo'); + } +} diff --git a/app/Domains/Tenant/Models/WebsiteTypeExtra.php b/app/Domains/Tenant/Models/WebsiteTypeExtra.php new file mode 100644 index 0000000..f3b5ffb --- /dev/null +++ b/app/Domains/Tenant/Models/WebsiteTypeExtra.php @@ -0,0 +1,51 @@ + + */ + protected function casts(): array + { + return [ + 'website_type' => 'integer', + 'is_required' => 'boolean', + 'config_schema' => 'array', + ]; + } + + /** + * @return BelongsTo + */ + public function websiteType(): BelongsTo + { + return $this->belongsTo(WebsiteType::class, 'website_type'); + } + + /** + * @return HasMany + */ + public function websiteExtras(): HasMany + { + return $this->hasMany(WebsiteExtra::class, 'website_type_extra_id'); + } +} diff --git a/database/migrations/2026_07_29_000200_create_website_extras_tables.php b/database/migrations/2026_07_29_000200_create_website_extras_tables.php new file mode 100644 index 0000000..ec02cc0 --- /dev/null +++ b/database/migrations/2026_07_29_000200_create_website_extras_tables.php @@ -0,0 +1,71 @@ +id(); + $table->string('codigo')->unique(); + $table->string('nombre'); + $table->timestamps(); + }); + + Schema::table('tenants', function (Blueprint $table): void { + $table->string('website_type_code')->nullable(); + + $table->foreign('website_type_code') + ->references('codigo') + ->on('website_type') + ->cascadeOnUpdate() + ->nullOnDelete(); + }); + + Schema::create('website_type_extras', function (Blueprint $table): void { + $table->id(); + $table->foreignId('website_type') + ->constrained('website_type') + ->cascadeOnUpdate() + ->cascadeOnDelete(); + $table->string('nombre'); + $table->text('descripcion'); + $table->boolean('is_required')->default(false); + $table->json('config_schema'); + $table->timestamps(); + }); + + Schema::create('websites_extras', function (Blueprint $table): void { + $table->id(); + $table->string('website_code'); + $table->foreignId('website_type_extra_id') + ->constrained('website_type_extras') + ->cascadeOnUpdate() + ->cascadeOnDelete(); + $table->json('config'); + $table->timestamps(); + + $table->foreign('website_code') + ->references('codigo') + ->on('tenants') + ->cascadeOnUpdate() + ->cascadeOnDelete(); + }); + } + + public function down(): void + { + Schema::dropIfExists('websites_extras'); + Schema::dropIfExists('website_type_extras'); + + Schema::table('tenants', function (Blueprint $table): void { + $table->dropForeign(['website_type_code']); + $table->dropColumn('website_type_code'); + }); + + Schema::dropIfExists('website_type'); + } +}; diff --git a/tests/Feature/Tenant/WebsiteExtrasTest.php b/tests/Feature/Tenant/WebsiteExtrasTest.php new file mode 100644 index 0000000..086fce3 --- /dev/null +++ b/tests/Feature/Tenant/WebsiteExtrasTest.php @@ -0,0 +1,124 @@ +assertTrue(Schema::hasColumn('tenants', 'website_type_code')); + + $this->assertEqualsCanonicalizing([ + 'id', + 'codigo', + 'nombre', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('website_type')); + + $this->assertEqualsCanonicalizing([ + 'id', + 'website_type', + 'nombre', + 'descripcion', + 'is_required', + 'config_schema', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('website_type_extras')); + + $this->assertEqualsCanonicalizing([ + 'id', + 'website_code', + 'website_type_extra_id', + 'config', + 'created_at', + 'updated_at', + ], Schema::getColumnListing('websites_extras')); + } + + public function test_models_map_the_diagram_relations_and_casts(): void + { + $type = WebsiteType::query()->create([ + 'codigo' => 'store', + 'nombre' => 'Tienda', + ]); + $typeExtra = $type->extras()->create([ + 'nombre' => 'WhatsApp', + 'descripcion' => 'Configuracion del canal de WhatsApp', + 'is_required' => true, + 'config_schema' => [ + 'type' => 'object', + 'required' => ['phone'], + ], + ]); + $tenant = $this->createTenant(); + $tenant->websiteType()->associate($type)->save(); + $websiteExtra = $tenant->websiteExtras()->create([ + 'website_type_extra_id' => $typeExtra->id, + 'config' => ['phone' => '+5491112345678'], + ]); + + $this->assertTrue($type->extras()->firstOrFail()->is($typeExtra)); + $this->assertTrue($type->tenants()->firstOrFail()->is($tenant)); + $this->assertTrue($tenant->websiteType()->firstOrFail()->is($type)); + $this->assertSame($type->codigo, $tenant->website_type_code); + $this->assertTrue($typeExtra->websiteType()->firstOrFail()->is($type)); + $this->assertTrue($typeExtra->websiteExtras()->firstOrFail()->is($websiteExtra)); + $this->assertTrue($websiteExtra->website()->firstOrFail()->is($tenant)); + $this->assertTrue($websiteExtra->websiteTypeExtra()->firstOrFail()->is($typeExtra)); + $this->assertTrue($typeExtra->is_required); + $this->assertSame([ + 'type' => 'object', + 'required' => ['phone'], + ], $typeExtra->config_schema); + $this->assertSame(['phone' => '+5491112345678'], $websiteExtra->config); + } + + public function test_deleting_a_type_cascades_its_definitions_and_website_values(): void + { + $type = WebsiteType::query()->create([ + 'codigo' => 'store', + 'nombre' => 'Tienda', + ]); + $typeExtra = $type->extras()->create([ + 'nombre' => 'WhatsApp', + 'descripcion' => 'Configuracion del canal de WhatsApp', + 'config_schema' => ['type' => 'object'], + ]); + $websiteExtra = $this->createTenant()->websiteExtras()->create([ + 'website_type_extra_id' => $typeExtra->id, + 'config' => ['phone' => '+5491112345678'], + ]); + + $type->delete(); + + $this->assertDatabaseMissing('website_type_extras', ['id' => $typeExtra->id]); + $this->assertDatabaseMissing('websites_extras', ['id' => $websiteExtra->id]); + } + + private function createTenant(): Tenant + { + return Tenant::query()->create([ + 'codigo' => 'acme', + 'nombre' => 'Acme', + 'dominio' => 'acme.com', + 'primary_color' => '#111111', + 'secondary_color' => '#222222', + 'danger_color' => '#333333', + 'success_color' => '#444444', + 'header_bg_color' => '#ffffff', + 'footer_bg_color' => '#ffffff', + 'header_logo_id' => 1, + 'footer_logo_id' => 2, + ]); + } +}