Refactor Catalog API: Remove Create Catalog Item Endpoint and Update Related Logic

- Removed the "Create Catalog" endpoint from the Postman collection.
- Updated the CatalogController to handle tenant-specific catalog items based on active events.
- Refactored CatalogItem model to automatically set event_id based on tenant's active_event_id during creation.
- Deleted StoreCatalogItemRequest and CatalogItemResource as they are no longer needed.
- Updated various services and controllers to utilize the new tenant-based catalog item retrieval logic.
- Added tests to ensure that catalog reads are limited to items associated with the active event.
This commit is contained in:
2026-09-18 13:51:13 -03:00
parent cdc17a13e2
commit 22ef5619f3
21 changed files with 107 additions and 482 deletions

View File

@@ -247,24 +247,14 @@ class BundleCatalogItemTest extends TestCase
]);
}
public function test_catalog_api_creates_and_returns_bundle_details(): void
public function test_catalog_api_returns_bundle_details_created_by_the_service(): void
{
$component = $this->createStandardItem('api-component', 8);
$bundle = $this->createBundle('api-bundle', [
['catalog_item_id' => $component->id, 'quantity' => 2],
], '75.00');
$bundleId = $this->postJson("/api/tenants/{$this->tenant->codigo}/catalog-items", [
'type' => CatalogItemType::Bundle->value,
'slug' => 'api-bundle',
'nombre' => 'API Bundle',
'precio' => 75,
'components' => [
['catalog_item_id' => $component->id, 'quantity' => 2],
],
])
->assertCreated()
->assertJsonPath('data.type', CatalogItemType::Bundle->value)
->json('data.id');
$this->getJson("/api/tenants/{$this->tenant->codigo}/catalog-items/{$bundleId}")
$this->getJson("/api/tenants/{$this->tenant->codigo}/catalog-items/{$bundle->id}")
->assertOk()
->assertJsonPath('data.type', CatalogItemType::Bundle->value)
->assertJsonPath('data.maximum_addable_quantity', 4)
@@ -274,7 +264,9 @@ class BundleCatalogItemTest extends TestCase
->assertJsonPath('data.components.0.variant_id', null)
->assertJsonPath('data.components.0.quantity', 2);
$this->postJson("/api/tenants/{$this->tenant->codigo}/catalog-items", [
$this->expectException(ValidationException::class);
$this->catalogService->create([
'tenant_code' => $this->tenant->codigo,
'type' => CatalogItemType::Bundle->value,
'slug' => 'bundle-with-stock',
'nombre' => 'Invalid Bundle',
@@ -283,9 +275,7 @@ class BundleCatalogItemTest extends TestCase
'components' => [
['catalog_item_id' => $component->id, 'quantity' => 1],
],
])
->assertUnprocessable()
->assertJsonValidationErrors(['real_stock']);
]);
}
public function test_bundle_components_are_preserved_when_the_bundle_is_soft_deleted(): void

View File

