From cdc17a13e28695f95263e31ab471792489c6bb44 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 18 Sep 2026 12:19:03 -0300 Subject: [PATCH] feat(catalog): add event_id to catalog items and backfill existing records --- .../Commerce/Catalog/Models/CatalogItem.php | 8 ++++++ .../Requests/StoreCatalogItemRequest.php | 1 + ...8_000700_add_event_id_to_catalog_items.php | 26 +++++++++++++++++++ ...18_000800_backfill_catalog_item_events.php | 25 ++++++++++++++++++ .../seeders/DesfilePuraTendenciaSeeder.php | 9 +++++++ .../FiestaFutbolInfantilProductSeeder.php | 7 +++++ .../Catalog/CatalogItemControllerTest.php | 25 ++++++++++++++++++ 7 files changed, 101 insertions(+) create mode 100644 database/migrations/2026_09_18_000700_add_event_id_to_catalog_items.php create mode 100644 database/migrations/2026_09_18_000800_backfill_catalog_item_events.php diff --git a/app/Domains/Commerce/Catalog/Models/CatalogItem.php b/app/Domains/Commerce/Catalog/Models/CatalogItem.php index 3d801ca..0e38d99 100644 --- a/app/Domains/Commerce/Catalog/Models/CatalogItem.php +++ b/app/Domains/Commerce/Catalog/Models/CatalogItem.php @@ -8,6 +8,7 @@ use App\Domains\Commerce\Catalog\Enums\InventoryPolicy; use App\Domains\Commerce\Catalog\Enums\InventorySubject; use App\Domains\Commerce\Catalog\Services\CatalogInventoryService; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Event\Models\Event; use App\Domains\Ticketing\Ticket\Models\Ticket; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Builder; @@ -54,6 +55,7 @@ class CatalogItem extends Model protected function casts(): array { return [ + 'event_id' => 'integer', 'category_id' => 'integer', 'brand_id' => 'integer', 'inventory_id' => 'integer', @@ -73,6 +75,12 @@ class CatalogItem extends Model return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo'); } + /** @return BelongsTo */ + public function event(): BelongsTo + { + return $this->belongsTo(Event::class); + } + /** @return BelongsTo */ public function category(): BelongsTo { diff --git a/app/Domains/Commerce/Catalog/Requests/StoreCatalogItemRequest.php b/app/Domains/Commerce/Catalog/Requests/StoreCatalogItemRequest.php index 0178517..186d393 100644 --- a/app/Domains/Commerce/Catalog/Requests/StoreCatalogItemRequest.php +++ b/app/Domains/Commerce/Catalog/Requests/StoreCatalogItemRequest.php @@ -25,6 +25,7 @@ class StoreCatalogItemRequest extends FormRequest return [ 'tenant_code' => ['prohibited'], + 'event_id' => ['prohibited'], 'type' => ['sometimes', Rule::enum(CatalogItemType::class)], 'category_id' => [ 'sometimes', diff --git a/database/migrations/2026_09_18_000700_add_event_id_to_catalog_items.php b/database/migrations/2026_09_18_000700_add_event_id_to_catalog_items.php new file mode 100644 index 0000000..543bfab --- /dev/null +++ b/database/migrations/2026_09_18_000700_add_event_id_to_catalog_items.php @@ -0,0 +1,26 @@ +foreignId('event_id') + ->nullable() + ->after('tenant_code') + ->constrained('events') + ->nullOnDelete(); + }); + } + + public function down(): void + { + Schema::table('catalog_items', function (Blueprint $table): void { + $table->dropConstrainedForeignId('event_id'); + }); + } +}; diff --git a/database/migrations/2026_09_18_000800_backfill_catalog_item_events.php b/database/migrations/2026_09_18_000800_backfill_catalog_item_events.php new file mode 100644 index 0000000..52c1a19 --- /dev/null +++ b/database/migrations/2026_09_18_000800_backfill_catalog_item_events.php @@ -0,0 +1,25 @@ +whereIn('codigo', ['desfile_pura_tendencia', 'fiesta_futbol_infantil']) + ->whereNotNull('active_event_id') + ->get(['codigo', 'active_event_id']) + ->each(function (object $tenant): void { + DB::table('catalog_items') + ->where('tenant_code', $tenant->codigo) + ->update(['event_id' => $tenant->active_event_id]); + }); + } + + public function down(): void + { + // The previous event associations cannot be recovered after this backfill. + } +}; diff --git a/database/seeders/DesfilePuraTendenciaSeeder.php b/database/seeders/DesfilePuraTendenciaSeeder.php index dd27313..a9f9902 100644 --- a/database/seeders/DesfilePuraTendenciaSeeder.php +++ b/database/seeders/DesfilePuraTendenciaSeeder.php @@ -51,6 +51,15 @@ class DesfilePuraTendenciaSeeder extends Seeder }); } + $activeEventId = DB::table('tenants') + ->where('codigo', self::TENANT_CODE) + ->value('active_event_id'); + if ($activeEventId !== null) { + DB::table('catalog_items') + ->where('tenant_code', self::TENANT_CODE) + ->update(['event_id' => $activeEventId]); + } + $this->invitationPurchaseProvisioner->provision(); } diff --git a/database/seeders/FiestaFutbolInfantilProductSeeder.php b/database/seeders/FiestaFutbolInfantilProductSeeder.php index 58dcfdd..d7cbb21 100644 --- a/database/seeders/FiestaFutbolInfantilProductSeeder.php +++ b/database/seeders/FiestaFutbolInfantilProductSeeder.php @@ -15,6 +15,7 @@ use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Ticketing\Ticket\Enums\ValidityTimeType; use App\Domains\Ticketing\Ticket\Models\ValidityTime; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\DB; use RuntimeException; class FiestaFutbolInfantilProductSeeder extends Seeder @@ -173,6 +174,12 @@ class FiestaFutbolInfantilProductSeeder extends Seeder 'group_name' => 'Productos', 'group_order' => 0, ]); + + if ($tenant->active_event_id !== null) { + DB::table('catalog_items') + ->where('tenant_code', $tenant->codigo) + ->update(['event_id' => $tenant->active_event_id]); + } } private function deleteExistingCatalog(Tenant $tenant): void diff --git a/tests/Feature/Catalog/CatalogItemControllerTest.php b/tests/Feature/Catalog/CatalogItemControllerTest.php index 8eda289..9566a6f 100644 --- a/tests/Feature/Catalog/CatalogItemControllerTest.php +++ b/tests/Feature/Catalog/CatalogItemControllerTest.php @@ -8,7 +8,9 @@ use App\Domains\Commerce\Catalog\Models\Attribute; use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Shared\Enums\FieldType; use App\Domains\Core\Tenant\Models\Tenant; +use App\Domains\Ticketing\Event\Models\Event; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Storage; use Tests\TestCase; @@ -48,6 +50,7 @@ class CatalogItemControllerTest extends TestCase $response ->assertCreated() ->assertJsonPath('data.nombre', 'Shirt') + ->assertJsonMissingPath('data.event_id') ->assertJsonMissingPath('data.group_order') ->assertJsonPath('data.max_units_per_user', 4) ->assertJsonCount(2, 'data.images') @@ -60,6 +63,7 @@ class CatalogItemControllerTest extends TestCase $this->assertSame([0, 1], $item->attachments()->get()->pluck('pivot.orden')->all()); $this->assertSame(4, $item->max_units_per_user); $this->assertSame(7, $item->group_order); + $this->assertNull($item->event_id); $this->assertSame([0], $variant->attachments()->get()->pluck('pivot.orden')->all()); $this->assertDatabaseHas('catalog_items_attachments', [ 'catalog_item_id' => $item->id, @@ -69,6 +73,27 @@ class CatalogItemControllerTest extends TestCase ]); } + public function test_event_id_is_an_internal_nullable_catalog_item_link(): void + { + $this->assertTrue(Schema::hasColumn('catalog_items', 'event_id')); + + $tenant = $this->createTenant('catalog-event-link'); + $event = Event::query()->create([ + 'tenant_code' => $tenant->codigo, + 'title' => 'Internal event', + ]); + + $this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [ + 'slug' => 'client-event-id', + 'nombre' => 'Client event ID', + 'precio' => 100, + 'real_stock' => 1, + 'event_id' => $event->id, + ])->assertUnprocessable()->assertJsonValidationErrors('event_id'); + + $this->assertDatabaseMissing('catalog_items', ['slug' => 'client-event-id']); + } + public function test_it_validates_images_before_creating_the_catalog_item(): void { $tenant = $this->createTenant('validation');