diff --git a/app/Domains/Core/Tenant/Services/AdminWebsiteTypeService.php b/app/Domains/Core/Tenant/Services/AdminWebsiteTypeService.php index 5e85bfe..38fa936 100644 --- a/app/Domains/Core/Tenant/Services/AdminWebsiteTypeService.php +++ b/app/Domains/Core/Tenant/Services/AdminWebsiteTypeService.php @@ -2,9 +2,10 @@ namespace App\Domains\Core\Tenant\Services; +use App\Domains\Core\Tenant\Models\AdminWebsiteType; +use App\Domains\Core\Tenant\Models\Tenant; use App\Shared\Attachable\Models\Attachment; use App\Shared\Attachable\Services\AttachmentService; -use App\Domains\Core\Tenant\Models\AdminWebsiteType; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; @@ -96,12 +97,12 @@ class AdminWebsiteTypeService { return Tenant::query()->where('favicon_id', $attachment->id)->exists() || AdminWebsiteType::query() - ->where(function ($query) use ($attachment): void { - $query - ->where('site_logo', $attachment->id) - ->orWhere('footer_logo', $attachment->id) - ->orWhere('favicon_id', $attachment->id); - }) - ->exists(); + ->where(function ($query) use ($attachment): void { + $query + ->where('site_logo', $attachment->id) + ->orWhere('footer_logo', $attachment->id) + ->orWhere('favicon_id', $attachment->id); + }) + ->exists(); } } diff --git a/app/Domains/Core/Tenant/Services/TenantProvisioningService.php b/app/Domains/Core/Tenant/Services/TenantProvisioningService.php new file mode 100644 index 0000000..370cc17 --- /dev/null +++ b/app/Domains/Core/Tenant/Services/TenantProvisioningService.php @@ -0,0 +1,125 @@ + $data */ + public function create(array $data): Tenant + { + return DB::transaction(function () use ($data): Tenant { + $images = [ + 'header_logo' => $data['header_logo'] ?? null, + 'footer_logo' => $data['footer_logo'] ?? null, + 'favicon' => $data['favicon'] ?? null, + 'header_bg_image' => $data['header_bg_image'] ?? null, + 'footer_bg_image' => $data['footer_bg_image'] ?? null, + ]; + $socialMedia = $data['social_media'] ?? []; + $extras = $data['extras'] ?? []; + + unset( + $data['header_logo'], + $data['footer_logo'], + $data['favicon'], + $data['header_bg_image'], + $data['footer_bg_image'], + $data['social_media'], + $data['extras'], + ); + + foreach ($images as $key => $image) { + $data[$key.'_id'] = $this->storeTenantImage($image); + } + + /** @var Tenant $tenant */ + $tenant = Tenant::query()->create($data); + $this->syncSocialMedia($tenant, $socialMedia); + + foreach ($extras as $code => $config) { + $this->websiteExtraService->updateForTenant($tenant, $code, $config); + } + + return $tenant; + }); + } + + /** @param array $data */ + public function update(Tenant $tenant, array $data): Tenant + { + return DB::transaction(function () use ($tenant, $data): Tenant { + $imageKeys = [ + 'header_logo', + 'footer_logo', + 'favicon', + 'header_bg_image', + 'footer_bg_image', + ]; + $hasSocialMedia = array_key_exists('social_media', $data); + $socialMedia = $data['social_media'] ?? []; + unset($data['social_media']); + + foreach ($imageKeys as $key) { + if (! array_key_exists($key, $data)) { + continue; + } + + $data[$key.'_id'] = $this->storeTenantImage($data[$key]); + unset($data[$key]); + } + + $tenant->update($data); + + if ($hasSocialMedia) { + $this->syncSocialMedia($tenant, $socialMedia); + } + + return $tenant->refresh(); + }); + } + + /** @param array $socialMedia */ + private function syncSocialMedia(Tenant $tenant, array $socialMedia): void + { + $associations = []; + + foreach (array_values($socialMedia) as $index => $item) { + $associations[$item['code']] = [ + 'url' => $item['url'], + 'orden' => $item['orden'] ?? $index, + ]; + } + + $tenant->socialMedia()->sync($associations); + $tenant->unsetRelation('socialMedia'); + } + + private function storeTenantImage(mixed $image): ?int + { + if (! $image) { + return null; + } + + $attachment = is_string($image) && Str::isUuid($image) + ? Attachment::query()->where('key', $image)->first() + : $this->attachmentService->store($image, 'tenants'); + + return $attachment?->id; + } +} diff --git a/database/migrations/2026_09_21_050000_provision_onticket_tenant.php b/database/migrations/2026_09_21_050000_provision_onticket_tenant.php new file mode 100644 index 0000000..af38fb0 --- /dev/null +++ b/database/migrations/2026_09_21_050000_provision_onticket_tenant.php @@ -0,0 +1,62 @@ +environment('testing')) { + Storage::fake('s3'); + } + + app(WebsiteTypeSeeder::class)->run(); + app(OnTicketTenantSeeder::class)->run(); + + $now = now(); + + DB::table('menues') + ->whereNotIn('code', self::EXCLUDED_MENU_CODES) + ->pluck('code') + ->each(function (string $menuCode) use ($now): void { + DB::table('tenants_menues')->updateOrInsert( + [ + 'tenant_code' => self::TENANT_CODE, + 'menu_code' => $menuCode, + ], + [ + 'static_content' => null, + 'created_at' => $now, + 'updated_at' => $now, + ], + ); + }); + } + + public function down(): void + { + // OnTicket is reference data. Never remove a possibly active tenant on rollback. + } +}; diff --git a/database/seeders/DesfilePuraTendenciaSeeder.php b/database/seeders/DesfilePuraTendenciaSeeder.php index a16c397..d3d3fc9 100644 --- a/database/seeders/DesfilePuraTendenciaSeeder.php +++ b/database/seeders/DesfilePuraTendenciaSeeder.php @@ -13,7 +13,7 @@ use App\Domains\Commerce\Catalog\Models\FeaturedGroup; use App\Domains\Commerce\Catalog\Services\CatalogService; use App\Domains\Core\Client\Models\Client; use App\Domains\Core\Tenant\Models\Tenant; -use App\Domains\Core\Tenant\Services\TenantService; +use App\Domains\Core\Tenant\Services\TenantProvisioningService; use App\Domains\Ticketing\Desfile\Services\InvitationPurchaseProvisioner; use Illuminate\Database\Seeder; use Illuminate\Http\UploadedFile; @@ -27,7 +27,7 @@ class DesfilePuraTendenciaSeeder extends Seeder private const TENANT_CODE = 'desfile_pura_tendencia'; public function __construct( - private readonly TenantService $tenantService, + private readonly TenantProvisioningService $tenantService, private readonly CatalogService $catalogService, private readonly InvitationPurchaseProvisioner $invitationPurchaseProvisioner, ) {} diff --git a/database/seeders/OnTicketTenantSeeder.php b/database/seeders/OnTicketTenantSeeder.php new file mode 100644 index 0000000..d0c324f --- /dev/null +++ b/database/seeders/OnTicketTenantSeeder.php @@ -0,0 +1,101 @@ +firstOrCreate( + ['code' => 'onticket'], + ['name' => 'OnTicket'], + ); + $type = AdminWebsiteType::query()->where('codigo', 'onticket')->firstOrFail(); + $tenant = Tenant::query()->where('codigo', 'onticket')->first(); + + if ($tenant === null) { + $tenant = $this->tenantProvisioningService->create([ + 'client_id' => $client->id, + 'codigo' => 'onticket', + 'nombre' => $type->nombre, + 'dominio' => $type->dominio, + 'site_title' => $type->site_title, + 'primary_color' => $type->primary_color, + 'secondary_color' => $type->secondary_color, + 'danger_color' => $type->danger_color, + 'success_color' => $type->success_color, + 'header_bg_color' => $type->surface_color, + 'footer_bg_color' => $type->primary_color, + 'display_categories' => true, + 'display_seach_bar' => true, + 'admin_website_type_code' => $type->codigo, + 'storefront_website_type_code' => 'onticket_multi_event', + 'header_logo' => $this->uploadedImage('onticket_logo.png'), + 'footer_logo' => $this->uploadedImage('onticket_footer_logo.png'), + 'favicon' => $this->uploadedImage('onticket_favicon.svg'), + 'header_bg_image' => $this->uploadedImage('onticket_header_background.png'), + ]); + } else { + $tenant->update([ + 'client_id' => $client->id, + 'storefront_website_type_code' => 'onticket_multi_event', + 'display_categories' => true, + 'display_seach_bar' => true, + ]); + + if ($tenant->header_bg_image_id === null) { + $tenant = $this->tenantProvisioningService->update($tenant, [ + 'header_bg_image' => $this->uploadedImage('onticket_header_background.png'), + ]); + } + } + + foreach (['Música', 'Teatro', 'Deportes', 'Infantiles', 'Otros'] as $name) { + $tenant->eventCategories()->firstOrCreate(['nombre' => $name]); + } + + if (! $tenant->websiteExtras()->whereHas( + 'websiteTypeExtra', + fn ($query) => $query->where('codigo', 'immersiveHero') + )->exists()) { + $this->websiteExtraService->updateForTenant($tenant, 'immersiveHero', [ + 'eyebrow' => 'Encendé tu', + 'title' => 'experiencia', + 'description' => 'Reservá tu entrada y formá parte', + ]); + } + + $this->carouselSeeder->run(); + } + + private function uploadedImage(string $filename): UploadedFile + { + $path = public_path("images/tennants/onticket/{$filename}"); + + if (! is_file($path)) { + throw new RuntimeException("Image not found at path: {$path}"); + } + + $mimeType = match (strtolower(pathinfo($filename, PATHINFO_EXTENSION))) { + 'svg' => 'image/svg+xml', + default => 'image/png', + }; + + return new UploadedFile($path, $filename, $mimeType, null, true); + } +} diff --git a/database/seeders/TenantSeeder.php b/database/seeders/TenantSeeder.php index 059e7f3..43c4dbe 100644 --- a/database/seeders/TenantSeeder.php +++ b/database/seeders/TenantSeeder.php @@ -3,10 +3,8 @@ namespace Database\Seeders; use App\Domains\Core\Client\Models\Client; -use App\Domains\Core\Tenant\Models\AdminWebsiteType; use App\Domains\Core\Tenant\Models\Tenant; -use App\Domains\Core\Tenant\Services\TenantService; -use App\Domains\Core\Tenant\Services\WebsiteExtraService; +use App\Domains\Core\Tenant\Services\TenantProvisioningService; use App\Shared\Attachable\Models\Attachment; use App\Shared\Attachable\Services\AttachmentService; use Illuminate\Database\Seeder; @@ -16,8 +14,6 @@ use Throwable; class TenantSeeder extends Seeder { - private const ONTICKET_CLIENT_CODE = 'onticket'; - private const PYME_RURAL_CLIENT_CODE = 'pyme_rural'; private const SONDER_CLIENT_CODE = 'sonder'; @@ -46,8 +42,7 @@ class TenantSeeder extends Seeder ]; public function __construct( - protected TenantService $tenantService, - protected WebsiteExtraService $websiteExtraService, + protected TenantProvisioningService $tenantService, ) {} public function run(): void @@ -57,89 +52,12 @@ class TenantSeeder extends Seeder ['name' => 'Sonder'], ); - $onTicketClient = Client::query()->firstOrCreate( - ['code' => self::ONTICKET_CLIENT_CODE], - ['name' => 'OnTicket'], - ); - $pymeRuralClient = Client::query()->updateOrCreate( ['code' => self::PYME_RURAL_CLIENT_CODE], ['name' => 'Pyme Rural'], ); - $onTicketType = AdminWebsiteType::query()->where('codigo', 'onticket')->firstOrFail(); - - $onTicketTenant = Tenant::query()->where('codigo', 'onticket')->first(); - - if ($onTicketTenant === null) { - $this->tenantService->create([ - 'client_id' => $onTicketClient->id, - 'codigo' => 'onticket', - 'nombre' => $onTicketType->nombre, - 'dominio' => $onTicketType->dominio, - 'site_title' => $onTicketType->site_title, - 'primary_color' => $onTicketType->primary_color, - 'secondary_color' => $onTicketType->secondary_color, - 'danger_color' => $onTicketType->danger_color, - 'success_color' => $onTicketType->success_color, - 'header_bg_color' => $onTicketType->surface_color, - 'footer_bg_color' => $onTicketType->primary_color, - 'display_categories' => true, - 'display_seach_bar' => true, - 'admin_website_type_code' => $onTicketType->codigo, - 'storefront_website_type_code' => 'onticket_multi_event', - 'header_logo' => $this->uploadedImage( - 'images/tennants/onticket/onticket_logo.png', - 'onticket_logo.png', - ), - 'footer_logo' => $this->uploadedImage( - 'images/tennants/onticket/onticket_footer_logo.png', - 'onticket_footer_logo.png', - ), - 'favicon' => $this->uploadedImage( - 'images/tennants/onticket/onticket_favicon.svg', - 'onticket_favicon.svg', - ), - 'header_bg_image' => $this->uploadedImage( - 'images/tennants/onticket/onticket_header_background.png', - 'onticket_header_background.png', - ), - ]); - } elseif ($onTicketTenant->header_bg_image_id === null) { - $this->tenantService->update($onTicketTenant, [ - 'header_bg_image' => $this->uploadedImage( - 'images/tennants/onticket/onticket_header_background.png', - 'onticket_header_background.png', - ), - ]); - } - - if ($onTicketTenant !== null && $onTicketTenant->storefront_website_type_code === 'onticket') { - $onTicketTenant->update(['storefront_website_type_code' => 'onticket_multi_event']); - } - - $onTicketTenant = Tenant::query()->where('codigo', 'onticket')->firstOrFail(); - - foreach (['Música', 'Teatro', 'Deportes', 'Infantiles', 'Otros'] as $nombre) { - $onTicketTenant->eventCategories()->firstOrCreate(['nombre' => $nombre]); - } - - if ($onTicketTenant->storefront_website_type_code === 'onticket_multi_event' && ! $onTicketTenant->display_seach_bar) { - $onTicketTenant->update(['display_seach_bar' => true]); - } - - if ( - $onTicketTenant->storefront_website_type_code === 'onticket_multi_event' - && ! $onTicketTenant->websiteExtras()->whereHas('websiteTypeExtra', fn ($query) => $query->where('codigo', 'immersiveHero'))->exists() - ) { - $this->websiteExtraService->updateForTenant($onTicketTenant, 'immersiveHero', [ - 'eyebrow' => 'Encendé tu', - 'title' => 'experiencia', - 'description' => 'Reservá tu entrada y formá parte', - ]); - } - - $this->call(OnticketImmersiveHeroCarouselSeeder::class); + $this->call(OnTicketTenantSeeder::class); $this->deleteTenant('sonder');