@@ -14,6 +14,7 @@ use App\Domains\Commerce\Catalog\Models\Category;
use App\Domains\Commerce\Catalog\Models\FeaturedGroup;
use App\Domains\Commerce\Catalog\Models\Inventory;
use App\Domains\Core\Tenant\Models\Tenant;
use App\Domains\Ticketing\Event\Models\Event;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
@@ -22,6 +23,41 @@ class CatalogControllerTest extends TestCase
{
use RefreshDatabase;
public function test_active_event_limits_catalog_reads_to_its_items(): void
{
$tenant = $this->createTenant('active-event-catalog');
$group = $this->createGroup($tenant, ProductLayout::Row, 'Events', groupLayout: GroupLayout::Simple);
$group->update(['source_type' => FeaturedGroupSource::All]);
$activeEvent = Event::query()->create(['tenant_code' => $tenant->codigo, 'title' => 'Active']);
$otherEvent = Event::query()->create(['tenant_code' => $tenant->codigo, 'title' => 'Other']);
$activeItem = $this->createItem($tenant, 'Active item', Inventory::query()->create(['real_stock' => 5]));
$activeItem->event_id = $activeEvent->id;
$activeItem->save();
$otherItem = $this->createItem($tenant, 'Other item', Inventory::query()->create(['real_stock' => 5]));
$otherItem->event_id = $otherEvent->id;
$otherItem->save();
$tenant->update(['active_event_id' => $activeEvent->id]);
$this->getJson("/api/tenants/{$tenant->codigo}/catalog")
->assertOk()
->assertJsonCount(1, '0.items')
->assertJsonPath('0.items.0.id', $activeItem->id)
->assertJsonMissing(['nombre' => 'Other item']);
$this->getJson("/api/tenants/{$tenant->codigo}/catalog-items?q=item")
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.id', $activeItem->id);
$this->getJson("/api/tenants/{$tenant->codigo}/catalog-items/{$otherItem->id}")
->assertNotFound();
$this->getJson("/api/tenants/{$tenant->codigo}/catalog-items/{$activeItem->id}")
->assertOk();
}
public function test_row_and_column_with_cart_return_item_details_variants_and_maximum_quantity(): void
{
$tenant = $this->createTenant('catalog-index');

View File

@@ -1,156 +0,0 @@
<?php
namespace Tests\Feature\Catalog;
use App\Shared\Attachable\Enums\AttachmentType;
use App\Shared\Attachable\Models\Attachment;
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;
class CatalogItemControllerTest extends TestCase
{
use RefreshDatabase;
public function test_it_creates_a_catalog_item_with_item_and_variant_images(): void
{
Storage::fake('s3');
$tenant = $this->createTenant();
$attribute = Attribute::query()->create([
'tenant_codigo' => $tenant->codigo,
'codigo' => 'size',
'nombre' => 'Size',
'type' => FieldType::String,
]);
$image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
$response = $this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [
'slug' => 'shirt',
'nombre' => 'Shirt',
'group_order' => 7,
'precio' => 100,
'max_units_per_user' => 4,
'attribute_codes' => [$attribute->codigo],
'images' => [$image, $image],
'variants' => [
[
'real_stock' => 5,
'values' => ['size' => 'M'],
'images' => [$image],
],
],
]);
$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')
->assertJsonCount(1, 'data.variants')
->assertJsonCount(1, 'data.variants.0.images');
$item = CatalogItem::query()->where('slug', 'shirt')->firstOrFail();
$variant = $item->variants()->firstOrFail();
$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,
'variant_id' => $variant->id,
'orden' => 0,
'is_enabled' => true,
]);
}
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');
$this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [
'slug' => 'invalid-image',
'nombre' => 'Invalid image',
'precio' => 100,
'real_stock' => 1,
'images' => ['not-an-image'],
])->assertUnprocessable()->assertJsonValidationErrors('images.0');
$this->assertDatabaseMissing('catalog_items', ['slug' => 'invalid-image']);
}
public function test_it_rejects_a_non_positive_user_purchase_limit(): void
{
$tenant = $this->createTenant('purchase-limit-validation');
$this->postJson("/api/tenants/{$tenant->codigo}/catalog-items", [
'slug' => 'invalid-purchase-limit',
'nombre' => 'Invalid purchase limit',
'precio' => 100,
'real_stock' => 10,
'max_units_per_user' => 0,
])->assertUnprocessable()->assertJsonValidationErrors('max_units_per_user');
$this->assertDatabaseMissing('catalog_items', ['slug' => 'invalid-purchase-limit']);
}
private function createTenant(string $code = 'catalog-controller'): Tenant
{
$headerLogo = $this->createAttachment("{$code}-header");
$footerLogo = $this->createAttachment("{$code}-footer");
return Tenant::query()->create([
'codigo' => $code,
'nombre' => ucfirst($code),
'dominio' => "{$code}.local",
'primary_color' => '#000000',
'secondary_color' => '#000000',
'danger_color' => '#000000',
'success_color' => '#000000',
'header_bg_color' => '#000000',
'footer_bg_color' => '#000000',
'header_logo_id' => $headerLogo->id,
'footer_logo_id' => $footerLogo->id,
]);
}
private function createAttachment(string $name): Attachment
{
return Attachment::query()->create([
'path' => "test/{$name}.png",
'filename' => "{$name}.png",
'type' => AttachmentType::Image,
'mime_type' => 'image/png',
]);
}
}